Upgrade to Pro — share decks privately, control downloads, hide ads and more …

chibiccを CILに移植した結果 (完全版)

Kouji Matsui
February 01, 2025

chibiccを CILに移植した結果 (完全版)

第9回Center CLR勉強会で発表した内容です。
発表はYouTube動画でも公開しています。

第9回Center CLR勉強会: https://centerclr.connpass.com/event/341192/
YouTube動画: https://youtu.be/VJrp39HfGwY

GitHub: https://github.com/kekyo/chibicc-cil-build/

chibicc-cil (YouTubeリスト): https://www.youtube.com/playlist?list=PLL43LzwbRhvRL2PkpewoRv0AFVobTtZGt

Rui Ueyama氏ご本人によるchibiccの解説: https://www.youtube.com/watch?v=k6A_FmIcHQE&t=220s

Kouji Matsui

February 01, 2025
Tweet

Video

Transcript

  1. (c) 2025 Kouji Matsui Kouji Matsui – kozy, kekyo NAGOYA

    city, AICHI pref., JP https://github.com/kekyo https://mi.kekyo.net/@kekyo Self employed Center CLR organizer. .NET OSS: IL2C RelaxVersioner Epoxy GitReader FlashCap etc… Bicycle rider
  2. (c) 2025 Kouji Matsui (c) 2025 Kouji Matsui Agenda chibiccとは

    chibicc-cilとは 移植の検討 移植した結果 ラスボス
  3. (c) 2025 Kouji Matsui chibiccとは Rui Ueyama氏のプロジェクト “A small C

    compiler” https://github.com/rui314/chibicc chibicc は、C11 のほとんどの機能を実装した、もう 1 つの小さな C コ ンパイラです。 他の小さなコンパイラと同様に、おそらく「おもちゃのコンパイラ」のカ テゴリに分類されるかもしれませんが、chibicc は、コンパイルされたプ ログラムに変更を加えることなく、Git、 SQLite、 libpng、chibicc 自体 を含むいくつかの実際のプログラムをコンパイルできます。
  4. (c) 2025 Kouji Matsui chibiccとは 与えられた整数をコンパイルしてコマンドの戻り値 (プロセス終了コード)として返す 42 chibicc gcc

    (gas) tmp.s tmp ソースコード (整数) アセンブリコード コンパイル処理 アセンブル処理 実行コード
  5. (c) 2025 Kouji Matsui chibiccとは 与えられた整数をコンパイルしてコマンドの戻り値 (プロセス終了コード)として返す $ chibicc 42

    $ ./tmp $ echo $? 42 引数に与えた整数(ソースコード)を 戻り値として返す実行ファイルを 生成する chibiccが `tmp` 実行 ファイルを生成する 「ソースコード」は整数限定です。 こんなのC言語とは呼べませんが、 それは第一歩だからです
  6. (c) 2025 Kouji Matsui (c) 2025 Kouji Matsui Agenda chibiccとは

    chibicc-cilとは 移植の検討 移植した結果 ラスボス
  7. (c) 2025 Kouji Matsui 有用なOSSライブラリがネイティブコードだと、実行環境に合 わせてバイナリを全部パッケージに含めなければならない (x86, x86-64, armel, aarch64,

    …) これを避けるために、ライブラリを.NETに移植しようとすると、 C言語で書かれたコードをC#に移植する必要がありますが、こ れが出来る技術者はそうそう居ないので… ◦ FlashCapで非常に苦労しましたもうやりたくないです .NETって
  8. (c) 2025 Kouji Matsui .NET/.NET Core/.NET Frameworkで使われる、CIL (Common Intemediate Language)の事です。

    CILは.NETにおけるJava Bytecodeのような中間言語仕様で、 ECMA-335で定義されています。 https://ecma-international.org/publications-and- standards/standards/ecma-335/ CILとは
  9. (c) 2025 Kouji Matsui (c) 2025 Kouji Matsui Agenda chibiccとは

    chibicc-cilとは 移植の検討 移植した結果 ラスボス
  10. (c) 2025 Kouji Matsui 移植の検討 C言語関数との整合性を持たせることを考える必要がある: ◦ ポインタの扱いはCILにポインタ型があるので、1:1で対応できる。 ◦ CILには構造体(ValueType)が存在するので、C言語構造体はだいたい

    1:1に対応出来る。共用体に対応するものは無いので、ValueTypeで代 用する。 ◦ CILにはP/Invokeという、ネイティブライブラリ(*.so)を呼び出せる機 能があるので、これを使えばシームレスにネイティブライブラリと結合 できるかも? (少なくともC#よりは簡単に)
  11. (c) 2025 Kouji Matsui 移植の検討 ◦ CILアセンブラは.NET標準のilasmがあるけど、これが色々扱いにくい。 CILアセンブラ作るのはそれほど難易度が高いわけではないので、 chibiccのバックエンドとして扱いやすいchibiasを作るか… ◦

    POSIX環境処理系として違和感を与えたくないので、普通に想定される chibildやchibiarも必要だよね。これらがないと、そもそもOSSのコード とかビルドできそうにない(Cの世界において、MSBuild周りのツール チェインは全く必要とされない) 。
  12. (c) 2025 Kouji Matsui (c) 2025 Kouji Matsui Agenda chibiccとは

    chibicc-cil 移植の検討 移植した結果 ラスボス
  13. (c) 2025 Kouji Matsui 移植した結果 そして… chibicc: 305/316 chibicc-cil: 367

    増えているのはCILへの 対応分です。 残り11コミット
  14. (c) 2025 Kouji Matsui 移植した結果 ldc.i4 %d で評価スタックに 数値をpushし、retする ilasm向けのCLIコードを出力

    public static class Program public static int Main(string[] args) chibiccの最初のコミットの 移植
  15. (c) 2025 Kouji Matsui 移植した結果 ◦ この手法を大きく変えないようにするために、chibicc-cilでもメンバー アクセスはオフセット計算で行います。 コンパイル時に オフセットを計算する

    コードジェネレータの構造が、ポインタとオフ セットアクセスに強く依存しているので、 逸脱すると大幅に変更する必要がある
  16. (c) 2025 Kouji Matsui 移植した結果 構造体のメンバーアクセスは(特にポインター型を含む場合)、 コンパイル時ではなく実行時に解決する必要がある。 ◦ “void*”や”intptr_t”は、普通のC言語処理系では「コンパイル時にター ゲット環境に合わせて32/64bit(4/8バイト)」と仮定出来るが、.NETの

    場合は実行時までわからない。 ◦ オフセット計算をコンパイラ内部で行うのではなく、オフセット計算式 をコードとして出力し、実行時にこのコードを計算することによってオ フセットを確定させる。
  17. (c) 2025 Kouji Matsui 移植した結果 関連する問題として、アライメントの指定と計算: ◦ CLRがどのようにメンバーをメモリ上に配置するのかは不透明なので、 必ずchibiccが出力するオフセット計算と一致するようにしなければなら ない。

    ◦ 構造体メンバーのオフセット計算は行う。グローバル変数とかのアライ メント計算は無視する(常にオフセット0から始まると仮定する)。 ◦ 多分、SSEやAVX intrinsicで、グローバル変数__int128を128バイト境界に配置したいという ような要求。.NETでintrinsicを想定するのは意味不明だと思うので、出来なくても問題なさそ う。
  18. (c) 2025 Kouji Matsui 移植した結果 パフォーマンスが悪すぎる: ◦ オフセット計算式をそのまま出力しても動作するが、非常にパフォーマ ンスが悪い。 ◦

    タイプイニシャライザで一度だけ計算して、結果をスタティックフィールドに保持するような コードを出力することでキャッシュし、構造体メンバーにアクセスするときはその値を使用す るようなコードを出力する。 後続フィールドの計算は式が増 えていくのでコストが高い
  19. (c) 2025 Kouji Matsui 移植した結果 ◦ オフセット計算式自体も、コンパイラ内部で計算可能な部分は計算して おく。このために簡約器(一種のインタプリタ)を実装した。 ◦ 直値の検出と、直値同士の計算をコンパイラ内で行う。

    ◦ 直値が条件となる分岐式(例えば “1 != 0 ? o1 : o2”)は、判定もコンパイラ内で行う。 ◦ おそらくこれを発展させれば、オプティマイザ機能に昇華出来ると思われる(未実施)。 ◦ これでかなりパフォーマンスが向上した(恐らく5倍から20倍程度)。 但し、現在の実装はそこ まで万能でもない…
  20. (c) 2025 Kouji Matsui 移植した結果 ◦ コードジェネレータの基本構造に手を入れて、関数の戻り値が存在する かどうかを追跡するように変更。 ◦ 関数のシグネチャを無理やり偽ると、x86-64と同様に未定義動作:

    ◦ x86-64: voidを返す関数でintを返すように偽ると、でたらめな値が返される(raxの偶然の値 が読める)。 ◦ CLI: voidを返す関数でintを返すように偽ると、クラッシュする。 無理やりキャストして呼び出す
  21. (c) 2025 Kouji Matsui 移植した結果 C言語の配列は、値型配列に相当するが、対応するCILの型が無 い。 ◦ .NETで配列と言えば、System.Array型の「参照型 (ObjRef)」であり、

    値型(ValueType)ではない。 ◦ C言語の配列は、.NETにおける値型と同じ。配列を指すポインターを計 算することで、初めて参照型と同じような立ち位置になる(C言語のキ モい構文規則のためにわかりにくい)。 ◦ .NETのSystem.Arrayをそのまま利用させると、ポインター計算との整 合性を取ることが困難。やはり値型として振る舞う必要がある。
  22. (c) 2025 Kouji Matsui 移植した結果 ◦ 同じような構造体型が多数生成されてしまうので、型名に規則性を持た せて、同じ型名なら再利用可能にする(後述)。 ◦ メンバーアクセス時は「オフセット計算式の出力をコンパイラが行う」

    ので、メンバーがprivateでも問題ない。ストレージスペースだけ担保で きれば良い。 注意: メタデータ上のメンバーレイ アウトが、オフセット計算式と厳密 に一致する必要がある
  23. (c) 2025 Kouji Matsui 移植した結果 異なる生成イメージ内の全く同じ名称の型が、CILメタデータ 上では区別されてしまう。 ◦ 前述の値型配列でも(特に大量に)発生。 ◦

    .NETでは、異なるアセンブリに定義された同じ名称の型が区別される。 ◦ C言語では、処理系が出力したタイミングが異なるだけで、同じヘッダ ファイルを使用していれば、定義された型が同じと見なす必要がある (わざと定義を変えない限り)。
  24. (c) 2025 Kouji Matsui 移植した結果 ◦ 万が一問題が起きる場合は… 構造体をポインター渡しに変更して、受信 側で内容を(サイズ基準で)コピーするとかですかね… ◦

    NOTE: chibicc-cilは全てがプリミティブ型か値型であり、参照型 (ObjRef)を扱わないので、値渡しの型が違っていても問題ないと推測 (ObjRefの物理レイアウトはCLRの仕様から見て不透明で、値型のコ ピーよりも何か問題が起きそうではある)。
  25. (c) 2025 Kouji Matsui 移植した結果 VLA (可変長配列・Variable length array)への対応。 ◦

    alloca()に相当。実行時にサイズを決定できて、ストレージはスタックに 配置される。 ◦ C#で言う所のstack_alloc。なので、alloca()の単純なマッピングは実現 可能。CILのlocalloc命令 (https://learn.microsoft.com/en- us/dotnet/api/system.reflection.emit.opcodes.localloc)を使えばいい。
  26. (c) 2025 Kouji Matsui 移植した結果 ◦ スクラッチバッファとして使う箇所を全部テンポラリローカル変数に格 納するようにすれば対応は可能だが... ◦ C#はそうしていた。OpCodeだけ見ると、スクラッチバッファをわざわざローカル変数として

    割り当てていて無駄なコードだなとは思っていたけど、どうやらこれが背景にありそう。 ◦ そういうわけで諦めた。多分VLA使う人いない… ローカル変数に保存(stloc)して直ぐにまたローカル変数から 値を取り出す(ldloc)操作は、一見無意味のように思える
  27. (c) 2025 Kouji Matsui 移植した結果 移動するグローバル変数 ◦ 以下のコード、fooの保持位置がメモリ上で移動する (.NET Core以降)

    ◦ スタティック変数なので、GCHandle.Alloc()でpinする事も出来ない… ◦ &fooとかやられたらアウト。 ◦ .NETの世界に閉じていれば、気が付かないと思われる(Issueにもない) 仮想メモリ空間内で 移動する…
  28. (c) 2025 Kouji Matsui 移植した結果 MMX, SSE, AVXなどのintrinsic ◦ .NET環境でこれに対応させる意味が良く分からん感あるのでスルー

    まともなデバッグ情報を出したい ◦ ilasmを使っている限り、その制約で無理ゲーなので、chibiasを作ることに。 ◦ 流れでchibiarも作ることになり、結局chibicc-cil-toolchainとした。 ◦ emitされるコードは、Mono.Cecil (https://github.com/jbevain/cecil) を使 用しています。 ◦ ドキュメントはかなり書いたので、これらの使い方はリポジトリを参照してく ださい。 https://github.com/kekyo/chibicc-cil-toolchain
  29. (c) 2025 Kouji Matsui 移植した結果 ランタイムライブラリの用意 ◦ chibicc自身のビルドは、chibicc内で使用している標準関数や定義だけ が用意出来れば良いので、「libc-bootstrap.dll」としてC#で実装。 ◦

    これにより、Stage1, 2, 3コンパイル(つまりセルフビルド)を実現。 ◦ Stage4(完全なCランタイムライブラリを使用するchibiccのビルド)を 実現するために、newlibを移植する。 ◦ newlib (https://sourceware.org/newlib/) は、主に組み込み環境向けのlibc互換実装。しん どかった… 結局完成してません。一応ビルドには成功し、libc.aとlibc.mは生成できました。 ◦ 他にもuclibcやmuslといった選択肢があります。newlibを選択すべきだったかどうかはわかり ません。
  30. (c) 2025 Kouji Matsui 移植した結果 ◦ newlibは内部で「複素数型 _Complex の計算関数」も公開していて、 これを実現するためにはコンパイラが複素数型を扱える必要があります。

    ◦ しかし、chibiccは複素数型を実装しておらず… ◦ つまり、一から複素数型をサポートするコードを実装しました(” .NET related: Added complex type basis.”)。 ◦ といっても、複素数型に対する実数と虚数パートそれぞれへのアクセスと、四則演算に対応す る関数が呼び出せるようにするだけなので、chibiccをここまで移植していれば誰でも実装でき るはずです。その他の複素数計算はnewlibに任せる…
  31. (c) 2025 Kouji Matsui 移植した結果 ◦ 子プロセス起動のハンドリング ◦ fork(), exec()が無いので、posix_spawn()を実装してお茶を濁した。

    ◦ 標準入出力ハンドルをマルチプラットフォーム安全に転送出来ないので、イベントハンドラで データをバイパスするワーカースレッドが必要だった。あまり効率が良くないと思う。 ◦ POSIXシグナル・スレッドの扱い ◦ pthreadの移植をがんばる(一部のみ実装)。内部はもちろん.NETのSystem.Threadingを使う。 ◦ POSIXシグナルは目下の所無視…
  32. (c) 2025 Kouji Matsui 移植した結果 関数の可変引数への対応(vararg) ◦ C#の可変引数(param array)とは違います。相互運用機能。 ◦

    CILのvarargメタデータ・arglist命令・System.ArgIteratorを使う varargメタデータ arglist ArgIterator 一対一に対応していて ネイティブコード連携のために 存在する事がうかがえる
  33. (c) 2025 Kouji Matsui 移植した結果 ◦ CILのvarargメタデータを使うようにする。これの生成はchibildで行う 必要がある。 ◦ 最初、varargを使うもうまく動かない事があった。これはnetstandard

    TFMでのみ発生し、.NETランタイムのissueにも上がっていない問題で、 どうしようもないことから、CIL vararg関連の機能を使わず独自の実装 で回避。 ◦ 独自実装なので、chibiccの世界では問題なく動作する。しかし、ネイ ティブコードとの連携は出来ない。
  34. (c) 2025 Kouji Matsui (c) 2025 Kouji Matsui Agenda chibiccとは

    chibicc-cil 移植の検討 移植した結果 ラスボス
  35. (c) 2025 Kouji Matsui ラスボス 関数の可変引数への対応 (vararg) ◦ vararg命令を使わず独自の実装で回避。これは動作した。 ◦

    しかし、P/Invokeで可変引数関数を呼び出したりコールバックで呼び出 し可能にするには、.NETランタイムがvarargを処理できる必要がある…
  36. (c) 2025 Kouji Matsui ラスボス “Initial thoughts on Native Vararg

    support on Unix platforms for RyuJit” https://github.com/dotnet/runtime/issues/10478 意訳: 「varargはWindowsにしか対応してないよーん」 うそだ!うそだと言ってくれ…!!!
  37. (c) 2025 Kouji Matsui クロージング もしかしたら、将来ランタイムでvararg対応されるかもしれません。しか し、その頃には色々忘れてしまって、もう続けられなさそうです… JVMでもがんばれば出来そうですが、多分sun.misc.Unsafeが〇されてし まったので、難しいと思います… と思ってたら、jdk.unsupportedに移動されたとの事なので、ワンチャン

    あるかも知れません(私はJava詳しくないのでJVM側のアドバイスは出来 ないと思います。 CILでどうやったのかについては質問どうぞ) Unsafeクラス:どんなスピードでも危険: https://blogs.oracle.com/otnjp/post/the-unsafe-class- unsafe-at-any-speed-ja
  38. (c) 2025 Kouji Matsui 質疑応答 成果物を公開します: ◦ chibicc-cil-build: https://github.com/kekyo/chibicc-cil-build この登壇のために最後の整理を行いましたが、基本的にもう触

    る事もないので、PRの対応とか期待しないでください… 類似プロジェクト: Cesium(ForNeVerさん/JBの中の人) 同じ問題がIssueで上がっています(もちろん未解決) https://github.com/ForNeVeR/Cesium