* acl4の開発ログ #10
-(by [[K]], 2026.04.21)
-''acl4開発のもくじ → [[a4_i01]]''
** 2026.03.27(金) #0
-ええとまず「失言ドリブン」という話をさせてください。
-私はかつて TONWS用PC98エミュレータ「V98」というソフトウェアを作りました。これは富士通のFM-TOWNSというパソコンで、NECのPC-9801というパソコン用のソフトウェアを動かすという、そういうものでした。
-なぜこれを作ったのかということですが、まず自分の家には98もTOWNSもあって、だからこのソフトがないと自分が困るということは特にはなかったのです。だけど作ったわけです。
-当時の私はTOWNSが大好きで(今も好きですよ、もちろん)、PC-9801よりもTOWNSのほうが高性能なんだと固く信じておりました。私は学校でも「パソコンに詳しい人」というイメージがあったので、何人かの友人が私にどんなパソコンを買ったらいいかなと相談に来ることがあって、「まあ世間的な普及度を考えたらお勧めするべきはPC-9801だろうけど、私はTOWNSが好きだなー」とか答えていたわけです。
-それで「ちょっと迷ったけど、たぶんわからないことがあったら君に聞くことになると思うから、オレもTOWNSにしたよー」「うわーいいなー、最新モデルじゃん!」みたいな展開が何回か発生したわけです。
-それで友人たちが後悔するとかそういうことはなかったのですが(たぶん)、私は調子に乗って「TOWNSのほうが性能はいいんだ。PC-9801にできることならTOWNSにもできるんだ。たぶんエミュレータだって作れるはずだ」などと何かの拍子に言ってしまい(笑)、さらにいくつかの失言がつながって成り行きで私がそのエミュレータを作って見せるという話になったのです。
-言ったからにはやらなきゃいけないわけです(とはいえ、友人たちはそんな話、ろくに覚えていなかったわけですが)。ということで私は頑張って「V98」を作ってフリーソフトとして公開したのでした。
-この話で私が伝えたいのは「軽率な失言をすると、やむを得ないという状況が発生して、それによって開発が促される(こともある)」ということです。
~
-私は今年に入ってから、軽率な約束を2つしています。それをここに書いておいて、将来の自分を「やむを得ないという状況」に追い込もうと思っています(笑)。
--Buntan-PCプロジェクトのためのCコンパイラを、私が年内に作る。
--OSASKのアプリケーションのkaodunを逆アセンブルなどで解析して、Windowsなど、他の環境でも動くようにする(ソースコードがあれば自作のOSに移植したいなーという声は、時々耳にするのです)。
** 2026.03.27(金) #1
-なんか適当に1時間くらいやれば、浮動小数点を基本型にもつインタプリタくらい作れるだろうと思っていたのですが、なんかいろいろ足りてなくて、今は自分の見積もりの甘さを反省しております・・・。
** 2026.03.29(日) #0
-a4vmの本番用の仮想マシンを作っているのですが、再帰処理で苦戦しています。フィボナッチ数の計算すらうまくいかない状況で、ちょっと途方に暮れています。まあたぶんもうすぐ直ると思いますが・・・。
** 2026.03.29(日) #1
-よし直ったー。
** 2026.03.30(月) #0
-[[a4_0014]]に書いたサンプルプログラムの t0014a.c は、思っていたよりも優秀なので楽しいです。
-明日にはプログラム例を書きます。
** 2026.03.31(火) #0
-[[a4_0014]](A4vm_exec0など)を書きました。これで何ができるのかを明らかにするために、サンプルコードをたくさんつけました。
** 2026.03.31(火) #1
-ここから、プリプロセッサの連鎖でコンパイラを構築するっていうのをやりたいわけだけど、これは初めての挑戦だから、きっと少し時間がかかるだろうなあ・・・。
** 2026.04.01(水) #0
-今日は病気でお休み。
** 2026.04.01(水) #1
-いきなり理想形を考えるのではなく、まずは現状のプリプロセッサだけでまねごとをするとしたらどうすればいいかを考えるのがよさそうだなあ。
** 2026.04.02(木) #0
-変換手順(妄想・暫定版)
[1]
int i, s;
for (i = 0; i < 10; i++)
s += i;
[2]
#define i Int:R00
#define s Int:R01
Lod(i, CInt:0); Lbl(Txt:LT0001);
Add(s, i);
Add(i, CInt:1); Jlt(i, CInt:10, Txt:LT0001);
[3]
Lod(Int:R00, CInt:0); Lbl(Txt:LT0001); Add(Int:R01, Int:R00); Add(Int:R00, CInt:1); Jlt(Int:R00, CInt:10, Txt:LT0001);
[4]
Lod_RI(R00, 0); Lbl_T(LT0001); Add_RR(R01, R00); Add_RI(R00, 1); Jlt_RIT(R00, 10, LT0001);
[5]
Lod_RI(R00, 0); Lbl_T(LT0001); Add_RRR(R01, R01, R00); Add_RRI(R00, R00, 1); Jlt_RIT(R00, 10, LT0001);
-うん、これくらいなら作れそうな気がします。
-まず[4]→[5]は簡単です。
#define Add_RR(r, s) Add_RRR(r, r, s)
-みたいなのをたくさん書けばいいだけだからです。
-それで[3]→[4]をやるには、型付きdefineを作らないといけません。
#typedDef Lod(Int:r, CInt:i) Lod_RI(r, i)
#typedDef Lod(Int:r, Int:s) Lod_RR(r, s)
-では[2]→[3]はどうかというと、これは普通のプリプロセッサです。
-残った[1]→[2]は、これはプリプロセッサではなくて言語です。
** 2026.04.02(金) #1
-[Q] これでCコンパイラができそうなことはなんとなくわかりますが、これにどんなメリットを感じているのかよくわかりません。
-[A] [1]→[2]の変換を見てください。コンパイラはiやsの型を意識するのはdefineの時だけで、それ以降は完全に忘れています。代入はLod、加算はAddと機械的に変換しているだけです。これならコンパイラを作るのは楽でしょう。
-一方で、この型の組み合わせの加算の時はこれに変換する、というルールを後付けで(=コンパイラの改変をすることなく)、いくらでも追加できるわけです。
-型の追加だって思いのままです。String型やComplex型、VectorやMatrixなどの型を追加してもコンパイラはそのままでいいわけです。それらに対してどんな演算を定義するか、後で好きに決めていいし、変えてもいいということです。
-C++のoperatorっぽいことができるわけです。
** 2026.04.02(金) #2
-[1]自分の作品に「主要なテーマ」があるととてもいいと思っています。
-たとえば料理を作るのなら、「〇〇産のこの野菜がとてもおいしいから、この野菜のおいしさが最大限生かされるように、お肉やお魚や調味料を選びたい。調理法も工夫したい」みたいに料理を工夫していけば、何のテーマもなくお料理をする場合よりもずっとおいしいものができると思うのです。
-絵を描くにしても、「この笑顔が忘れられない。これを最大限に生かせるような色合いや背景にしたい。」って考えて描いていけば、ただ場面を切り取ってそれを精密に写し取るよりも印象深い絵になると思うのです。
-[2]私がOS自作をしようと思ったとき(第一世代OSASKのとき)、私にはテーマがありました。私はi386/i486というCPUに感動したのです。
-まず32bitというレジスタ幅に感動しました。16bitでは64KBを超えるアクセスが素直には書けません。一方で、16bitカラーで640x480ピクセルを扱おうと思えば、600KBにアクセスする必要があって、386は夢のような仕様だったのです。
-そもそもメモリが64KBとか1MBしか使えないっていう8086は狭いなと思っていました。これが4GBになるわけです。これならやりたいことは全部できる!って思いました。
-新しくなったセグメンテーションもとても感激しました。メモリ領域をOSが移動したとしても、セグメントの設定も一緒に変更すれば、アプリケーションは一切影響を受けずに動き続けることができるわけです。
-実行速度は高速でした。特にi486はとんでもなく速いと感じました。
-私はこのCPUの機能を生かし切るためのOSを作ってやろう!と思ったのです。
-結果としてとても面白いOSができたと思っています。
-[3]最近は移植性が大事で、特定のハードウェアや特定のOSに依存しないほうがよいという風潮があるので「これを生かし切るプログラムを書いてやるぞ!」とはなりにくいかもしれません。もったいないですね。
-[4]acl4は何がテーマでしょうか。・・・まず、テーマより前に目的を書いておくと、それは私が私自身の開発力を底上げすることです。「このライブラリさえあれば、あれもこれもちょこっと書くだけでできる」という状態を実現することです。
-じゃあテーマは何かというと、C言語だけでもここまでできるということ示すことです。C言語には足りない部分があって、それゆえに後続の言語が生まれてきたわけですが、それはC言語に不得意な処理があるということです。そこを責めずに得意な部分をうまく使えば、C言語だってまだまだやれるんです。
** 2026.04.05(日) #0
-#typedDefの仕様が固まってきたので、そろそろ作れそうな気がしてきました。
#typedDef Lod(Int:,r, CInt:,i) Lod_RI(defRight(r,:), defRight(i,:))
#typedDef Lod(Int:,r, Int:,s) Lod_RR(defRight(r,:), defRight(s,:))
-rの中身が"Int:"で始まっていて、かつiの中身が"CInt:"で始まっている場合のみ、1つめのLod()はマッチします。その際に、rやiは"Int:R00"とか"CInt:0"みたいな形で展開されます。
-つまり「型=どんな文字列で始まっているか」という解釈にしました。
-展開先に型情報は不要で:よりも後ろだけを取り出したい場合は、defRight(r,;)とします。
** 2026.04.06(月) #0
-ポインタを多用する構造体を適当に作っていたら、バグを2個くらい作ってしまったけれど、a_malloc/a_freeのおかげで、あっという間に直すことができましたー。我ながら本当に使えるなー。たぶんデバッグ支援機能がなかったら、3時間くらいはかかった気がします。
-すごく役に立ったなーと思ったポイント:
--バグ1: 構造体の中にポインタを入れる場所があるのに、initはそこにポインタを入れ忘れて入れたつもりになっていました。deinitのときに、このポインタをfreeしようとして発覚。
--バグ2: a_malloc_debugList()によるとメモリリークがあるとわかったのだけど、いやちゃんと全部開放しているよ?なんで??って思いました。でもdebugListはプログラムの何行目のmallocで確保したメモリかも教えてくれるので、「あー、これのことかー。確かにこの条件の場合だとfreeし忘れてるー」ってすぐにわかりました。大変すばらしい!
** 2026.04.07(火) #0
-私は個別に効率の良いデータ構造を設計するのではなく、同じデータ構造を流用していくほうが賢いかもしれないと思い始めました。もちろん非効率にはなるのですが、しかしプログラムの多くの部分は共通化できて、単純化できる気がするのです。
-VecChr は可変長な文字列や可変長の配列に適していて、固定長に限定すればもっと効率よく作ることはできるのですが、固定長か可変長かで作り分けるのはちょっと面倒ですし、効率に差があるとはいってもそれほど大きくはないので、もう全部 VecChr でいいんじゃないの? みたいな話です。
-いい機会なので、両方の方針で作ってみて、違いを比べてみたいと思います。
** 2026.04.07(火) #1
-作り比べました。77行が70行になって、7行も少なくなりました。1割減です。
-今後も似たような開発はあると思うので、トータルではかなり減りそうな気がします。
** 2026.04.07(火) #2
-[Q] 行数が減ることはそんなに重要ですか?
-[A] 重要です、少なくとも私にとっては。行数が減ると、一気に見通しがよくなって、改良ポイントに気づけます。70行になった後にこれが発動して、64行まで減りました。
-この6行の削減は、77行の時の書き方でも適用可能なのですが、ややこしいせいで瞬時には思いつけなかったのです。
** 2026.04.09(木) #0
-typedDefの宣言だけはできるようになりました。
-あとは展開ができれば・・・。
-まあ展開ルーチンを書いてないので、本当に宣言が期待したメモリ構造になっているかは、定かではないです(いやもちろんある程度は確認しましたが)。
-deinit後にメモリリークもないので、きっと、たぶん、うまくいっているはずです・・・。
** 2026.04.10(金) #0
-typedDefがちゃんと動きましたー。展開部も書いてデバッグしたら期待通りに動くようになりました。
-プログラムはきれいになってないのですが、それをどうするかは後で考えます。
** 2026.04.10(金) #1
-現状をいったんまとめます。
-私はここ数週間、今までとは違ったやり方でコンパイラを作ろうとしています。
-どこが違うのかというと、「for (i=0; i<10; i++) { s+=i; }」みたいな記述があったときに、ミニコンパイラが「LET(i,0); LABEL(L1); ADD(s,i); ADD(i,1); JLE(i,10,L1);」に変換します。この時ミニコンパイラはiやsの変数型を解釈しません。何型であってもLETやADDとして出力します。
-でも実際のCPUは、整数命令なのか、浮動小数点数の命令なのか、ポインタなのか、などで命令が変わります。それはミニコンパイラの後の「型付きdefineのあるプリプロセッサ」によって、何度も置換されて、目的の機械語になります。上記のプログラム例では変数宣言命令がないので、各変数の型が不明ですが、変数宣言命令があればミニコンパイラはこの変数はこの型だよっていう文を出力します(ミニコンパイラはそれだけやって、あとの型の管理には手を出しません)。
-これのよいところは、デバッグ支援のために「設定した上限・下限を下回ったらエラーになるような整数型を作ろう」とか思ったときに、ミニコンパイラをほとんど修正する必要がないことです。そして後段用のdefine文を多少編集すればそれだけで対応完了にできるのです。
-「チェック機構を充実させて、不正な場所にアクセスすることを許さないポインタ」だって作れるでしょう。複素数型や行列型も作れるでしょう。
-従来のCコンパイラの作り方では、型の管理はコンパイラと蜜結合していて、新たな型の追加や演算の挙動の変更は全く容易ではありませんでした。・・・C++であれば operator キーワードを使って演算子の挙動を拡張できますが、それをCコンパイラでも実現できるようにした感じです。
-この開発は「こういう作り方にすることで、Cコンパイラを簡単に作れるようにする、改造しやすくする」ことが主眼です。「それならC++コンパイラを作ればいいじゃん」だとCコンパイラを作るよりもはるかに大変になってしまって、解決にはなりません。
** 2026.04.10(金) #2
-手元の実験では、「s=0; for (i=0;i<10;i=i+1) for (j=0;j<10;j=j+1) s=s+i*j;」がコンパイルできて実行もできて、2025っていう答えも出たのですが、同時にすごくたくさんのメモリリークも出てしまいました。
-何とかして直さないと・・・。
** 2026.04.10(金) #3
-やったー、メモリリークが直りましたー。
* こめんと欄
#comment