a4_log
の編集
https://essen.osask.jp/?a4_log
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
BracketName
EssenRev4
FormattingRules
FrontPage
Help
InterWiki
InterWikiName
InterWikiSandBox
K
MenuBar
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SDL2_01
SandBox
WikiEngines
WikiName
WikiWikiWeb
YukiWiki
a21
a21_acl01
a21_bbs01
a21_challengers
a21_count
a21_edu01
a21_edu02
a21_edu03
a21_edu04
a21_edu05
a21_edu06
a21_edu07
a21_edu08
a21_edu09
a21_edu10
a21_edu11
a21_hlx000
a21_hlx001
a21_hlx001_1
a21_hlx001_2
a21_hlx001_3
a21_hlx002
a21_hlx002_1
a21_hlx003
a21_hlx003_1
a21_hlx004_1
a21_memo01
a21_opt
a21_opt02
a21_opt03
a21_p01
a21_special
a21_tl9a
a21_todo
a21_txt01
a21_txt01_10
a21_txt01_1a
a21_txt01_2
a21_txt01_2a
a21_txt01_2b
a21_txt01_3
a21_txt01_4
a21_txt01_5
a21_txt01_6
a21_txt01_6a
a21_txt01_7
a21_txt01_8
a21_txt01_8a
a21_txt01_9
a21_txt01_9a
a21_txt02
a21_txt02_10
a21_txt02_10a
a21_txt02_10b
a21_txt02_11
a21_txt02_11a
a21_txt02_12
a21_txt02_12a
a21_txt02_12b
a21_txt02_1a
a21_txt02_1b
a21_txt02_2
a21_txt02_2a
a21_txt02_3
a21_txt02_3a
a21_txt02_4
a21_txt02_4a
a21_txt02_5
a21_txt02_5a
a21_txt02_6
a21_txt02_6a
a21_txt02_6b
a21_txt02_6b_rev0
a21_txt02_6x
a21_txt02_7
a21_txt02_7a
a21_txt02_8
a21_txt02_8a
a21_txt02_9
a21_txt02_9a
a22_acl2_01
a22_acl2_02
a22_edu12
a22_intro01
a22_intro02
a22_intro03
a22_memman01
a22_memman02
a22_memman03
a22_memman04
a22_memman05
a22_memman06
a22_memman07
a22_memo01
a22_mingw_debug
a22_txt03
a22_txt03_1a
a22_txt03_1b
a22_txt03_2
a22_txt03_2a
a22_ufcs01
a23_bbs
a23_ec001
a23_ec002
a23_intro00
a23_intro000
a23_intro01
a23_intro02
a23_intro03
a23_intro04
a23_intro05
a23_intro06
a23_intro07
a23_intro08
a23_intro09
a23_intro10
a23_intro10wk1
a23_intro10wk2
a23_intro10wk3
a23_intro11
a23_intro12
a23_intro13
a23_intro13wk1
a23_intro14
a23_intro15
a23_intro16
a23_intro17
a23_intro17wk1
a23_intro18
a23_intro19
a23_intro90
a23_intro91
a23_neopixel1
a23_os01
a23_useSelfMade
a23_usm001
a23_usm002
a23_usm003
a23_usm004
a23_usm005
a23_usm006
a23_usm007
a23_usm008
a23_usm009
a24_AMap11
a24_AMapSim11
a24_AMemFile
a24_AMemMan
a24_aErrExit
a24_aFnv
a24_aOsFunc
a24_aQSort
a24_aXorShift32
a24_acl1T_doc01
a24_acl1Tiny
a24_acpp0
a24_buntan01
a24_cMin
a24_getTyp
a24_goodvalues
a24_idea001
a24_longdef
a24_memo01
a24_memo02
a24_osc20240310
a24_osc20241026
a24_picoLcd13
a24_picoTrain1
a24_programs
a24_raspberrypi01
a24_raspberrypi02
a24_schedule
a24_spc2tab
a24_tab2spc
a24_useSelfMade
a25_acl3
a25_acl4
a25_acl4_001
a25_acl4_002
a25_acl4_003
a25_acl4_log01
a25_acl4_log02
a25_acl4_log03
a25_buntan02
a25_buntan03
a25_buntan04
a25_buntan05
a25_kcas01
a25_kharc01
a25_kharc02
a25_kharc03
a25_kharc04
a25_kharc05
a25_kharc06
a25_kharcs1
a25_kharcs2
a25_kharcs3
a25_kharcs4
a25_kharcs5
a25_kharcs6
a25_kharcs7
a25_kharcs8
a25_kharcs9
a26_txt03
a26_txt03_001
a26_txt03_002
a4_0001
a4_0002
a4_0003
a4_0004
a4_0005
a4_0006
a4_0007
a4_0008
a4_0009
a4_0010
a4_0011
a4_0012
a4_0013
a4_0014
a4_0016
a4_0017
a4_0018
a4_0019
a4_0020
a4_0021
a4_0022
a4_0023
a4_comments
a4_d0001
a4_d0002
a4_d0003
a4_d0004
a4_d0005
a4_d0006
a4_i01
a4_links
a4_log
a4_log00
a4_log04
a4_log05
a4_log06
a4_log07
a4_log08
a4_log09
a4_log10
a4_log11
a4_p0001
a4_p0002
a4_p0003
a4_p0004
a4_p0005
a4_p0006
a4_p0007
a4_p0008
a4_t0002
a4_t0003
aclib00
aclib01
aclib02
aclib03
aclib04
aclib05
aclib06
aclib07
aclib08
aclib09
aclib10
aclib11
aclib12
aclib13
aclib14
aclib15
aclib16
aclib17
aclib18
aclib19
aclib20
aclib21
aclib22
aclib23
aclib24
aclib25
aclib_bbs
arm64_01
avm0001
edu0001
edu0002
edu0003
esb02b_hrb
esb_dbg
esbasic0001
esbasic0002
esbasic0003
esbasic0004
esbasic0005
esbasic0006
esbasic0007
esbasic0008
esbasic0009
esbasic0010
esbasic0011
esbasic0012
esbasic0013
esbasic0014
esbasic0015
esbasic0016
esbasic0017
esbasic02a
esc0001
escm0001
essen_hist
esvm0001
esvm0002
esvm0003
esvm0004
esvm0005
esvm0006
esvm_i0
hh4a
idea0001
idea0002
idea0003
impressions
jck_0000
jck_0001
kawai
kbcl0_0000
kbcl0_0001
kbcl0_0002
kbcl0_0003
kbcl0_0004
kbcl0_0005
kbcl0_0006
kbcl0_0007
kclib1_0000
kclib1_0001
kclib1_0002
kclib1_0003
kclib1_0004
kclib1_0005
kclib1_0006
kclib1_0007
kclib1_0008
kclib1_0009
kclib1_0010
kpap0001
members
memo0001
osask4g
osask4g_r2
p20200311a
p20200610a
p20200610b
p20200624a
p20200711a
p20200716a
p20250813a
p20250813b
p20250813c
p20250815a
p20250903a
p20251006a
page0001
page0002
page0003
page0004
page0005
page0006
page0007
page0008
page0009
page0010
page0011
page0012
page0013
page0014
page0015
page0016
page0017
page0018
page0019
page0020
page0021
page0022
page0023
populars
seccamp
seccamp2019
sechack
sechack2019
seclang01
sh3_2020
sh3_2020_kw
sh3_2020_nk
sh3_2021_kw
sh3_2021_nk
sh3_2022_kw
sh3_2023_kw
sh3_2024_kw
sh3_2025_kw
sh3_2026_kw
sh3_kw_hist
termux001
termux002
text0001
text0001a
text0002
text0002a
text0003
text0004
text0005
text0006
text0006a
text0007
text0008
text0010
text0011
text0012
text0013
text0014
text0015
text0016
text0017
text0018
text0019
text0020
text0021
tl1c
tl2c
tl3c
tl3d
* acl4の開発ログ -(by [[K]], 2026.03.14) -''acl4開発のもくじ → [[a4_i01]]'' -開発ログの過去ログのもくじ → [[a4_log00]] -最近の過去ログ: //-[[a4_log08]](2026.03.14(土)#0~2026.03.17(火)#2): プリプロセッサ的な置換ルールだけで、コンパイラの大半の処理をやってしまえば、コンパイラはすごく作りやすくなるかもしれないという構想を思いつく。→ミニコンパイラ構想。および試作。 -[[a4_log09]](2026.03.18(水)#0~2026.03.26(木)#5): 有理数型を作ろうと思ったけど、途中から浮動小数点数から近い有理数を探すアルゴリズムに注目。 -[[a4_log10]](2026.03.27(金)#0~2026.04.10(金)#3): プリプロセッサの連鎖でコンパイラを構成するやり方を引き続き試作。 -[[a4_log11]](2026.04.11(土)#0~2026.04.18(土)#0): acl4 → acl4v1 の移行。 ** 2026.04.20(月) #0 -mallocでメモリを確保したり、reallocでメモリを拡張したりすると、なんか適当な値が雑に書かれたメモリが手に入ります。そのことそのものに問題はないのですが、しかしデバッグするときにこれを見て「初期化をしてないからこの値なのか」「初期化をしたからこの値なのか」よくわからない気分になります。だから自作mallocと自作reallocに手を加えてデバッグモードの時はメモリを適当な値で塗りつぶして渡すという仕組みを作ることにしました。 -とりあえず簡単のためにmemset()で塗りつぶすことにして、ではどんな値で塗りつぶすかということを考えました。 --0x00: これで塗りつぶすと明らかに「何も書いてない」感じになります。それはとても良いですが、そもそもプログラムのほうで初期化する際も 0x00 で塗りつぶすことが多く、つまりこれだと初期化を忘れてもかえって気づきにくい、初期化し忘れているのにデバッグモードだと正常に動いちゃう、という問題があります(リリースモードでは動かない)。0x00にしたいのならプログラムが自分でやるべきです。mallocやreallocを当てにされては困ります。 --0xff: 0x00が一番きれいなのだとしたら、じゃあ一番汚いのは何か?それは0xffだよね、と考えました。数値的にも255は0から一番遠い値で、未初期化だと最も気づきやすそうです。・・・しかし、非常によく使われる符号付き整数で考えれば、0xffは8bitの-1でもあります。0xffffは16bitの-1、0xffffffffは32bitの-1です。つまり未初期化で値を取り出すと-1が読めてしまうわけです。-1なんて0にかなり近い数値です。初期化を忘れていても、「なんかちょっとだけおかしいかもしれないけど、まあでもこんなものかな?」で見過ごされる可能性があります。だからもっと明らかにおかしい値にするべきです。 --0x80: では符号付きの整数で、0x00から最も遠い値にしたらどうかと考えました。8bitの0x80は-128、16bitの0x8080は-32640、32bitの0x80808080は-2139062144です。これはちょっと普通では出てこない数値でしょう。しかし、だからこそ、invalidな値として初期値で使われる場合もあります(私自身もやったことがあります)。・・・それだと初期化忘れを見逃す原因にもなりうるのです。 --0x87: それでは、この1バイトの中の「0」のビットと「1」のビットを同数にすることにして、それで0x00から最も遠い値というのを考えてみました。「0のビットと1のビットを同数にする」というのは、メモリチェックパターンの0x55aaとかでも使われている考え方です。今回はメモリチェックとは全く関係ない文脈ですが、オールゼロやオールワンが真っ先にダメだというのなら、いっそのこと半々にしてしまえ、という安易な発想です。・・・0x87や0x8787などを初期値として採用するケースはまず考えられないですし、異常値っぽさは十分にあります。ということで今回はこれで行くことにします。 ** 2026.04.20(月) #1 -acl4v1でもdefine機能が使えるようになりました。でもまだtypedDefがうまく動いていません。 ** 2026.04.20(月) #2 -なんか最近このwikiが不安定な気がします。よしxreaからcoreserverに移行しよう! ** 2026.04.21(火) #0 -ほかのドメインで練習中です。 ** 2026.04.21(火) #1 -移行できたと思います。無事に安定して表示できるといいなあ。 ** 2026.04.21(火) #2 -さっきバグって、ポインタが変な値になっていました。当然、セグメンテーションフォルトで落ちたわけですが、なんでだ?と思ってポインタ値をprintfしてみたら、なんと 0x87878787 ! -・・・うお、いきなりこれかー(笑)。つまり初期化してないポインタがあって、そこを読んだわけですね。なんてわかりやすい! -ということで30秒くらい、くくく・・・と笑ってしまいました。 -[註] 2026.04.20(月)#0 で自分が仕掛けたバグ発見用のトラップに、さっそくはまって原因が分かった、という事例です(笑)。 -いや、これに限らず、私は毎日何度も自分の仕込んだデバッグトラップを踏んでいます。 -まあなんとも情けないわけですが、でもそのうちの多くはデバッグトラップがなくてもすぐに直せただろうとは思います。 -ただなんというか、私のデバッグトラップたちが優秀すぎて(?)、私が頑張る前に先にバグを見つけてしまうんです。 -ありがたい!! acl4 は私に合っている!! ** 2026.04.22(水) #0 -a4_0001~a4_0014のリファクタリングが完了しました。次はミニコンパイラをacl4v1用に直します。 ** 2026.04.24(金) #0 -やっと、何を作ったらプリプロセッサの良さが伝わるかが分かったので、頑張って作ります。たぶん明日か明後日にはできます! ** 2026.04.25(土) #0 -できました! --[[a4_p0007]]: プリプロセッサ処理をする関数を作ったら、いろんな言語が簡単に作れるかもしれない可能性が出てきました! -[[a4_i01]]のページを改装中です。 ** 2026.04.26(日) #0 -[[a4_0016]]~[[a4_0023]]と[[a4_t0002]]のページを作りました。 -これで acl4v1 は旧 acl4 に追いつきました。 ** 2026.04.27(月) #0 -[[a4_p0007]]のページにダウンロード用のzipファイルを追加しました。これで、35.0KBってホントにできるの?って言われずに済みます。 -x86-32でコンパイルしないと、35.0KBにはなりません。あと /Da_DbgLv=0 をつけて、リリースモードにする必要もあります。 -さて、次にやるべきは配列変数のサポートです。配列変数が使えるようになれば、kcubeが動かせるようになるはずです。 -とにかくこの「プリプロセッサで置換する方式」でどこまで行けるのか確かめたいです。 ** 2026.04.28(火) #0 -[[a4_t0002]] は Windows 専用になっていますが、かつて X11 でのグラフィックのやり方を勉強したので、それを思い出せば、 Linux 版も作れそうだとは思っています。 --[[a24_raspberrypi01]], [[a24_raspberrypi02]] ** 2026.04.30(木) #0 -考えがうまくまとまらないので、とりあえずシンプルにやります。 -たぶん設計がまとまらないのは、それを設計するだけの知識が揃っていないのだと思います。そんなときに無理しても駄作になって結局作り直すことになるので、背伸びはせず、単純明快にできるところまでをやります。 ** 2026.05.01(金) #0 -A4vm_eval0()のEnter命令の仕様: -Ent_III(i, j, k) --i: 整数レジスタ数, j: 浮動小数点数レジスタ数, k: 関数呼び出しのための予約メモリ -今回追加する引数: -Ent_IIIII(i, j, k, l, m) --l: ポインタレジスタ数, m: スタック変数のための領域サイズ ** 2026.05.01(金) #1 -そうかー! 構造体をどうしたらいいかなって考えるよりも先に、プリプロセッサの力でJITコンパイラが作れるのか試すべきです。 -もしうまくできたら、「これでもう将来どんなCPUが主流になっても、簡単に移植できるよー」って言えるようになるはずです。 ** 2026.05.01(金) #2 -これが動くようになりました! >p0008a "src:inline: DB(0x68); DD(\"hello\\n\"); DB(0xe8); DD_R(printf - 4); DB(0x58); DB(0xc3);" -PUSH(imm32)の機械語を 6A だと誤解して、それに気づかなくて、デバッグに30分を要してしまいました。 ** 2026.05.01(金) #3 -こう書けるようになりました。これで機械語を間違えるバグはなくなります。 >p0008a "src:inline: PUSH_I(\"hello\\n\"); CALL_I(printf); POP_R(EAX); RET();" -これが機械語になってすぐに動くので、「JITアセンブラ」と言えるかもしれません。 -ちなみに p0008a.c は50行のプログラムです。50行でJITアセンブラが作れるので楽しいです。 ** 2026.05.01(金) #4 -まだ printf しかできないですが、とにかくJITコンパイラはできました。ちゃんとx86の機械語に変換されて動いています。 -[追記] その後、for文もできました。簡単すぎて楽しいです。 -[追記] さらに1時間くらいやったら、 p0007h.txt が動くくらいになりました!( mandel )。やっぱりJITコンパイラは実行が速いです。 -やっぱりプリプロセッサは偉大だなあー。 ** 2026.05.03(日) #0 -今、 p0008a.c っていうJITコンパイラを作っているのですが、その中身は Cコンパイラ+アセンブラ になっています。 -そのアセンブラ部分は、 #define RET() DB(0xC3) みたいなのを50行くらい書いただけでしかないのですが、命令によってはこのDB命令は連続するので DB(a, b); みたいな形式もサポートしたいなって思いました。 -でもなあ、複数の引数をサポートするのは面倒だよなあ、まあだから現状のままでいいかー、2回書けばいいだけだし・・・とか思って妥協していました。 -でもさっき、思いつきました。あれ?もしかして、 #define DB(a, b) DB(a); DB(b) っていう1行を追加しておけばいいだけなんじゃないの? -その通りでした。こうして DB() 命令は複数の引数に対応できたのでした。 -こんなに楽していいのかな?(笑) -上記で、平気なふりして「#define を50行くらい書くだけで、アセンブラができた」みたいなことを書いていますが、これも結構とんでもなくて、今まで何度かアセンブラを作ってきた私としては「今までの苦労は一体なんだったのか・・・」という気持ちになっています。 -まあ進歩ってそういうものですよね。 ** 2026.05.04(月) #0 -この欄の 2026.04.04.11(土)#1 に、プリプロセッサだと、a,b,cの定義の順序によらず値が正しく求められるという話を書きました。 #define c a+b #define b 2 #define a 1 printf("%d\n", c); -これに対して、printf()が最後にある必要はあるけど、それ以外の順序はどうやっても同じ結果になるという話です。・・・それの続きです。 -今回プリプロセッサを改良したので、以下のような書き方でも同じ結果になります。 printf("%d\n", c); #define c a+b #define b 2 #define a 1 -つまり私の自作プリプロセッサなら define は後出しじゃんけんしてもいいのです。 ** 2026.05.04(月) #1 -[[a4_t0003]] を書きました。現行のミニコンパイラを補助ライブラリとしてまとめて、何度もコピー&ペーストしないでもいいようにしました。 -ちょっと実験したのでメモしておきます。 8.0KB : printf("hello, world\n"); のみの.exeファイル 23.5KB : p0008a.c で pp のみ(Mode=0) 28.0KB : p0008a.c で pp+cc (Mode=1) 30.0KB : p0008a.c で pp+cc+cjit (Mode=2) 35.5KB : p0008a.c で pp+cc+cjit+clang (Mode=3) 37.5KB : p0008a.c で pp+cc+cjit+clang+calc+graphic (Mode=4) -pp(プリプロセッサ)は実質的には 15.5KB 。 -cc(Cコンパイラ)は pp+4.5KB (でも実装している機能が少ないからここは不正確かな)。 -JITコンパイラは +2.0KB 程度。 -a4vm を入れると +5.5KB 程度。 -[[a4_t0002]]程度のグラフィック機能であれば +2.0KB 程度。 ** 2026.05.04(月) #2 -[[a4_p0008]] を書きました。JITコンパイラを試せます。 ** 2026.05.06(水) #0 -「新しい言語の作り方」という12ページのスライド資料をなんとなく作りました。 --https://essen.osask.jp/files/pr20260506a_acl4.pdf --今のところ発表予定はありません。 --[追記] 資料を更新しました → https://essen.osask.jp/files/pr20260505b_acl4.pdf ** 2026.05.10(日) #0 -運よくプレゼン機会があったので、上記の「新しい言語の作り方」でLTをしてきました(昨日)。 -好評でたくさんコメントをもらえたので、それを加筆したバージョンを作ろうと思います。 -ちなみにここで発表してきました。 --「第51回 自作OSもくもく発表作業会」 https://osdev-jp.connpass.com/event/379064/ --(ここの発表枠5で) ** 2026.05.13(水) #0 -ここまでの開発を振り返っています。 -[1] 第1層([[a4_0016]]) --デバッグに便利な機能が付いた malloc/realloc/free を整備。 -[2] 第2層([[a4_0017]]~[[a4_0019]]) --第1層の成果を使って、vector<char> を参考にして作った VecChr クラス(動的可変長配列)、簡単な Key-Value Store を提供する Set0 クラスを整備。 --さらにトークンを切り出す Token0/Token1 クラス、かっこでくくられた構造を切り分ける parseArgs などを整備。 -[3] 第3層([[a4_0020]]) --第2層の成果を使って、式の評価クラスを整備。文字列で式を与えれば、計算して答えを返します。 -[4] 第4層([[a4_0021]]~[[a4_0022]]) --第3層までの成果を使って、プリプロセッサ関数を整備。 -[5] 第5層([[a4_0023]]~) --第4層で作ったプリプロセッサ関数を使って、アセンブラやC言語を試作。 --JITコンパイラも難なく成功。ついでにa4vmという仮想マシンも整備。 --概要 → https://essen.osask.jp/files/pr20260505b_acl4.pdf --実装 → [[a4_p0008]] -ということで、まずAを作り、そのAを利用してBを作り、そのBを利用してCを作り・・・というのを繰り返して、どんどん自分の開発力をアップさせようというたくらみは、今のところ順調に進んでいます! ** 2026.05.16(土) #0 -何となく自分が納得できる程度の memmem() ができたのでここに書きます。 -まず memmem() が何かというと strstr() の仲間みたいなものです。ゼロ終端文字列ではなくて、メモリブロックの中からメモリブロックを探すのです。 --https://manpages.ubuntu.com/manpages/focal/ja/man3/memmem.3.html -GNU拡張なので、標準の <string.h> には入っていません。私はとっても便利な関数だと思っていますが。 -普通に作っても面白くないので、ちょっとだけ工夫をして高速化してあります。 -説明用のバージョン: a_static char *a_memmem0(const char *t, intptr_t tn, const char *p, intptr_t pn) { if (pn <= 0) return (char *) t; if (tn < pn) return NULL; intptr_t i = pn - 1; const char *t1 = t + tn - pn, c0 = p[0]; for (t--;;) { nxt: t = memchr(t + 1, c0, t1 - t); if (t == NULL) return NULL; if (t[i] == p[i]) { for (i = 1; i < pn; i++) { if (t[i] != p[i]) goto nxt; } return (char *) t; } } } --まず memchr() で0バイト目が一致している場所を見つけてきます。無事に見つかったら、i番目についても比較します。それも一致したら、全部比較します。 --全部比較するときに memcmp() を使えばいいし、比較だけならたぶんそのほうが速いのですが、不一致だった場合に何バイト目で食い違ったのかを知りたいので、自前ループしています。 --普通、この手のストリング検索では、 Boyer–Moore の有名なアルゴリズムを使って高速化します。しかしそれは事前解析処理が面倒だったので今回はやらないで、不一致の場合に速く気付くことを目指してこのアルゴリズムを思いつきました。まあシンプルなので、たぶん車輪の再発明だろうと思います(笑)。 --最初に t--; して、次に t+1 から memchr() するのは一見すると無駄なのですが、不一致になって戻ってきたときに t+1 してくれるのは便利なのです。だから最初に t--; してでも、この構造にしたかったのです。 -でもこのバージョンをそのまま使うと、 memchr() があまり速くありません。こういうストリング系の関数は自前実装しても性能がよくなることはほとんどないと言われていますが、私はそんなうわさ程度じゃあきらめません。そもそもここに書くことになったということは、自前実装でも十分に速くなったから書いているのです(笑)。 -ちゃんと高速化したバージョン: a_static char *a_memmem(const char *t, intptr_t tn, const char *p, intptr_t pn) { if (pn <= 0) return (char *) t; if (tn < pn) return NULL; intptr_t i = pn - 1; const char *t1 = t + tn - pn, *t16 = t1 - 16, c0 = p[0]; for (t--;;) { nxt: t++; for (;;) { if (t <= t16) { if (t[0] == c0) goto t0; if (t[1] == c0) goto t1; if (t[2] == c0) goto t2; if (t[3] == c0) goto t3; if (t[4] == c0) goto t4; if (t[5] == c0) goto t5; if (t[6] == c0) goto t6; if (t[7] == c0) goto t7; if (t[8] == c0) goto t8; if (t[9] == c0) goto t9; if (t[10] == c0) goto t10; if (t[11] == c0) goto t11; if (t[12] == c0) goto t12; if (t[13] == c0) goto t13; if (t[14] == c0) goto t14; if (t[15] == c0) goto t15; t += 16; continue; } if (t > t1) return NULL; if (t[0] == c0) goto t0; t++; } t15: t++; t14: t++; t13: t++; t12: t += 12; goto t0; t11: t++; t10: t++; t9: t++; t8: t += 8; goto t0; t7: t++; t6: t++; t5: t++; t4: t += 4; goto t0; t3: t++; t2: t++; t1: t++; t0: if (t[i] == p[i]) { for (i = 1; i < pn; i++) { if (t[i] != p[i]) goto nxt; } return (char *) t; } } } --さあ、久しぶりに「キモイい」プログラムになりました。 --この改造の趣旨は、とにかくtを加算したり、tnとの比較をしたりする頻度を下げたかったのです。だから16回に1度だけ加算&比較するようにしました。 --それで一致した場所が見つかった場合は、tをその分だけ加算しなければつじつまがあわないので、t++;が連続しているところのどこかへ分岐させています。 --やっていることはそれだけです。 -そしてお楽しみのベンチマークです。 -芸のないベンチマーク: #include <acl4v1.c> #include "t0003a.c" // ここにa_memmem()があります. int main(void) { char *s = malloc(900 * 1024 * 1024); // 900MB. int i, j = 0; for (i = 0; i <= 100000000; i++) // とりあえず1億. j += sprintf(s + j, "%d ", i); printf("j=%d\n", j); clock_t t0, t1, t2; t0 = clock(); int rs = (int) strstr(s, " 100000000"); t1 = clock(); int rm = (int) a_memmem(s, j, " 100000000", 10); t2 = clock(); printf("strstr=%x\n", rs); printf("a_memmem=%x\n", rm); printf("t1-t0=%d t2-t1=%d\n", t1-t0, t2-t1); return 0; } --まあいろいろ雑ですが、とりあえずこれでもベンチマークにはなります。 --3回やってみると、こんな感じでした。まあ標準関数のstrstr()と互角ですね。この処理系の clock() の単位はミリ秒です。 t1-t0=321 t2-t1=292 t1-t0=272 t2-t1=272 t1-t0=278 t2-t1=273 --ちなみに今回の malloc() は acl4 の malloc_() ではなく、標準関数のものをそのまま使っています。別にどっちでも同じなので。 -次に、 a_memmem() が少し有利になりそうなベンチマークです。いつも最初の数文字が一致しているようなパターンです。 #include <acl4v1.c> #include "t0003a.c" int main(void) { char *s = malloc(140 * 1024 * 1024); // 140MB. int i, j = 0; for (i = 0; i <= 10000000; i++) // とりあえず100万. j += sprintf(s + j, "count=%d ", i); printf("j=%d\n", j); clock_t t0, t1, t2; t0 = clock(); int rs = (int) strstr(s, "count=10000000"); t1 = clock(); int rm = (int) a_memmem(s, j, "count=10000000", 14); t2 = clock(); printf("strstr=%x\n", rs); printf("a_memmem=%x\n", rm); printf("t1-t0=%d t2-t1=%d\n", t1 - t0, t2 - t1); return 0; } --3回やってみるとこんな感じでした。 t1-t0=101 t2-t1=46 t1-t0=96 t2-t1=45 t1-t0=97 t2-t1=42 --やったね! ** 2026.05.22(金) #0 -最近は、SecHack365の選考に忙殺されていて、acl4があまり進んでいません。 -あ、でも、昨日は buntan-pc のuchanさんと、CPUにこんな命令があったら便利になるかもしれないっていう話をたくさんしました。私は CPU の自作を気軽にやったりはできないのですが(勉強不足で)、仮想マシンとしてそういう命令を持たせることなら余裕でできるので、いつか試すかもしれません。 -考えていた内容は、たとえば整数演算でゼロ割しても例外を起こさずに商を 0x800...000 として処理を続行してくれて、またNULLポインタでメモリライトしても無視してくれて、NULLポインタでメモリリードしたら 0x800...000 が読みだされて続行されるような、そんな命令体系です。どの場合も、問題が起きて対処した場合はフラグレジスタ内のエラーフラグを1にするくらいはしてもいいかなと思っています。加減算などでもオーバーフローを検出したら値を 0x800...000 にするモード(モードというか新設命令)があってもいいと思っています。 -この命令体系だとプログラミングのパラダイムが変わるかもしれなくて、今までは演算する前に入力値が正常の範囲に収まっているかチェックしながら演算してきました。そうでないと例外を踏まされるからです。でもこの命令体系なら、値はおかしくなるもののとにかく処理は問題なく続行できるので、最後にエラーフラグをみてエラー処理するだけで済みます。結果的にif文が減ると思うので高速化するかもしれません。 ** 2026.05.26(火) #0 -hh4みたいな多倍長の形式について思索。 -4bitあれば 0000~1111 の16通りの表現があります。これをAパターン(a通り)とBパターン(b通り)に分けます。a+b=16とします。 -Bパターンが来たら、そこで終了です。これで 0~(b-1) を表現できます。 -Aパターンが来たら、Bパターンが来るまで繰り返してから終了します。ABの場合 0~(ab-1)を表現できます。AAAB形式なら 0~(aaab-1) です。 -hh4の単純形式の場合、a=b=8になっていて、だから 8/64/512...と拡張していくことができます。 -拡張形式として、10+6=16 みたいなものを考えることができます。4bitで0~5、8bitで0~59、12bitで0~599、16bitで0~5999が表現できます。8+8=16と比べると、4bit形式は7→5、8bit形式は63→59と犠牲になりますが、12bitは511→599となって少し増えます。 -表にしてみます。 |8+8=16型|8|64|512|4096| |10+6=16型|6|60|600|6000| |7+9=16型|9|63|441|3087| |6+10=16型|10|60|360|2160| |5+11=16型|11|55|275|1375| |4+12=16型|12|48|192|768| -この中だと4+12=16はかなり魅力的に見えます。 ** 2026.05.27(水) #0 -[[a26_txt03]](川合のプログラミング言語自作のためのテキスト第四版#0)を書き始めました。 ** 2026.05.30(土) #0 -説明を平易にしようとすると、ポインタとかはあまり使えないよなあと思いつつ、でもやりたいことができないのは本末転倒なので、やっぱりポインタを遠慮なく使って書いています。 -だから言い訳を考えているのですが、結局ライブラリは「どんな機能を提供してくれているのか」が一番大事で、その実装の細部まで理解しなきゃいけないかというとそんなことはないですよね。・・・「私たちは printf() の実装がどうなっているか知らなくても、 printf() を問題なく使えるのです。だから分かるところまでわかれば良くて、よくわかんないところは適当に読み飛ばしてください」という説明で逃げようかなと・・・。 ** 2026.05.31(日) #0 -まあまあいいものができた予感! ** 2026.06.01(月) #0 -メモリ管理とかデバイス管理など、プログラミングでは〇〇管理という機能を作ることがよくあります。それでその管理を担うクラスとかは管理者なので、〇〇マネジャーという名前で命名します。その際に私は長い名前を嫌うので、マネジャーを短くしようとして Man と略してきました。だからメモリ管理なら MemMan になります。 -ところが、ふと「なぜ私は Manager を Man と略すのだろうか」と思いました。つまり Mgr じゃないのはなぜか?ということです。・・・いや、私の漠然とした認識では、「人間のマネージャーとかを略して書くときは Mgr だけど、プログラミングの世界では Man と書く」ということになっていたのですが、果たして本当にそうなのかと急に不安になったのです。 -不安は的中します。Google検索で調べていたらGeminiに、プログラミングの世界でも普通はMgrだよ、むしろManはマニュアルの略だよ!Manだと男の人とも間違えやすいからよくないよ、と言われてしまいました。・・・しかし一方で「DevMan」は何の略だと思うかとGeminiに聞くと、「デバイスマネジャー」か「デベロップメントマネジャー」だと言われました。あれ?キミはそこは「デバイスマニュアル」と言うところじゃないの??(笑)。 -Geminiのちょっと微妙な受け答えはともかくとして、Man派は少数でありそうな感じはしました。よしこれからは私も Mgr 派になろう! -とまあ、それはそれでよいとして、ではなぜ私は今まで Man 派でここまでやってきたのかという疑問が残ります。私は自分で省略形を勝手に考え出したわけではないのです。・・・それで、私が最初にプログラミングで〇〇マネジャーというのを学んだときに省略形が Man だったから、それに引きずられてきたんだろうと考えました。 -私が最初に学んだのは・・・OS-9/6809です。それで昔読んだ書籍をひっぱり出して調べてみたら、 IOMAN, RBFMAN, SCFMAN, PIPEMAN, SBFMAN, NFMAN などと、それらしきものがどんどん見つかりました。ああこれです。私はこれを最初に見たから Man=マネジャー になったのです。そっかー、OS-9は主流になれなかったから、この略し方はそれ以上には流行らなかったということなんですね。納得しました。 ** 2026.06.01(月) #0 -[[a26_txt03_001]](川合のプログラミング言語自作のためのテキスト第四版#001)を書きました。 -このログページの一部を切り出して[[a4_log11]]を作って、その分このログページを短くしました(長すぎだよなーと思っていたので)。 ** 2026.06.02(火) #0 -「デバッグ支援機能はこれが最後です」なんて書いたら、「うーん他に何かいいアイデアはないかな、あったら今のうちに入れておかないと・・・」っていう気分になってきました(笑)。 ** 2026.06.09(火) #0 -ここしばらく、SecHack365関係で気が散って、あまり進んでいませんでしたが、そろそろ再開します! ** 2026.06.10(水) #0 -C言語の標準ライブラリのqsort()関数は、配列をソートできて便利なのですが、 a[i]があったとして、私が欲しいのはa[i]の最大値や最小値ではなく、いや最大値や最小値も欲しいのですが、それよりもその時のiがいくつ七日が知りたいです。つまりargmaxとかargminです。もっというと1番目だけじゃなくて2番目や3番目のiも知りたいです。 -この目的のためにqsortを使う場合は、a[i]の値とiの値を構造体で組にしてソートするのが普通かなと思いますが、もういっそのこと、iだけにしてしまってソートしてしまえばいいと思ってやってみました。 int (*iqsor_cmpF)(void *, int, int); void *iqsor_cmpW; int iqsort_cmp(const void *a, const void *b) { int ai = *(const int *) a; int bi = *(const int *) b; return iqsor_cmpF(iqsor_cmpW, ai, bi); } void iqsort(int *a, int n, int (*cmpF)(void *, int, int), void *cmpW) { int i; for (i = 0; i < n; i++) a[i] = i; iqsor_cmpF = cmpF; iqsor_cmpW = cmpW; qsort(a, n, sizeof (int), iqsort_cmp); } // ここからサンプルコード. int cmp(void *w, int i, int j) { const char **s = w; return strcmp(s[i], s[j]); } int main() { char *name[5] = { "Tanaka", "Yamada", "Kawai", "Sato", "Suzuki" }; int idx[5], i; iqsort(idx, 5, cmp, name); // これで idx[] を得る. for (i = 0; i < 5; i++) { printf("%d ", idx[i]); } printf("\n"); for (i = 0; i < 5; i++) { printf("%d: %s\n", i, name[idx[i]]); } return 0; } -これを実行すると、以下のようになります。 2 3 4 0 1 0: Kawai 1: Sato 2: Suzuki 3: Tanaka 4: Yamada -最初にidx[i]の値が表示されています。idx[0]は2なので、一番小さいのはname[2]です。idx[4]は1なので、一番大きいのはname[1]です。 -要点は、name[]は全く並べ替えられることなくそのままになっていて、idx[]に順番を辞書引きするための目次(index)が入っているというところです。・・・あとは比較関数に任意のポインタを一つ渡せるようにしています。 -私はこの仕様のほうが使いやすいなーと思いました。 -いやその別に全然大したことはないのですが・・・。 * こめんと欄 #comment
タイムスタンプを変更しない
* acl4の開発ログ -(by [[K]], 2026.03.14) -''acl4開発のもくじ → [[a4_i01]]'' -開発ログの過去ログのもくじ → [[a4_log00]] -最近の過去ログ: //-[[a4_log08]](2026.03.14(土)#0~2026.03.17(火)#2): プリプロセッサ的な置換ルールだけで、コンパイラの大半の処理をやってしまえば、コンパイラはすごく作りやすくなるかもしれないという構想を思いつく。→ミニコンパイラ構想。および試作。 -[[a4_log09]](2026.03.18(水)#0~2026.03.26(木)#5): 有理数型を作ろうと思ったけど、途中から浮動小数点数から近い有理数を探すアルゴリズムに注目。 -[[a4_log10]](2026.03.27(金)#0~2026.04.10(金)#3): プリプロセッサの連鎖でコンパイラを構成するやり方を引き続き試作。 -[[a4_log11]](2026.04.11(土)#0~2026.04.18(土)#0): acl4 → acl4v1 の移行。 ** 2026.04.20(月) #0 -mallocでメモリを確保したり、reallocでメモリを拡張したりすると、なんか適当な値が雑に書かれたメモリが手に入ります。そのことそのものに問題はないのですが、しかしデバッグするときにこれを見て「初期化をしてないからこの値なのか」「初期化をしたからこの値なのか」よくわからない気分になります。だから自作mallocと自作reallocに手を加えてデバッグモードの時はメモリを適当な値で塗りつぶして渡すという仕組みを作ることにしました。 -とりあえず簡単のためにmemset()で塗りつぶすことにして、ではどんな値で塗りつぶすかということを考えました。 --0x00: これで塗りつぶすと明らかに「何も書いてない」感じになります。それはとても良いですが、そもそもプログラムのほうで初期化する際も 0x00 で塗りつぶすことが多く、つまりこれだと初期化を忘れてもかえって気づきにくい、初期化し忘れているのにデバッグモードだと正常に動いちゃう、という問題があります(リリースモードでは動かない)。0x00にしたいのならプログラムが自分でやるべきです。mallocやreallocを当てにされては困ります。 --0xff: 0x00が一番きれいなのだとしたら、じゃあ一番汚いのは何か?それは0xffだよね、と考えました。数値的にも255は0から一番遠い値で、未初期化だと最も気づきやすそうです。・・・しかし、非常によく使われる符号付き整数で考えれば、0xffは8bitの-1でもあります。0xffffは16bitの-1、0xffffffffは32bitの-1です。つまり未初期化で値を取り出すと-1が読めてしまうわけです。-1なんて0にかなり近い数値です。初期化を忘れていても、「なんかちょっとだけおかしいかもしれないけど、まあでもこんなものかな?」で見過ごされる可能性があります。だからもっと明らかにおかしい値にするべきです。 --0x80: では符号付きの整数で、0x00から最も遠い値にしたらどうかと考えました。8bitの0x80は-128、16bitの0x8080は-32640、32bitの0x80808080は-2139062144です。これはちょっと普通では出てこない数値でしょう。しかし、だからこそ、invalidな値として初期値で使われる場合もあります(私自身もやったことがあります)。・・・それだと初期化忘れを見逃す原因にもなりうるのです。 --0x87: それでは、この1バイトの中の「0」のビットと「1」のビットを同数にすることにして、それで0x00から最も遠い値というのを考えてみました。「0のビットと1のビットを同数にする」というのは、メモリチェックパターンの0x55aaとかでも使われている考え方です。今回はメモリチェックとは全く関係ない文脈ですが、オールゼロやオールワンが真っ先にダメだというのなら、いっそのこと半々にしてしまえ、という安易な発想です。・・・0x87や0x8787などを初期値として採用するケースはまず考えられないですし、異常値っぽさは十分にあります。ということで今回はこれで行くことにします。 ** 2026.04.20(月) #1 -acl4v1でもdefine機能が使えるようになりました。でもまだtypedDefがうまく動いていません。 ** 2026.04.20(月) #2 -なんか最近このwikiが不安定な気がします。よしxreaからcoreserverに移行しよう! ** 2026.04.21(火) #0 -ほかのドメインで練習中です。 ** 2026.04.21(火) #1 -移行できたと思います。無事に安定して表示できるといいなあ。 ** 2026.04.21(火) #2 -さっきバグって、ポインタが変な値になっていました。当然、セグメンテーションフォルトで落ちたわけですが、なんでだ?と思ってポインタ値をprintfしてみたら、なんと 0x87878787 ! -・・・うお、いきなりこれかー(笑)。つまり初期化してないポインタがあって、そこを読んだわけですね。なんてわかりやすい! -ということで30秒くらい、くくく・・・と笑ってしまいました。 -[註] 2026.04.20(月)#0 で自分が仕掛けたバグ発見用のトラップに、さっそくはまって原因が分かった、という事例です(笑)。 -いや、これに限らず、私は毎日何度も自分の仕込んだデバッグトラップを踏んでいます。 -まあなんとも情けないわけですが、でもそのうちの多くはデバッグトラップがなくてもすぐに直せただろうとは思います。 -ただなんというか、私のデバッグトラップたちが優秀すぎて(?)、私が頑張る前に先にバグを見つけてしまうんです。 -ありがたい!! acl4 は私に合っている!! ** 2026.04.22(水) #0 -a4_0001~a4_0014のリファクタリングが完了しました。次はミニコンパイラをacl4v1用に直します。 ** 2026.04.24(金) #0 -やっと、何を作ったらプリプロセッサの良さが伝わるかが分かったので、頑張って作ります。たぶん明日か明後日にはできます! ** 2026.04.25(土) #0 -できました! --[[a4_p0007]]: プリプロセッサ処理をする関数を作ったら、いろんな言語が簡単に作れるかもしれない可能性が出てきました! -[[a4_i01]]のページを改装中です。 ** 2026.04.26(日) #0 -[[a4_0016]]~[[a4_0023]]と[[a4_t0002]]のページを作りました。 -これで acl4v1 は旧 acl4 に追いつきました。 ** 2026.04.27(月) #0 -[[a4_p0007]]のページにダウンロード用のzipファイルを追加しました。これで、35.0KBってホントにできるの?って言われずに済みます。 -x86-32でコンパイルしないと、35.0KBにはなりません。あと /Da_DbgLv=0 をつけて、リリースモードにする必要もあります。 -さて、次にやるべきは配列変数のサポートです。配列変数が使えるようになれば、kcubeが動かせるようになるはずです。 -とにかくこの「プリプロセッサで置換する方式」でどこまで行けるのか確かめたいです。 ** 2026.04.28(火) #0 -[[a4_t0002]] は Windows 専用になっていますが、かつて X11 でのグラフィックのやり方を勉強したので、それを思い出せば、 Linux 版も作れそうだとは思っています。 --[[a24_raspberrypi01]], [[a24_raspberrypi02]] ** 2026.04.30(木) #0 -考えがうまくまとまらないので、とりあえずシンプルにやります。 -たぶん設計がまとまらないのは、それを設計するだけの知識が揃っていないのだと思います。そんなときに無理しても駄作になって結局作り直すことになるので、背伸びはせず、単純明快にできるところまでをやります。 ** 2026.05.01(金) #0 -A4vm_eval0()のEnter命令の仕様: -Ent_III(i, j, k) --i: 整数レジスタ数, j: 浮動小数点数レジスタ数, k: 関数呼び出しのための予約メモリ -今回追加する引数: -Ent_IIIII(i, j, k, l, m) --l: ポインタレジスタ数, m: スタック変数のための領域サイズ ** 2026.05.01(金) #1 -そうかー! 構造体をどうしたらいいかなって考えるよりも先に、プリプロセッサの力でJITコンパイラが作れるのか試すべきです。 -もしうまくできたら、「これでもう将来どんなCPUが主流になっても、簡単に移植できるよー」って言えるようになるはずです。 ** 2026.05.01(金) #2 -これが動くようになりました! >p0008a "src:inline: DB(0x68); DD(\"hello\\n\"); DB(0xe8); DD_R(printf - 4); DB(0x58); DB(0xc3);" -PUSH(imm32)の機械語を 6A だと誤解して、それに気づかなくて、デバッグに30分を要してしまいました。 ** 2026.05.01(金) #3 -こう書けるようになりました。これで機械語を間違えるバグはなくなります。 >p0008a "src:inline: PUSH_I(\"hello\\n\"); CALL_I(printf); POP_R(EAX); RET();" -これが機械語になってすぐに動くので、「JITアセンブラ」と言えるかもしれません。 -ちなみに p0008a.c は50行のプログラムです。50行でJITアセンブラが作れるので楽しいです。 ** 2026.05.01(金) #4 -まだ printf しかできないですが、とにかくJITコンパイラはできました。ちゃんとx86の機械語に変換されて動いています。 -[追記] その後、for文もできました。簡単すぎて楽しいです。 -[追記] さらに1時間くらいやったら、 p0007h.txt が動くくらいになりました!( mandel )。やっぱりJITコンパイラは実行が速いです。 -やっぱりプリプロセッサは偉大だなあー。 ** 2026.05.03(日) #0 -今、 p0008a.c っていうJITコンパイラを作っているのですが、その中身は Cコンパイラ+アセンブラ になっています。 -そのアセンブラ部分は、 #define RET() DB(0xC3) みたいなのを50行くらい書いただけでしかないのですが、命令によってはこのDB命令は連続するので DB(a, b); みたいな形式もサポートしたいなって思いました。 -でもなあ、複数の引数をサポートするのは面倒だよなあ、まあだから現状のままでいいかー、2回書けばいいだけだし・・・とか思って妥協していました。 -でもさっき、思いつきました。あれ?もしかして、 #define DB(a, b) DB(a); DB(b) っていう1行を追加しておけばいいだけなんじゃないの? -その通りでした。こうして DB() 命令は複数の引数に対応できたのでした。 -こんなに楽していいのかな?(笑) -上記で、平気なふりして「#define を50行くらい書くだけで、アセンブラができた」みたいなことを書いていますが、これも結構とんでもなくて、今まで何度かアセンブラを作ってきた私としては「今までの苦労は一体なんだったのか・・・」という気持ちになっています。 -まあ進歩ってそういうものですよね。 ** 2026.05.04(月) #0 -この欄の 2026.04.04.11(土)#1 に、プリプロセッサだと、a,b,cの定義の順序によらず値が正しく求められるという話を書きました。 #define c a+b #define b 2 #define a 1 printf("%d\n", c); -これに対して、printf()が最後にある必要はあるけど、それ以外の順序はどうやっても同じ結果になるという話です。・・・それの続きです。 -今回プリプロセッサを改良したので、以下のような書き方でも同じ結果になります。 printf("%d\n", c); #define c a+b #define b 2 #define a 1 -つまり私の自作プリプロセッサなら define は後出しじゃんけんしてもいいのです。 ** 2026.05.04(月) #1 -[[a4_t0003]] を書きました。現行のミニコンパイラを補助ライブラリとしてまとめて、何度もコピー&ペーストしないでもいいようにしました。 -ちょっと実験したのでメモしておきます。 8.0KB : printf("hello, world\n"); のみの.exeファイル 23.5KB : p0008a.c で pp のみ(Mode=0) 28.0KB : p0008a.c で pp+cc (Mode=1) 30.0KB : p0008a.c で pp+cc+cjit (Mode=2) 35.5KB : p0008a.c で pp+cc+cjit+clang (Mode=3) 37.5KB : p0008a.c で pp+cc+cjit+clang+calc+graphic (Mode=4) -pp(プリプロセッサ)は実質的には 15.5KB 。 -cc(Cコンパイラ)は pp+4.5KB (でも実装している機能が少ないからここは不正確かな)。 -JITコンパイラは +2.0KB 程度。 -a4vm を入れると +5.5KB 程度。 -[[a4_t0002]]程度のグラフィック機能であれば +2.0KB 程度。 ** 2026.05.04(月) #2 -[[a4_p0008]] を書きました。JITコンパイラを試せます。 ** 2026.05.06(水) #0 -「新しい言語の作り方」という12ページのスライド資料をなんとなく作りました。 --https://essen.osask.jp/files/pr20260506a_acl4.pdf --今のところ発表予定はありません。 --[追記] 資料を更新しました → https://essen.osask.jp/files/pr20260505b_acl4.pdf ** 2026.05.10(日) #0 -運よくプレゼン機会があったので、上記の「新しい言語の作り方」でLTをしてきました(昨日)。 -好評でたくさんコメントをもらえたので、それを加筆したバージョンを作ろうと思います。 -ちなみにここで発表してきました。 --「第51回 自作OSもくもく発表作業会」 https://osdev-jp.connpass.com/event/379064/ --(ここの発表枠5で) ** 2026.05.13(水) #0 -ここまでの開発を振り返っています。 -[1] 第1層([[a4_0016]]) --デバッグに便利な機能が付いた malloc/realloc/free を整備。 -[2] 第2層([[a4_0017]]~[[a4_0019]]) --第1層の成果を使って、vector<char> を参考にして作った VecChr クラス(動的可変長配列)、簡単な Key-Value Store を提供する Set0 クラスを整備。 --さらにトークンを切り出す Token0/Token1 クラス、かっこでくくられた構造を切り分ける parseArgs などを整備。 -[3] 第3層([[a4_0020]]) --第2層の成果を使って、式の評価クラスを整備。文字列で式を与えれば、計算して答えを返します。 -[4] 第4層([[a4_0021]]~[[a4_0022]]) --第3層までの成果を使って、プリプロセッサ関数を整備。 -[5] 第5層([[a4_0023]]~) --第4層で作ったプリプロセッサ関数を使って、アセンブラやC言語を試作。 --JITコンパイラも難なく成功。ついでにa4vmという仮想マシンも整備。 --概要 → https://essen.osask.jp/files/pr20260505b_acl4.pdf --実装 → [[a4_p0008]] -ということで、まずAを作り、そのAを利用してBを作り、そのBを利用してCを作り・・・というのを繰り返して、どんどん自分の開発力をアップさせようというたくらみは、今のところ順調に進んでいます! ** 2026.05.16(土) #0 -何となく自分が納得できる程度の memmem() ができたのでここに書きます。 -まず memmem() が何かというと strstr() の仲間みたいなものです。ゼロ終端文字列ではなくて、メモリブロックの中からメモリブロックを探すのです。 --https://manpages.ubuntu.com/manpages/focal/ja/man3/memmem.3.html -GNU拡張なので、標準の <string.h> には入っていません。私はとっても便利な関数だと思っていますが。 -普通に作っても面白くないので、ちょっとだけ工夫をして高速化してあります。 -説明用のバージョン: a_static char *a_memmem0(const char *t, intptr_t tn, const char *p, intptr_t pn) { if (pn <= 0) return (char *) t; if (tn < pn) return NULL; intptr_t i = pn - 1; const char *t1 = t + tn - pn, c0 = p[0]; for (t--;;) { nxt: t = memchr(t + 1, c0, t1 - t); if (t == NULL) return NULL; if (t[i] == p[i]) { for (i = 1; i < pn; i++) { if (t[i] != p[i]) goto nxt; } return (char *) t; } } } --まず memchr() で0バイト目が一致している場所を見つけてきます。無事に見つかったら、i番目についても比較します。それも一致したら、全部比較します。 --全部比較するときに memcmp() を使えばいいし、比較だけならたぶんそのほうが速いのですが、不一致だった場合に何バイト目で食い違ったのかを知りたいので、自前ループしています。 --普通、この手のストリング検索では、 Boyer–Moore の有名なアルゴリズムを使って高速化します。しかしそれは事前解析処理が面倒だったので今回はやらないで、不一致の場合に速く気付くことを目指してこのアルゴリズムを思いつきました。まあシンプルなので、たぶん車輪の再発明だろうと思います(笑)。 --最初に t--; して、次に t+1 から memchr() するのは一見すると無駄なのですが、不一致になって戻ってきたときに t+1 してくれるのは便利なのです。だから最初に t--; してでも、この構造にしたかったのです。 -でもこのバージョンをそのまま使うと、 memchr() があまり速くありません。こういうストリング系の関数は自前実装しても性能がよくなることはほとんどないと言われていますが、私はそんなうわさ程度じゃあきらめません。そもそもここに書くことになったということは、自前実装でも十分に速くなったから書いているのです(笑)。 -ちゃんと高速化したバージョン: a_static char *a_memmem(const char *t, intptr_t tn, const char *p, intptr_t pn) { if (pn <= 0) return (char *) t; if (tn < pn) return NULL; intptr_t i = pn - 1; const char *t1 = t + tn - pn, *t16 = t1 - 16, c0 = p[0]; for (t--;;) { nxt: t++; for (;;) { if (t <= t16) { if (t[0] == c0) goto t0; if (t[1] == c0) goto t1; if (t[2] == c0) goto t2; if (t[3] == c0) goto t3; if (t[4] == c0) goto t4; if (t[5] == c0) goto t5; if (t[6] == c0) goto t6; if (t[7] == c0) goto t7; if (t[8] == c0) goto t8; if (t[9] == c0) goto t9; if (t[10] == c0) goto t10; if (t[11] == c0) goto t11; if (t[12] == c0) goto t12; if (t[13] == c0) goto t13; if (t[14] == c0) goto t14; if (t[15] == c0) goto t15; t += 16; continue; } if (t > t1) return NULL; if (t[0] == c0) goto t0; t++; } t15: t++; t14: t++; t13: t++; t12: t += 12; goto t0; t11: t++; t10: t++; t9: t++; t8: t += 8; goto t0; t7: t++; t6: t++; t5: t++; t4: t += 4; goto t0; t3: t++; t2: t++; t1: t++; t0: if (t[i] == p[i]) { for (i = 1; i < pn; i++) { if (t[i] != p[i]) goto nxt; } return (char *) t; } } } --さあ、久しぶりに「キモイい」プログラムになりました。 --この改造の趣旨は、とにかくtを加算したり、tnとの比較をしたりする頻度を下げたかったのです。だから16回に1度だけ加算&比較するようにしました。 --それで一致した場所が見つかった場合は、tをその分だけ加算しなければつじつまがあわないので、t++;が連続しているところのどこかへ分岐させています。 --やっていることはそれだけです。 -そしてお楽しみのベンチマークです。 -芸のないベンチマーク: #include <acl4v1.c> #include "t0003a.c" // ここにa_memmem()があります. int main(void) { char *s = malloc(900 * 1024 * 1024); // 900MB. int i, j = 0; for (i = 0; i <= 100000000; i++) // とりあえず1億. j += sprintf(s + j, "%d ", i); printf("j=%d\n", j); clock_t t0, t1, t2; t0 = clock(); int rs = (int) strstr(s, " 100000000"); t1 = clock(); int rm = (int) a_memmem(s, j, " 100000000", 10); t2 = clock(); printf("strstr=%x\n", rs); printf("a_memmem=%x\n", rm); printf("t1-t0=%d t2-t1=%d\n", t1-t0, t2-t1); return 0; } --まあいろいろ雑ですが、とりあえずこれでもベンチマークにはなります。 --3回やってみると、こんな感じでした。まあ標準関数のstrstr()と互角ですね。この処理系の clock() の単位はミリ秒です。 t1-t0=321 t2-t1=292 t1-t0=272 t2-t1=272 t1-t0=278 t2-t1=273 --ちなみに今回の malloc() は acl4 の malloc_() ではなく、標準関数のものをそのまま使っています。別にどっちでも同じなので。 -次に、 a_memmem() が少し有利になりそうなベンチマークです。いつも最初の数文字が一致しているようなパターンです。 #include <acl4v1.c> #include "t0003a.c" int main(void) { char *s = malloc(140 * 1024 * 1024); // 140MB. int i, j = 0; for (i = 0; i <= 10000000; i++) // とりあえず100万. j += sprintf(s + j, "count=%d ", i); printf("j=%d\n", j); clock_t t0, t1, t2; t0 = clock(); int rs = (int) strstr(s, "count=10000000"); t1 = clock(); int rm = (int) a_memmem(s, j, "count=10000000", 14); t2 = clock(); printf("strstr=%x\n", rs); printf("a_memmem=%x\n", rm); printf("t1-t0=%d t2-t1=%d\n", t1 - t0, t2 - t1); return 0; } --3回やってみるとこんな感じでした。 t1-t0=101 t2-t1=46 t1-t0=96 t2-t1=45 t1-t0=97 t2-t1=42 --やったね! ** 2026.05.22(金) #0 -最近は、SecHack365の選考に忙殺されていて、acl4があまり進んでいません。 -あ、でも、昨日は buntan-pc のuchanさんと、CPUにこんな命令があったら便利になるかもしれないっていう話をたくさんしました。私は CPU の自作を気軽にやったりはできないのですが(勉強不足で)、仮想マシンとしてそういう命令を持たせることなら余裕でできるので、いつか試すかもしれません。 -考えていた内容は、たとえば整数演算でゼロ割しても例外を起こさずに商を 0x800...000 として処理を続行してくれて、またNULLポインタでメモリライトしても無視してくれて、NULLポインタでメモリリードしたら 0x800...000 が読みだされて続行されるような、そんな命令体系です。どの場合も、問題が起きて対処した場合はフラグレジスタ内のエラーフラグを1にするくらいはしてもいいかなと思っています。加減算などでもオーバーフローを検出したら値を 0x800...000 にするモード(モードというか新設命令)があってもいいと思っています。 -この命令体系だとプログラミングのパラダイムが変わるかもしれなくて、今までは演算する前に入力値が正常の範囲に収まっているかチェックしながら演算してきました。そうでないと例外を踏まされるからです。でもこの命令体系なら、値はおかしくなるもののとにかく処理は問題なく続行できるので、最後にエラーフラグをみてエラー処理するだけで済みます。結果的にif文が減ると思うので高速化するかもしれません。 ** 2026.05.26(火) #0 -hh4みたいな多倍長の形式について思索。 -4bitあれば 0000~1111 の16通りの表現があります。これをAパターン(a通り)とBパターン(b通り)に分けます。a+b=16とします。 -Bパターンが来たら、そこで終了です。これで 0~(b-1) を表現できます。 -Aパターンが来たら、Bパターンが来るまで繰り返してから終了します。ABの場合 0~(ab-1)を表現できます。AAAB形式なら 0~(aaab-1) です。 -hh4の単純形式の場合、a=b=8になっていて、だから 8/64/512...と拡張していくことができます。 -拡張形式として、10+6=16 みたいなものを考えることができます。4bitで0~5、8bitで0~59、12bitで0~599、16bitで0~5999が表現できます。8+8=16と比べると、4bit形式は7→5、8bit形式は63→59と犠牲になりますが、12bitは511→599となって少し増えます。 -表にしてみます。 |8+8=16型|8|64|512|4096| |10+6=16型|6|60|600|6000| |7+9=16型|9|63|441|3087| |6+10=16型|10|60|360|2160| |5+11=16型|11|55|275|1375| |4+12=16型|12|48|192|768| -この中だと4+12=16はかなり魅力的に見えます。 ** 2026.05.27(水) #0 -[[a26_txt03]](川合のプログラミング言語自作のためのテキスト第四版#0)を書き始めました。 ** 2026.05.30(土) #0 -説明を平易にしようとすると、ポインタとかはあまり使えないよなあと思いつつ、でもやりたいことができないのは本末転倒なので、やっぱりポインタを遠慮なく使って書いています。 -だから言い訳を考えているのですが、結局ライブラリは「どんな機能を提供してくれているのか」が一番大事で、その実装の細部まで理解しなきゃいけないかというとそんなことはないですよね。・・・「私たちは printf() の実装がどうなっているか知らなくても、 printf() を問題なく使えるのです。だから分かるところまでわかれば良くて、よくわかんないところは適当に読み飛ばしてください」という説明で逃げようかなと・・・。 ** 2026.05.31(日) #0 -まあまあいいものができた予感! ** 2026.06.01(月) #0 -メモリ管理とかデバイス管理など、プログラミングでは〇〇管理という機能を作ることがよくあります。それでその管理を担うクラスとかは管理者なので、〇〇マネジャーという名前で命名します。その際に私は長い名前を嫌うので、マネジャーを短くしようとして Man と略してきました。だからメモリ管理なら MemMan になります。 -ところが、ふと「なぜ私は Manager を Man と略すのだろうか」と思いました。つまり Mgr じゃないのはなぜか?ということです。・・・いや、私の漠然とした認識では、「人間のマネージャーとかを略して書くときは Mgr だけど、プログラミングの世界では Man と書く」ということになっていたのですが、果たして本当にそうなのかと急に不安になったのです。 -不安は的中します。Google検索で調べていたらGeminiに、プログラミングの世界でも普通はMgrだよ、むしろManはマニュアルの略だよ!Manだと男の人とも間違えやすいからよくないよ、と言われてしまいました。・・・しかし一方で「DevMan」は何の略だと思うかとGeminiに聞くと、「デバイスマネジャー」か「デベロップメントマネジャー」だと言われました。あれ?キミはそこは「デバイスマニュアル」と言うところじゃないの??(笑)。 -Geminiのちょっと微妙な受け答えはともかくとして、Man派は少数でありそうな感じはしました。よしこれからは私も Mgr 派になろう! -とまあ、それはそれでよいとして、ではなぜ私は今まで Man 派でここまでやってきたのかという疑問が残ります。私は自分で省略形を勝手に考え出したわけではないのです。・・・それで、私が最初にプログラミングで〇〇マネジャーというのを学んだときに省略形が Man だったから、それに引きずられてきたんだろうと考えました。 -私が最初に学んだのは・・・OS-9/6809です。それで昔読んだ書籍をひっぱり出して調べてみたら、 IOMAN, RBFMAN, SCFMAN, PIPEMAN, SBFMAN, NFMAN などと、それらしきものがどんどん見つかりました。ああこれです。私はこれを最初に見たから Man=マネジャー になったのです。そっかー、OS-9は主流になれなかったから、この略し方はそれ以上には流行らなかったということなんですね。納得しました。 ** 2026.06.01(月) #0 -[[a26_txt03_001]](川合のプログラミング言語自作のためのテキスト第四版#001)を書きました。 -このログページの一部を切り出して[[a4_log11]]を作って、その分このログページを短くしました(長すぎだよなーと思っていたので)。 ** 2026.06.02(火) #0 -「デバッグ支援機能はこれが最後です」なんて書いたら、「うーん他に何かいいアイデアはないかな、あったら今のうちに入れておかないと・・・」っていう気分になってきました(笑)。 ** 2026.06.09(火) #0 -ここしばらく、SecHack365関係で気が散って、あまり進んでいませんでしたが、そろそろ再開します! ** 2026.06.10(水) #0 -C言語の標準ライブラリのqsort()関数は、配列をソートできて便利なのですが、 a[i]があったとして、私が欲しいのはa[i]の最大値や最小値ではなく、いや最大値や最小値も欲しいのですが、それよりもその時のiがいくつ七日が知りたいです。つまりargmaxとかargminです。もっというと1番目だけじゃなくて2番目や3番目のiも知りたいです。 -この目的のためにqsortを使う場合は、a[i]の値とiの値を構造体で組にしてソートするのが普通かなと思いますが、もういっそのこと、iだけにしてしまってソートしてしまえばいいと思ってやってみました。 int (*iqsor_cmpF)(void *, int, int); void *iqsor_cmpW; int iqsort_cmp(const void *a, const void *b) { int ai = *(const int *) a; int bi = *(const int *) b; return iqsor_cmpF(iqsor_cmpW, ai, bi); } void iqsort(int *a, int n, int (*cmpF)(void *, int, int), void *cmpW) { int i; for (i = 0; i < n; i++) a[i] = i; iqsor_cmpF = cmpF; iqsor_cmpW = cmpW; qsort(a, n, sizeof (int), iqsort_cmp); } // ここからサンプルコード. int cmp(void *w, int i, int j) { const char **s = w; return strcmp(s[i], s[j]); } int main() { char *name[5] = { "Tanaka", "Yamada", "Kawai", "Sato", "Suzuki" }; int idx[5], i; iqsort(idx, 5, cmp, name); // これで idx[] を得る. for (i = 0; i < 5; i++) { printf("%d ", idx[i]); } printf("\n"); for (i = 0; i < 5; i++) { printf("%d: %s\n", i, name[idx[i]]); } return 0; } -これを実行すると、以下のようになります。 2 3 4 0 1 0: Kawai 1: Sato 2: Suzuki 3: Tanaka 4: Yamada -最初にidx[i]の値が表示されています。idx[0]は2なので、一番小さいのはname[2]です。idx[4]は1なので、一番大きいのはname[1]です。 -要点は、name[]は全く並べ替えられることなくそのままになっていて、idx[]に順番を辞書引きするための目次(index)が入っているというところです。・・・あとは比較関数に任意のポインタを一つ渡せるようにしています。 -私はこの仕様のほうが使いやすいなーと思いました。 -いやその別に全然大したことはないのですが・・・。 * こめんと欄 #comment
テキスト整形のルールを表示する