📜

Cycloneのソースリポジトリを蘇生してみる (前編)

2022/07/23に公開

Cycloneとは

CycloneはRustのリージョン推論の原型のひとつになった実験的なプログラミング言語です。

現在はメンテナンスされていませんが、歴史的な意義があることからCycloneのビルド環境を整備してみました。

(完結するかは未定)

ソースの取得

CycloneのWebサイトは生きているので、ソースはCycloneのDownloadページから取得できます。しかしここに不穏な文言があります。

If you use gcc 4, you must get the latest version of Cyclone from SVN (see below).

最新安定版よりも新しい版があること、またgccのバージョンに依存して壊れることが読み取れます。そしてSubversionと書いてあることから嫌な予感がした人もいると思いますが、このリポジトリは既に動いていません。

というわけで野良クローンを探すと、gitでクローンしたリポジトリがGitHub上に存在しました。

https://github.com/dagit/Cyclone-Language

それなりに新しそうな雰囲気もあります (新しいといっても2009年とかですが)。 今回はこれをありがたく使うことにします🙏🙏🙏🙏。知らん奴のリポジトリを信頼するなという忠告は黙殺します。

こういうのはいつ無くなるかわからないのでとりあえず複製を作っておきます。

https://github.com/qnighy/Cyclone-Language

64bitで動かない

とりあえずソースを取得して ./configure するとコケます (コンソールログ略)。ポインタ長のチェックがありそこで弾かれています。

config/configure.ac が大元っぽいので、怪しい箇所をコメントアウトしてconfigureを通してみます。

autoreconf config/configure.ac && mv config/configure configure
./configure

これでconfigureは通りますが、案の定makeするとむちゃくちゃwarningが出てやがてsegfaultします (コンソールログ略)。

64bitで動くようにしたいところですが、ここで問題があります。Cycloneコンパイラはセルフホストされているので、まずはコンパイラを作らないと修正もままならないのです。というわけで一旦64bitで動かすのはあきらめて32bitの環境を作ります。手元環境はUbuntuなので、DebianのMultiarch/HOWTOを参考に環境を整えます。

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libc6-dev:i386 libc6-dbg:i386 ligcc-9-dev:i386

-m32 が渡った状態でconfigureすると通ります。なぜかarchはx86_64-unknown-linux-gnuということになっていますがとりあえずこのまま強行します。

CC="gcc -m32 -g" ./configure

セルフホストについて

セルフホストについて説明してなかったので少し書いておきます。要するにCycloneコンパイラ自体はCycloneコンパイラで書かれているので鶏卵問題/ブートストラップ問題が起きています。

ではこの問題をCycloneではどう解決しているかというと、CycloneはCへのトランスパイラでバックエンドはgccに任せているので、トランスパイル済みのCycloneコンパイラのCコードを同梱することで入口を作っています。Cycloneが作られた当時はLLVMのような使いやすいコンパイラバックエンドは無かった (orメジャーではなかった) はずなので、割と自然な判断ではないかと思います。

ただ、現代でいうところのBabelなどのように入力と出力の対応関係が比較的明確なトランスパイラとは違い、Cycloneの場合はかなり色々と展開したものが出てくるので、手で修正するのはかなり厳しいです。

Boehm GC

-m32 をつけると途中まではいい感じで進みますが、やはり生成したツールの実行でsegfaultが出てしまいます (コンソールログ略)。

仕方ないのでvalgrindをつけて再実行してみると (というかx86_64のvalgrindがi386のコードをどうやって実行しているんだ??) 、実はCyclone本体じゃなくてBoehm GCの初期化処理で失敗していることがわかります (コンソールログ略)。

Boehm GCのことはよくわかりませんが、まあどうせ新しくすれば直るだろうということで最新リリースのtarballを引っぱってきて置き換えます。7.1から8.0.6にしました。

これで問題の箇所は通過できました。更新されているソフトウェアは神。そして、更新を続けている方々も神です。

https://github.com/qnighy/Cyclone-Language/commit/2cf660492a5effca72e873c6fc41d1415a24a4d2

Error reading spec file

./configure結果の一部は bin/lib/cyc-lib/x86_64-unknown-linux-gnu/cycspecs というファイルに吐かれるのですが、これの読み取り中になんかエラーが出ます。ここで出てるみたいです。

CPPのデフォルト定義の一覧が4096文字からはみ出てるのが原因っぽいです。こういうのはだいたい積み増しされていく運命にあるから仕方ないね。というわけで定数を書き換えて65536とかにしてしまいます。

お気付きのことかと思いますが、「CycloneコンパイラをビルドできないとCycloneコンパイラは修正できない」と言ったそばから生成されたCソースを書き換えています。幸運にも定数4096はファイル内で1箇所しかなかったので、生成されたコードの4096を全部65536に置き換えればOKというタネです。

https://github.com/qnighy/Cyclone-Language/commit/4ffcb59a65dc38ccdecf08cb198cebc664ac2d13

<header> is not supported on this platform

さらに進めていくと "stdlib.h is not supported on this platform" みたいなエラーが出てきます。「stdlib.hがサポートされていないプラットフォーム」……って、そんなことあるかい。

これは結論から言うと最近のCの構文とか最近のGCCの独自構文をCycloneのパーサーが認識できないことが問題です。ログが build/x86_64-unknown-linux-gnu/include/BUILDLIB.LOG に出ているので確認しています。いやーロギングをちゃんと行っているソフトウェアは神。

