* buntan-pc #4
-(by [[K]], 2025.05.07)
** (0)
-https://github.com/buntan-pc/
-これにかかわる開発の話
** 2025.05.07 Wed #0
-今日はacl3.cを仕上げて、kuas.cをacl3.cに対応させたいです。
-ソースコードのサイズ比較(kuas04a→kuas05a):
|aclmini.c/acl3.c| 5125バイト → 9563バイト|
|kuas.c| 11106バイト → 9552バイト|
-ライブラリは結構いろいろ追加したので、増えたのはしょうがないかなー。
-kuas.cが1554バイト減ったのはうれしいです。それだけacl3.cはうまく開発をアシストできているということだと思います。
-このライブラリを使っただけで、アセンブラが10KB未満で書けるのなら、それは十分に楽しいと私は思うのです。
-https://essen.osask.jp/files/kuas05a.zip (6.17KB)
--ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。
--コンパイルの際は、 kuas.c のみコンパイルすればいいです。acl3.cは勝手にインクルードされます。
** 2025.05.07 Wed #1
-実はAX_printf()っていうのを作りたい。これがあれば事前に十分な大きさのバッファを用意するとかしなくてよくなるから、プログラムがすごく気楽に作れるはずだ。そのためにはAX_printfの中でvsnprintf()を使えばよさそうだけど、サイズが足りなかったらバッファサイズを2倍とかにして何回かやり直すのだろうか。うーん、それはちょっと遅そうだなあ。
-と思ったら、snprintf族は、バッファに収まらなかった場合でも「仮にバッファが十分にあったとしたら何文字出力したか」を返すらしい。それはいい!これならバッファを拡大しながら呼び直すなんていうループはいらない。2回目で必ず成功できる。
--参考: https://surf.st.seikei.ac.jp/~nakano/JMwww/html/LDP_man-pages/man3/printf.3.html
** 2025.05.07 Wed #2
-C言語のプリプロセッサを自作するのは大変だろうか・・・。もし簡単に作れるのなら作ってみたいかも・・・。
-プリプロセッサって何をやればいいんだろう?
#define
#undef
#if ~ #elif ~ #else ~ #endif
#include
#
##
-これくらいでいいのかな?これならなんか比較的簡単にできそうな気がしないでもない。
** 2025.05.08 Thu #0
-とりあえず、includeは問題なくできるようになった気がする。入れ子になっていても動いているし、循環参照になっても止まれるようにしてみた。
-次はdefineかな。
** 2025.05.09 Fri #0
-こんな実験をしました。WSL2上でUbuntu 24.04.2 LTSを使って、そこでgcc 13.3.0を使って試しました。
-以下のプログラムを作って、gccの-Sでアセンブラソースを出力させています。
#include <stdio.h>
static char msg[] = "not use this message";
static int func(int i)
{
puts(msg);
return i * i;
}
int main(int argc, const char **argv)
{
puts("hello, world");
return 0;
}
-要点は、mainからfuncは一切呼ばれていないし、msg[]を使っているのはfuncだけだということです。
|gcc -S -O0|msgとfuncもアセンブラソースに含まれる|
|gcc -S -O1|msgとfuncはアセンブラソースに含まれない|
|gcc -S -O2|msgとfuncはアセンブラソースに含まれない|
|gcc -S -O3|msgとfuncはアセンブラソースに含まれない|
-このように、-O1以上では、使わない関数やグローバル変数があっても、それが実行ファイルに反映されることはなく、無視されます(static属性がない場合は、リンク時にほかのオブジェクトファイルから参照される可能性があるので、勝手には消さなくなります)。
--今回は明示的には確認していませんが、ほかのコンパイラでも同様の挙動を示します。
-昔からライブラリはクラスとプロトタイプ宣言のみのヘッダファイルに書き、関数の実体は別のソースコードに書いて分割コンパイルして、リンク時に必要なものだけ(参照されるものだけ)リンクされます。
-でもこの方法だと、コンパイラによる高度な最適化は適用されません。別々にコンパイルして、リンクするかしないかしか選べません。
-一方で、ヘッダファイルに関数の実体も含めて全部書いてしまえば、コンパイル時間はかなり長くなるものの、コンパイラは大局的な最適ができるようになり、必要に応じて関数のインライン展開などもするようになります。そしてstatic属性さえつけておけば、使わない関数は出力されないので、いくら書いても問題ありません。
-近年ではコンピュータの性能が上がってきて、小さなライブラリであれば分割しない方法でも十分に現実的な速度でコンパイルできるようになってきました。だから最近の[[K]]はヘッダファイルではなく関数本体をincludeするタイプのライブラリばかり作っています。
-でも一つだけ問題があります。gccとかで警告を多めにすると、未使用関数(記述したのに使わなかった関数)に対して、いちいち警告を出すのです。これがめんどくさいのです。
-まあ普通は関数を書いたら使われる方が当たり前で、そうなってないということは「あれ?何か書き忘れがあるんじゃないですか?」ってことで教えてくれるのは親切なのですが、私のような使い方だと「おせっかい」で「ありがた迷惑」なのです。
-gccの場合「-Wno-unused-function」とか「-Wno-unused-variable」を指定すれば、この手の警告だけ抑制できるので、私はいつも指定しています。
** 2025.05.10 Sat #0
-buntan-pcでは、平凡な仕様よりもちょっと変な仕様の方が望ましいので、#defineについてもちょっとだけ冒険することにします。
--[1]#defineマクロは、引数の有無、引数の数で区別されます。
--[2]#ifdefやdefined(...)は引数有り無し含めてとにかくどれか一つでも定義されていれば真とします。
--[3]既に定義済みのマクロに対してさらに#defineすることを許します。その場合、新しい定義が有効になります。そして#undefすると元に戻ります。
--[4]#undefでカッコのつかないマクロ名を指定した場合、まずはカッコのつかないマクロのundefを試みますが、もし該当するものが見つからなければ、同名の引数あり版を探してundefします。
** 2025.05.12 Mon #0
-すごく適当に#defineを作ってみたら、メモリ効率が良くなかったので、もうちょっとちゃんと作ることにします。
** 2025.05.17 Sat #0
-単純に作れるか作れないかで言えば作れるのですが、しかし「なんでこの程度の処理がすっきり書けないのか?」と思うと、要するにライブラリの不備のせいだということになり、じゃあライブラリをどうすればいいのかというと、考え込んでしまいます。まあでもそれが楽しいんです。がんばります。
** 2025.05.19 Mon #0
-defineとundefもできるようになりました。あとは#if類だけです。
--acl3.c : 498行(14.9KB)
--kcpp0.c : 277行(6.8KB)
-もちろん、 ## や # も使えます。
#define Cat_Helper(x, y) x##y
#define Cat(x, y) Cat_Helper(x, y)
#define PutsNum(i) puts(#i)
Cat(Puts, Num)(123); → puts("123");になる
-私はこの成果にとても驚いています。まだincludeとdefine類だけではありますが、結構プリプロセッサらしく動いています。
-2000年くらいにOSASKを作っていたころ、私はASKAでプリプロセッサを使いたくて、gccのcpp0を利用していました。あの時はプリプロセッサは「きっと作るのが大変だろう」という存在でした。こんな簡単に作れると知っていたら、きっと自作したでしょう。
-今回こんなに小さく作れたのは自作ライブラリであるacl3.cがあるからです。自分の開発力が底上げされているなととても実感します。
--可変長メモリ構造を楽に扱える関数とか、メモリリークをすぐに確認できる機能をとてもよく使っています。
** 2025.05.19 Mon #1
-調べてみてびっくり、いつの間にか#elifdefと#elifndefというものが追加されたらしいです。
--https://yohhoy.hatenadiary.jp/entry/20210604/p1
-とりあえず、内部処理としては、
--#ifdef ... → #if defined(...)
--#ifndef ... → #if !defined(...)
--#elifdef ... → #elif defined(...)
--#elifndef ... → #elif !defined(...)
-と読み替えるだけにしたので、コスト的には7行の追加で済みました。
-これであとは#if、#else、#elif、#endifを何とかすれば完成ということになります。
** 2025.05.20 Tue #0
-#if類も実装しました。今は!以外の演算はできないのでそこがダメですが、しかし#ifの中で計算をしない範囲でなら完璧に使えます。
--acl3.c : 498行(14.9KB)
--kcpp0.c : 323行(9.0KB)
** 2025.05.21 Wed #0
-#ifの中の演算にも対応しました。機能的には問題なさそうです!
--acl3.c : 498行(14.9KB)
--kcpp0.c : 394行(12.6KB)
-でもまだメモリの使い方が一部気に入らないので、明日はそれを直したいです。
** 2025.05.23 Fri #0
-メモリの使い方を直して整理しました。私は満足です。
--acl3.c : 498行(14.9KB)
--kcpp0.c : 371行(12.2KB)
** 2025.05.24 Sat #0
-ここまでを振り返ってみると、kuasを開発して、kcpp0を作って、その際に「もし〇〇な機能がacl3ライブラリにあれば、開発が楽になりそうなんだけどな」を考えてライブラリを強化してきました。
-私はもっとライブラリを強化したいです。そのためには何かを作らないといけません。次には何を作るといいのでしょうか。Cコンパイラ?
-今回の開発でライブラリ開発のコツをつかみました。
--1.大きな機能のライブラリは作らない。小さなライブラリを作る。一歩だけ進めばいいのであって、いきなり三歩も四歩も進めるようなものはダメ。たくさん進みたいのなら、一歩だけ進むライブラリ関数をいくつか作って、それを組み合わせてトータルで三歩でも四歩でも進めばいい。小分けにしておく方が応用がききやすい。
--2.何か具体的な開発があって、そのためにライブラリも作る。「こういうのがあったらいいだろう」みたいな発想はうまくいかない。実際の利用ケースをよく考えずに作ってもいいものにはならず、結局使わないことになる。
--3.最適化はしない。速度が遅くても動けばいいくらいで考える。
--4.記述が重複し始めたら、それは重複部分を別の関数で置き換えるべき時期だろう。
** 2025.05.24 Sat #1
-生のC言語は複雑すぎる気がするから、シンプルなC言語を考えて、
--1.普通のC言語からシンプルCへトランスパイル
--2.シンプルCからアセンブラもしくは機械語へコンパイル
-の2段階に分離したほうがよさそう。シンプルCには機種依存な仕様を持たせない(Cから引き継いだインラインアセンブラとかはあってもいいけど)。
-この形式にしておけば、2を作るだけで任意のCPUアーキテクチャに対応できるようになるというメリットがある。
-あとは「なんとなく1週間くらいでできそう」って思えたら開発開始かなあ。
** 2025.05.30 Fri #0
-急に思いついた、私にとっての理想のCPUアーキテクチャ。
--レジスタがいっぱいある。まあ16本くらいあればいいかなという気はするので、x64を使えばいいかな。
--アドレスを入れるためのレジスタは固定したい。たぶん6個くらいがちょうどよい。RSP,RBP,RSI,RDI,R14,R15くらいでいいんじゃないかな。残りはメモリアドレスを入れてはいけない。
--これらのアドレスレジスタに対して、リミットチェックのための補助レジスタがあるとよい。そうすればアクセス違反を検出できる。
---これってさ、x86のセグメンテーションでいいんじゃないの?という気はする。セグメンテーションにはリミットチェックがある。そうなんだよ、x86は実は結構理想の仕様なんだよなあ。(OSASK以上に)セグメンテーションを使いまくったOSを作ってみたら面白いかもしれないぞ!・・・これは何をやりたいかというと、タスク内でメモリオブジェクトがまとまってなくて、いろんなタスクのいろんなオブジェクトが単一のメモリ空間にまぜこぜになっている。そうなっていても、セグメンテーションがあるから干渉しないで済む。・・・そんなことできるのかな?ほんとに。
---x64になってセグメンテーションがなくなったわけだけど、要するに世間的にはリミットチェックなんかいらないってことなんだよね。デバッグモードではリミットチェックしたいけど、リリースモードではリミットチェックいらない。チェックするだけ比較器のムダ。・・・まあそうなんだよなー。
--メモリアドレッシングは、[ベース+インデックス*定数+disp]でいい。x64との違いは、定数に任意の整数を指定できるところ。
-つまり私はVMが欲しいんだな。・・・うん、確かに欲しい。
-そしてそのVM用にアセンブラを作って、そのVM用にCコンパイラを作って、そのVM上で動くOSを書いて、ほらこんなに便利なんだぜっていうのをやりたい。
** 2025.05.30 Fri #1
-さらに考えて、実はVMが欲しいわけじゃないことが分かった。改造しやすいCコンパイラが欲しい。
-さらに考えて、実はVMが欲しいわけじゃないことが分かった。改造しやすいCコンパイラが欲しい。C言語インタプリタでも可。それでデバッグ情報を満載にして、超充実のprintfデバッグがしたい(笑)。
-世間ではIDEとかデバッガを使ってデバッグするけど、私はprintfデバッグが最強だと思う。だって複雑な条件とか設定し放題だよ?
// seg(seg,vsg)やofs(lb, vpc)という構文は作れる.
// データのオフセットは共通になる。16bitアクセスがメインなら、データもuint16_tで書かれるべき.