* kharcs #3 -(by [[K]], 2025.06.13) ** (0) これはなに? -(kharcsページは、kharc開発でわかったことを整理して説明するためのページ群です。) -今回説明したいこと: ~この順番でCコンパイラを作ったら早くできたよ!~ --[1]~[6]→[[a25_kharcs2]] --[7]プリプロセッサ、グラフィックライブラリ(8日目) ** (7) プリプロセッサ、グラフィックライブラリ(8日目:6/13) -昨日のエミュレータ&JITコンパイラで楽しくなってきたので、プリプロセッサとグラフィックライブラリをkccにマージしました。 -プリプロセッサは [[a25_buntan04]] の2025.05.07~2025.05.23にて開発していたものをほぼのそのまま使いました。 -グラフィックライブラリはeasy-Cを作った時に使ったものをacl3に合わせて少し修正したものを使いました(これをacl3gという名前にしました)。 -ここでいったん現状をまとめます。 |(1)acl3ライブラリ|RIGHT:607行|RIGHT:19.9KB|開発期間:2025.04.11~2025.06.13|自分自身の開発力上げていくためのライブラリ| |(1)acl3ライブラリ|RIGHT:607行|RIGHT:19.9KB|開発期間:2025.04.11~2025.06.13|自分自身の開発力を上げていくためのライブラリ| |(2)acl3gライブラリ|RIGHT:653行|RIGHT:16.6KB|開発期間:2025.06.13~2025.06.13|過去に作って使ってきたacl1を手直しして作ったものなので、開発期間は参考程度に| |(3)プリプロセッサ|RIGHT:378行|RIGHT:12.6KB|開発期間:2025.05.07~2025.05.23|C言語のソースコードテキストデータから、プリプロセッサ済みのテキストデータを出力する| |(4)コンパイラ本体|RIGHT:840行|RIGHT:27.7KB|開発期間:2025.06.04~2025.06.13|プリプロセッサ済みのテキストデータから、kharc用のアセンブラをテキストデータで出力する| |(5)内蔵用アセンブラ|RIGHT:87行|RIGHT:13.3KB|開発期間:2025.06.12~2025.06.13|kharc用のアセンブラで、テキストデータからバイナリデータを出力する| |(6)kharcエミュレータ|RIGHT:73行|CENTER:↑|開発期間:2025.06.12~2025.06.13|kharcのバイナリをエミュレータ実行する| |(7)kharcバイナリ→x86機械語のJITコンパイラ|RIGHT:66行|CENTER:↑|開発期間:2025.06.12~2025.06.13|kharcのバイナリをJITコンパイルして、x86の機械語にする| |(8)(main)|RIGHT:52行|RIGHT:2.1KB|開発期間:2025.06.04~2025.06.13|上記のプログラム群を呼び出している部分| |(4~8の集計)|RIGHT:1118行|RIGHT:43.1KB|開発期間:2025.06.04~2025.06.13|8日間で1118行ということなので、1日に140行(5.4KB)くらいしか書いてない。| -今回作った、「それっぽい見た目にするための」ヘッダファイル。汚い部分をすべてこれで隠しています(笑)。 -プリプロセッサが使えるようになったので作りました。 void startup() { main(); asm { End(); } } // APIs (syscall) #define openWin(xSiz, ySiz) Syc(0, xSiz, ySiz) #define wait(msec) Syc(1, msec) #define setMode(win, mod) Syc(2, win, mod) #define setPix0(win, x, y, c) Syc(3, win, x, y, c) #define fillRect0(win, sx, sy, x, y, c) Syc(4, win, sx, sy, x, y, c) #define fillRect(win, sx, sy, x, y, c) Syc(5, win, sx, sy, x, y, c) #define drawRect0(win, sx, sy, x, y, c) Syc(6, win, sx, sy, x, y, c) #define drawRect(win, sx, sy, x, y, c) Syc(7, win, sx, sy, x, y, c) #define drawLine(win, x0, y0, x1, y1, c) Syc(8, win, x0, y0, x1, y1, c) #define fillOval0(win, sx, sy, x, y, c) Syc(9, win, sx, sy, x, y, c) #define fillOval(win, sx, sy, x, y, c) Syc(10, win, sx, sy, x, y, c) #define drawOval(win, sx, sy, x, y, c) Syc(11, win, sx, sy, x, y, c) #define fillOvalCent(win, x, y, a, b, c) Syc(12, win, x, y, a, b, c) #define drawOvalCent(win, x, y, a, b, c) Syc(13, win, x, y, a, b, c) #define fill(win, x, y, c) Syc(14, win, x, y, c) #define Amul64Shr(a, b, c) Syc(15, a, b, c) #define AprintTime() Syc(16) #define Argb8(r, g, b) ((r)*65536+(g)*256+(b)) #define AWin_ModeSet 0 #define AWin_ModeOr 1 #define AWin_ModeAnd 2 #define AWin_ModeXor 3 void putchar(int c) { asm { LodRMd(R0,SP,$c); printf("%c", R0); } } ---- -まずは私にとって定番のこれを作りました(サンプル1)。 #include "kharc.h" int main() { int win, x, y, c; win = openWin(256, 256); c = 0; for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { setPix0(win, x, y, c); c = c + 0x100; } } wait(-1); } --https://essen.osask.jp/files/pic20250613a.jpg -こうやってグラフィックスが出せるようになると、なんかすごい感じがしてきます(笑)。 -これは上記のソースコードをテキストファイルに保存して、 prompt>kcc sample1.txt -とすればすぐに表示されます。kccさえあれば、gccなどのコンパイラがインストールされていなくても実行できるのです。中間ファイルも実行ファイルも生成されることはありません。全てメモリ内で処理しています。 -このプログラムのように最後がwait(-1)で終わっているものは、そのままではいくら待っても終了しないので、Ctrl-Cで終了させてください。 ---- -次はこれを作りました(サンプル2)。私がcharsと呼んでいるものです。 #include "kharc.h" int main() { int i; for (i = 0x20; i <= 0x7e; i++) { putchar(i); } putchar(0x0a); return 0; } -実行結果はこうなります。 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ---- -次はこれを作りました(サンプル3)。 #include "kharc.h" int main() { int win, x, y, c; win = openWin(512, 512); setMode(win, AWin_ModeXor); c = 0xffff00; for (x = 0; x < 512; x++) { drawLine(win, 0, 0, x, 511, c); drawLine(win, x, 0, 511, 511, c); } wait(-1); } --https://essen.osask.jp/files/pic20250613b.png ---- -次はこれを作りました(サンプル4)。 #include "kharc.h" int main() { int win, x, y, c; win = openWin(256, 256); for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { c = (x ^ y) * 0x010101; setPix0(win, x, y, c); } } wait(-1); } --https://essen.osask.jp/files/pic20250613c.png ---- -これはマンデルブロー集合の画像をかくためのプログラムです(サンプル5)。 -普通に実行すると数分の実行時間がかかるので、 prompt>kcc sample5.txt 2 -のようにファイル名のあとに「2」を付けます。これは実行モード指定で、0が普通のエミュレーション、1が改良して高速になったエミュレーション、2がJITコンパイルによる実行になります。デフォルトは1にしてあります。 #include "kharc.h" int main() { int w; w = openWin(512, 384); int x, y; for (y = 0; y < 384; y++) { for (x = 0; x < 512; x++) { int sn, sx, sy, n; sn = 0; int c, cx, cy, zx, zy, xx, yy; for (sx = 0; sx < 4; sx++) { cx = (x * 4 + sx) * 56 + 4673536; for (sy = 0; sy < 4; sy++) { cy = (y * 4 + sy) * (-56) - 124928; zx = cx; zy = cy; for (n = 1; n < 447; n++) { xx = Amul64Shr(zx, zx, 24); yy = Amul64Shr(zy, zy, 24); if (xx + yy > 0x4000000) break; zy = Amul64Shr(zy, zx, 23); zx = xx + cx - yy; zy = zy + cy; } sn = sn + n; } } n = sn / 16; c = Argb8(n, 0, 0); if (n >= 256) { c = Argb8(0, 0, 0); if (n < 447) { c = Argb8(255, n - 255, 0); } } setPix0(w, x, y, c); } } AprintTime(); wait(-1); } --https://essen.osask.jp/files/pic20250613d.jpg -多用している Amul64Shr(a, b, c) という関数は、aとbを掛け算して64bitの結果を生成し、それをcビットだけ右シフトして、その結果を32bitで返す関数です。aもbもcも計算結果もすべて32bit整数なのですが、途中で64bit演算が必要なので、こんな関数になっています。 ---- -Windows用のkcc.exeとそのソースコードです。 --https://essen.osask.jp/files/kcc00a.zip (50.5KB) -これは開発途上のものを適当にまとめたものです。実行しようとすると「プリプロセッサの出力結果」「コンパイラの出力したアセンブラ」が表示されたのちに実行されます。 -これはデバッグ中の表示が残ったもので、まあ「よくわからんが、ちゃんと動いている感じはするな」と眺めてニヤニヤするためのものです。 -JITコンパイラは32bit用に作ったので、kcc.exeを作る時は32bitで作らないとJITコンパイラは動きません。 -ビルドするときは、kcc.cだけを指定すればいいです。残りは全部includeされています。 -GDI32.DLLを使うので、「-lgdi32」を指定する必要があります。 -機能としては、ここに提示されているサンプルを動かす程度のことしかできません。必要に応じて演算子を追加しているので、未実装の演算子が結構残っています。だから多くを期待しないでください。 -エラーチェックもほとんどしてなくて「バグがないと分かっているプログラムだけを、どうにかこうにか動かす」ように作ってあります。ご了承ください。 ** 今後の予定 -ポインタと配列を使えるようにしたら、できることが増えそうなのでそれをやりたいです。 -構造体も使えるようにしたいです。 -浮動小数点演算もやりたいし、最適化もやりたいです。 -でもkharcばかりやっていると他の仕事があまりできないので、しばらくはお休みかなあ(それでためてしまった他の仕事を片付ける)。 -今回は SecHack365 の第1回のオンラインイベント(2025.06.14)までにどこまで作れるかということで集中的に頑張ったのです。 -今回作ってわかったことは、Cコンパラを作るのはそれほど大変ではなさそうだということです。8日間でここまでできるなら、必要に応じて作っていいかなと思いました。 -2013年とか2014年にOSECPU-VMを作っていたころは、Cコンパイラがないと嘆いていました。作ればよかったのですが、作れる気がしなかったのです。 -実はkccはパーサやトークナイザを使っていません。なくてもCコンパイラは作れるとわかりました。 -いろいろ開発しながら「これは将来も使いそうだな」と思った関数をacl3に追加していく開発スタイルはいいなと思いました。今回もそれで相当に開発時間を節約できている気がします。 -kharcはLLVMやLuaみたいなカテゴリで、超軽量型という立場を確立できるでしょうか。・・・いやでも、そんな大それた目的のために作り始めたわけじゃなくて、コンパイラがないと不便だなあと思っただけのことなのですが・・・。 -まあコンパイラをちゃんと作り切った時にどのくらいの規模になるかですよね。でもせいぜい今の2倍くらいじゃないかなあ。実行ファイル換算で80KBくらい?それなら悪くないんじゃないかなあ。しかもJITコンパイラをいろんなCPUに対応させるのは、たぶん簡単なはずです。