stdlib.h:
(中略)
Got Core::Failure(list_t<decl_t,`H>)
Not supported on this platform

??????????
??????????

前言撤回です。何もわかりませんでした。

後知恵で説明すると、これはCycloneが実装している独自Bison内のエラーで、非終端記号のsemantic valueに代入したときの型と取り出そうとしたときの型が違うことで発生しています。多分構文エラーからの復帰にバグがあるんじゃないかな。さすが安全性を重視する言語の先駆けだけあってちゃんとエラーになって偉い。でもこのログじゃわからん。

CPPのオプションをいじる

↑の処理はそもそも何をしているかというと、CのヘッダーからCycloneのヘッダーを作っています。OSでどんな関数が使えるかとか各種定数の値とかは環境固有なのでCヘッダーから取得したいです。一方でCのヘッダーにはCycloneの型チェックに必要な情報が不足しているので、 libc.cys というファイルに書いておきます。この2つの情報源を合わせてCycloneのヘッダーを作るのがbuildlibというツールの仕事で、その中でパーサーが失敗しているせいでヘッダーがうまく作れてないというわけです。

Cのヘッダーをどうパースしているのかというと、プリプロセスまではGCCに任せてそこから先は自前のパーサーでパースしています。すごい。プリプロセッサだけを実行する方法はStackOverflowとかに書いてある方法と同じです。

# プリプロセッサ定義を取り出す
echo "#include <stdlib.h>" | $(CC) -dM -E -
# Cの定義を取り出す
echo "#include <stdlib.h>" | $(CC) -E -

なので、まずこのプリプロセッサのオプションをいじる方法を探します。先ほどいじっていた cycspecs というファイルがconfigureの出力なので、ここをいじればどうやら反映されることはわかりました。

*cyclone_cc:
  gcc -m32 -g
  # ↑ここをいじるとbuildlibの挙動もいい感じに変わる

ただ(後からわかったことですが)これをいじるとbuildlibより後のフェーズでのgccのオプションも変わってしまいます。これは色々と困るんですが、特に -I でインクルードパスを挿入したいときに困ります。OSのstdlib.hから生成されるCycloneヘッダの名前もstdlib.hなのですが、

  • buildlibを使うときは、stdlib.hを独自版に差し替えてデバッグしたい
  • 以降ではCycloneヘッダのほうが優先されてほしい

というのがうまく反映されなかったりします。

そこでbuildlibを呼んだときだけオプションを足す方法を考えます。buildlib内でgccを呼び出している箇所を見ると、cycspecs由来のオプション以外に cppargs というのを渡していて、これはコマンドラインオプション由来であることがわかりました。

ブートストラップ時のbuildlibの呼び出しはこの行で行われているので、ここにオプションを足せばOKです。 (この正解を引き当てるのも結構大変だったけど面白くないので省略)

他にもbuildlibの呼び出し箇所はあるんですが、まあ最初に作ったCycloneヘッダを使い回せば当面困らないので放置しておきます。

さてここで困ったのが、コマンドラインオプションはどうやって渡すのが正解なのか?という点です。

最初はbuildlibの中をブラックボックスと思って試せばうまくいくかなーと思ったのですがなかなか正解を引き当てられなかったので、もう少し情報を見ながら探索することにしました。そこでstraceの登場です。

strace -f -e execve -o strace.txt 以下コマンド

とやると、中で起動されたコマンドが全部記録されるので、所望のgccコマンドが起動されているかどうかを確認しました。すると以下のことがわかりました。

  • buildlibは - で始まる引数のうち自身が理解したなかったものをgccにフォワードする
  • 特に -I /path/to/dir と書くと -I はgccにフォワードされるが /path/to/dir はbuildlibに食われて生き別れになってしまうのが罠だった
    • -I/path/to/dir ならOK

これで無事にテストできるようになりました

(なお後でわかったことですが、buildlibに -v をつけると内部で実行しているgccコマンドが表示されるので、これが先にわかっていればstraceを使う必要はありませんでした)

二分探索

これでプリプロセッサに干渉する手段を得ました (実際の手順は少し前後していて、先に二分探索をしてからbuildlibのオプションを調べています)。これにより本来OSが持っているファイルのかわりに自分たちのファイルをCycloneに食わせることができます。

なのでデバッグをしていきます。プリプロセッサはだいたい羃等なので、先にプリプロセッサを適用したものを作っておきます。 (これだとマクロ定義が消えてしまいますが、デバッグしたいのはパーサーのほうなので結果としてはOKでした)

echo "#include <stdlib.h>" | gcc -m32 -E - > overrides/stdlib.h

このoverridesディレクトリを -I で指定しておきます。

あとは、 overrides/stdlib.h のある行から下を消してbuildlibを実行して成否を見れば、残した範囲内に問題があるかどうかがわかるので、二分探索で問題箇所を絞り込めます。結果としてはだいたい以下のような問題があることがわかりました。

  • _Float128, float128 などの新しめの型定義がパースできない。
  • 特定の条件下で vfscanf, vscanf, vsscanf を __asm__ 関数属性でリネームしようとするが、リネームがうまく認識されず(?)に重複定義が発生する。

ここまでわかったら、展開前のヘッダを確認しながらなぜなに問答をはじめます。「このマクロ定義を無理矢理置き換えればどうにかなるはずだ」というものを見つける作業です。

結論としては以下の2つを入れれば動くようだということがわかりました。

  • -std=gnu99 ... __STDC__ の値が小さくなり、新しいCの構文が使われなくなる
  • -D__GNUC__=0 ... GNU特有の構文が使われなくなる

https://github.com/qnighy/Cyclone-Language/commit/93af03e893f9ae213610731a2af5e655679bfe5c

まとめ

ここまでで32bitのCycloneコンパイラがビルドされるところまではできました。

しかし、ヘッダが変わるとすぐ対処不能になる問題や、64bit対応ができていないのはかなり厳しいです。何とかしたいですね。

……何とかしたら誰が得するんだ????

次: 中編

Discussion