* a23_useSelfMade #8
-(by [[K]], 2023.04.14)
--親ページ: [[a23_useSelfMade]]
** 2023.04.14 Fri #1 [casm] [easy-C]
-HL-9のcasm出力が少しマシになりました。
DosPrompt>hl9
HL9>casm 1
HL9>pr 0:<10
LdImm32(R01, 0);
CmpJmpGrtEquImm32(R01, 10, 2);
Label(3);
ApiPrIntReg32(R01);
Label(4);
AddShtImm32(R01, 1);
CmpJmpLesThnImm32(R01, 10, 3);
Label(2);
End();
HL9>
-これをt0004.cにコピペして実行すると・・・
DosPrompt>gcc -m32 -O3 -DAArch_X86 -DAGraph_Win -Iacl1/ -include casm32.c -o t0004.exe t0004.c -lgdi32 -lwinmm
DosPrompt>t0004
0123456789
DosPrompt>
-こんなふうにばっちり実行できました。
** 2023.04.17 Mon #1 [casm]
-casmはOSECPU-VMと同じく、整数レジスタはデータレジスタとポインタレジスタに分かれています。これは68000と同じ設計方針で、x86やARMやRISC-Vの汎用レジスタ方式とは違う設計です。汎用レジスタ方式では、データもポインタも同じレジスタを使います。今調べたら、PowerPCやMIPSも汎用レジスタ方式のようです。
-ということで、レジスタをデータとポインタに分けるのはもはや主流ではなさそうです(8bitのころは主流だった気がするのですが・・・)。それでも私は、分けたほうがいいと思っています。
-データに必要なbit幅と、アドレスを表すのに必要なbit幅は必ず一致するとは限りません。汎用レジスタ方式はこれらを強引に一致させています。8bit-CPUでは、アドレスレジスタが16bitでデータレジスタは8bitになっていて、この不一致に対応していました。
-OSECPU-VMが汎用レジスタ方式を採用しなかったのは、まず第一にこれは仮想的な命令セットで、実際は何らかのCPUの機械語に翻訳されて動くことを想定していたので、どのCPUでもそこそこうまく適合できる必要があったためです。データ&アドレス方式の実CPUが汎用レジスタの「ふり」をするのにはかなりのオーバーヘッドを伴いまず。それぞれのレジスタの用途を判別して実レジスタにマッピングしなければいけないからです。しかし逆の、汎用レジスタ方式の実CPUがデータ&アドレス方式を真似るのは結構簡単です。EAXは常にデータレジスタとして使う、みたいに決め打ちしてしまえばいいだけだからです。
-また私がもしFPGAなどでCPUを作る機会があったら、たぶんデータ&アドレス方式のCPUを作ると思います。理由は、例えばアドレスレジスタが8本でデータレジスタも8本の時、これは合わせて16本になるわけですが、しかしアドレッシングでベースレジスタを指定するときはアドレスレジスタ番号しか必要ないので、3bitで済みます。追加のディスプレースメントを指定するためのレジスタはデータレジスタしかありえないのでやはり3bitで済みます。こうして、レジスタは16本あるのに、命令の中のレジスタ指定のビット数は節約できるのです。
-一方で、問題があります。C言語で書かれたプログラムのうちのいくつかは、ポインタとintが同じbit幅であることを期待しています。今ではその書き方は問題になったので、intptr_tという型を使うようになっていますが、それでも「ポインタと何らかの整数型は相互に変換可能である」という期待はなくなっていません。しかもアドレスはバイト単位で表現されていると期待されています。たとえば4バイトのintがあったら、そのアドレスは当然4の倍数だと思われているのです。
-しかし私の考えでは、ポインタはその指しているメモリに読み書きができればいいのであって、その内部表現は本来はCPUの裁量でどうとでもなるはずです。私は触ったことはまだないのですが、charが32bitの処理系も存在していて、そういうマシンではintのポインタに++しても、1しか増えないかもしれません。
-こういうことに配慮すると、「ポインタの下位2bitは0に固定されるはずだから、ここにフラグを入れて利用しよう」みたいなアイデアはCPUに強く依存していることがわかります。ポインタの値をintにキャストしてANDして・・・みたいなのは移植性を下げてしまうのです(移植性を気にしなくていいのなら、メモリを節約するいい方法だとも思いますが)。
-ということで、私はポインタレジスタの中身はすべてのポインタとして扱うことにして、その中の数ビットをフラグにするのはやめるべきだと思います、移植性が必要なら。そしてこういうことを一切しないのなら、データレジスタとアドレスレジスタが分かれているのは、それほど不便ではないのです。
** 2023.04.18 Tue #1 [casm] [easy-C]
-これができるようになりました。
HL9>casm 1
HL9>AWin *w = aOpenWin(256, 256, "gradation"); c=0; [y=0:<256] { [x=0:<256] { aSetPix(w, x, y, c); c += 256; }}
LdPtrImm(P02, "gradation");
LdImm32(R00, 256)
LdImm32(R01, 256)
ApiOpenWinReg32(P03, R01, R00, P02);
CpyPtr(P04, P03);
LdImm32(R02, 0);
LdImm32(R03, 0);
CmpJmpGrtEquImm32(R03, 256, 2);
Label(3);
LdImm32(R04, 0);
CmpJmpGrtEquImm32(R04, 256, 4);
Label(5);
ApiSetPix32(P04, R04, R03, R02);
AddLngImm32(R02, R02, 256);
Label(6);
AddShtImm32(R04, 1);
CmpJmpLesThnImm32(R04, 256, 5);
Label(4);
Label(7);
AddShtImm32(R03, 1);
CmpJmpLesThnImm32(R03, 256, 3);
Label(2);
End();
** 2023.04.18 Tue #2 [casm]
-このcasmとOSECPU-VMの技術を組み合わせたら、また超小さいバイナリが作れるのかな?なんて思いついてしまいました。
-ちょっとOSECPU-VM rev2 のフロントエンドコードのメモを引っ張り出します。
|RIGHT:0|nop||
|RIGHT:1|LB|連番用|
|RIGHT:4-1|LB i|連番以外も可|
|RIGHT:2|LIMM/CP src,reg|{0,1,2,3,4,rep0,-1}|
|RIGHT:3|JMP lb||
|RIGHT:4-3|PLIMM lb,preg||
|RIGHT:4-4|CND reg|条件実行プリフィクス|
|RIGHT:5|API||
|RIGHT:4-5|API||
|RIGHT:6|loop v1,reg||
|RIGHT:4-6|loop v0,v1,reg||
|RIGHT:7|endloop||
|RIGHT:8|LMEM ?||
|RIGHT:9|SMEM ?||
|RIGHT:10|OR2||
|RIGHT:11|XOR2||
|RIGHT:12|AND2||
|RIGHT:13|SBX2||
|RIGHT:14|ADD2||
|RIGHT:15|SUB2||
|RIGHT:16|MUL2||
|RIGHT:17|SBR2||
|RIGHT:18|SHL2||
|RIGHT:19|SAR2||
|RIGHT:1a|DIV2||
|RIGHT:1b|MOD2||
|RIGHT:1e|PCP||
|RIGHT:20|CMP||
|RIGHT:21|CMP||
|RIGHT:22|CMP||
|RIGHT:23|CMP||
|RIGHT:24|CMP||
|RIGHT:25|CMP||
|RIGHT:26|DIV3||
|RIGHT:27|MOD3||
|RIGHT:28|OR3||
|RIGHT:29|XOR3||
|RIGHT:2a|AND3||
|RIGHT:2b|SBX3||
|RIGHT:2c|SHL2||
|RIGHT:2d|SAR2||
|RIGHT:2e|||
-4bit変換表
{ R1, 16, 1, 2, 8, 4, R0 }, // OR
{ -1, R1, 1, 2, 3, 4, R0 }, // XOR
{ R1, 15, 1, 7, 3, 5, R0 }, // AND
{ 64, 16, 1, 2, 8, 4, 32 }, // SBX
{ -1, R1, 1, 2, 3, 4, R0 }, // ADD
{ R1, R0, 1, 2, 3, 4, 5 }, // SUB
{ 10, R0, R1, 7, 3, 6, 5 }, // MUL
{ -1, 0, 1, 2, 3, 4, R0 }, // SBR
{ R1, R0, 1, 2, 3, 4, 5 }, // SHL
{ R1, R0, 1, 2, 3, 4, 5 }, // SAR
{ 10, R0, R1, 7, 3, 6, 5 }, // DIV
{ 10, R0, R1, 7, 3, 6, 5 }, // MOD
-8bit変換表
|80~96|0~16|
|97~9f|20, 18, 40, 80, 100, ff, 7f, 3f, 1f|
|a0~ae|R00~R0e|
|af|R3f|
|b0~b7|rep0~rep7|
|b8~bf|-8~-1|
-12bit変換表
|c00~cee|0~ee|
|cef~cf7|200, f0, 400, 800, 1000, 2000, 4000, 8000, 10000|
|cf8~cff|ffff, 7fff, 3fff, 1fff, fff, 7ff, 3ff, 1ff|
|d00~d3f|R00~R3f|
|d40~d4f|reserved|
|d50~d56|reserved|
|d57|reserved|
|d58~d5f|reserved|
|d60~d6f|(この項10進数)128K,256K,512K,...,4G|
|d90~dff|-70~-1|
-これを踏まえて、 2023.04.07 Fri #1 の t0002.c をOSECPU-VMのフロントエンドコードで書いてみます。
LdImm32(R00, 256); // 2 9b 0
ApiWindowReg32(P04, R00, R00); // 5 ?? 5 5 4
for (R02 = 0; R02 < R00; R02++) { // 6 5 2
for (R01 = 0; R01 < R00; R01++) { // 6 a0 1
ApiSetPix(P04, R01, R02, R03); // 5 ?? 5 5 a1 a2 a3
AddSht32(R03, R00); // 94 3 a0
} // 以降は自動補完
}
ApiWaitForExit32();
End();
// 29 b0 5? ?5 54 65 26 a0 15 ?? 55 a1 a2 a3 94 3a : 16バイト
-うーん、小さい!
** 2023.04.18 Tue #3 [easy-C]
-小学2年生に、以下のような課題を与えてみました。
-シート1
(1) → NO1:
(2) → NO2:
(3) → NO3:
Kをかく(すきまあり) → PRIS K;
Kを1ふやす → K=K+1;
(1)へもどる → GO NO1;
-シート2
(1) K=1;
(2) Kをかく(すきまあり)
(3) Kを1ふやす
(4) (2)へもどる
-小学生に与えたミッション:
--[1]シート2は日本語で書かれています。シート1をみながら、プログラムに直して入力し、実行してください。
--[2]それができたら、3 6 9 12 15 18 21...になるように改造してください。
-40分間ほど頑張らせたら、[1]と[2]がちゃんとできました。
-もちろん最初は入力ミスなどがあって失敗しましたが、ちゃんと自分でエラーを見て直せました。
-やったね!
-Q&A
--[Q-1]なぜ変数名としてKを選んだの?最初はIじゃないの?
---[A-1]私もそう思ったのですが、Iは1と見間違いやすいのでやめました。Jでもよかったのですが、Kのほうがローマ字などで使用頻度が高そうかなと思って、これにしました。Aとかでもいいのですが、easy-CだとAは宣言せずに使える変数ではないので、I~Nの中から選びました。
--[Q-2]もっと説明して教えるべきだったのでは?
---[A-2]それだとつまらないかなと思って、シート1にヒントというかカンニング情報を書いて、それを直筆で写させて(そうすればたぶん内容が頭に入る)、写したあとでは私が作ったシート1を隠して、その上でシート2を見せて、プログラムを入力させました。
** 2023.04.19 Wed #1 [easy-C]
-算数の問題を easy-C で解いてみるよシリーズ#1。
--こういう例を見せたら小学生でも、「プログラミングって役に立つなー」って思ってくれるかなあ。それとも試験の時にプログラミングできるわけじゃないから、「結局使えないじゃん」としか思わないかなあ。
-[1]2から10000までの素数を表示してみよう!
HL9>char a[10000]; a[0:<10000] = 0; [i = 2:<10000] { if (a[i] == 0) { pris i; for (j = i; j < 10000; j += i) { a[j] = 1; }}}
//上記を複数行で書いたもの.
char a[10000]; a[0:<10000] = 0;
[i = 2:<10000] {
if (a[i] == 0) {
pris i;
for (j = i; j < 10000; j += i) { a[j] = 1; }
}
}
-[2]整数kを素因数分解してみよう!
HL9>k = 12345; for (i = k; i > 1;) { for (j = 2; i % j != 0; j++) {} pris j; i /= j; }
//上記を複数行で書いたもの.
k = 12345;
for (i = k; i > 1;) {
for (j = 2; i % j != 0; j++) {}
pris j; i /= j;
}
-[3]整数mと整数nの公約数を求めよう!
HL9>m = 123; n = 456; [i = 1:<=m] { if (m % i == 0 && n % i == 0) { pris i; }}
//上記を複数行で書いたもの.
m = 123; n = 456;
[i = 1:<=m] {
if (m % i == 0 && n % i == 0) { pris i; }
}
-[4]整数mと整数nの最小公倍数を求めよう!
HL9>m = 123; n = 456; for (i = m;; i++) { if (i % m == 0 && i % n == 0) break; } pris i;
//上記を複数行で書いたもの.
m = 123; n = 456;
for (i = m;; i++) {
if (i % m == 0 && i % n == 0) break;
}
pris i;
-[5]円周率を求めよう!
HL9>n=0;[100000000]{i=aRnd(10000);j=aRnd(10000);if(i*i+j*j<=10000*10000){n++;}}pr n*4;
//上記を複数行で書いたもの.
n=0;
[100000000] {
i = aRnd(10000); j = aRnd(10000);
if (i*i+j*j <= 10000*10000) {n++;}
}
pr n*4;
-[6]ルート3を求めよう!
HL9>for(i=0;i*i<300000000;i++){}pr i
//上記を複数行で書いたもの.
for (i = 0; i * i < 300000000; i++) {}
pr i;
** 2023.04.19 Wed #2 [easy-C]
-小学2年生に、以下のような課題を与えてみました#2。
-シート3
NO1: PRIS 1;
NO2: PRIS 2;
NO3: GO NO5;
NO4: PRIS 4;
NO5: PRIS 5;
--さてこれを実行したらどんな結果になるでしょうか?
-シート1に追記
もしKが5より大きければ(8)へ。 → IF (K > 5) GO NO8;
-シート4
(1) K=1;
(2) Kをかく(すきまあり)
(3) Kを1ふやす
(4) もしKが100より小さければ(2)へもどる
-小学生に与えたミッション:
--[1]シート3には知っている命令しかありません。これの実行結果を予想して教えてください。
--[2]シート4を実行するとどうなると思いますか?実行結果を予想して教えてください。・・・また、実際に入力して実行してみてください。
--[3](難しい)プログラミングでは掛け算の記号は * です。これを使って、 1 2 4 8 16 32 64 128 256 を表示するプログラムを作れますか?表示は256で止まらなければいけません。
** 2023.04.20 Thu #1 [easy-C]
-最大公約数gcdを簡単に求めるには、こんな感じでやればよさそうです。
int aGcd(int a, int b)
{
while (a > 0 && b > 0) {
if (a > b) { a %= b; } else { b %= a; }
}
if (a == 0) return b;
return a;
}
-分数計算を支援したら、小学生は喜ぶかなあ。
-・・・しかし気軽に計算させる方法がない?・・・分子と分母を AXFnc_i2 で返せばいいのではないか?よしその方針でやってみよう。
** 2023.04.23 Sun #1 [easy-C]
-ちょっとデバッグとかで苦労したけど(通分をミスりました・・・苦笑)、有理数計算ができるようになりました。
-有理数を表す型として ARat を作りました。
HL9>!ARat_print(ARat_sub(ARat_new(5,8), ARat_new(1,8))); // 5/8 - 1/8
1/2
HL9>!ARat_print(ARat_add(ARat_new(7,12), ARat_new(1,12)))
2/3
HL9>!ARat_print(ARat_add(ARat_new(19,9), ARat_new(8,9)))
3/1
HL9>!ARat_print(ARat_add(ARat_new(2,15), ARat_new(4,5)))
14/15
HL9>!ARat_print(ARat_add(ARat_new(3,10), ARat_new(2,15)))
13/30
HL9>
-まあ有理数で計算したくなることなんてめったにないのですが、でも小学生の算数の問題ではよく出てくるので、きっと小学生にとっては夢の機能でしょう。
** 2023.04.23 Sun #2 [easy-C]
-小学2年生に、以下のような課題を与えてみました#3。
「10 9 8 7 6 5 4 3 2 1 0」と表示してね!
-これは IF(K>=0)GO NO2; が書ければいいだけなのですが、この >= は小学生は見慣れない記号なのです。うーん、どうしたらいいかなあ。
-もちろん -1 を使ってもいいのなら、 IF(K>-1)GO NO2; でもいいのですが、負の数なんてもっと小学生には見慣れない数です。
-まずは「10 9 8 7 6 5 4 3 2 1」という課題にして、次に>=を教えて、それで0まで含めるループを書かせるのがいいかなあ・・・。
-これが一番自然かなあ。
** 2023.04.24 Mon #1 [easy-C]
-有理数計算で、 ARat_new(5,8) を 5:/8 と書けたら便利だろうかと考えています。
改造前>!ARat_print(ARat_sub(ARat_new(5,8), ARat_new(1,8)));
改造後>!ARat_print(ARat_sub(5:/8, 1:/8));
-これができるなら演算子もオーバーライドできて、 ARat_print(5:/8 - 1:/8); ってできたらなって思うけど、それはちょっと実現のためにはハードルがあるなあ。
** 2023.04.25 Tue #1 [easy-C]
-昨日、有理数計算で、 5:/8 と書けるようにする改造はすぐにできたのですが、それを簡易ループ演算子と混ぜ書きするとなぜかうまくいかなくてそれで悩みました。
-原因を突き止めてみると、両方を混ぜ書きしたときに増加するソースコードの量が当初の想定を上回ってしまったせいでした。つまりまさかこんなに生産性が高くなる日が来るなんて数か月前は予想していなかったのです!
** 2023.04.25 Tue #2 [easy-C]
-円周率を計算する方法として、 1/1-1/3+1/5-1/7+1/9-1/11+... の値を4倍するという方法があります(収束が遅いので人気のない計算方法ではありますが)。これをdoubleでやるなら、
HL9>!double d=0;for(i=1;i<20;i+=4){j=i+2;d+=4.0/i-4.0/j;}prf d
3.041840
まあこんな感じになります。
-でもこれくらいなら有理数でも計算できます。有理数計算なら丸め誤差は一切ありません。
HL9>!ARat r=0:/1;for(i=1;i<20;i+=4){j=i+2;r=ARat_add(r, ARat_sub(4:/i,4:/j));}ARat_print(r)
44257352:/14549535
-まあこんな計算をして何が楽しいのかっていわれると答えに苦しむのですが、でも有理数の計算がすごく身近になった気がします!
-有理数計算はRubyやPythonにもあるのですが、わざわざそれらを引っ張り出さなくても、(私の場合は常時起動している)HL-9で計算できるようになったというわけです。
** 2023.04.26 Wed #1 [easy-C]
-ARat_addなどと書くのが面倒になってきたので、:+:という演算子を追加しました。追加なので、以前の ARat_add などもそのまま使えます。
HL9>!prr((2:/6 :+: 0:/1))
1:/3
HL9>!prr((5:/8 :-: 1:/8))
1:/2
HL9>!prr((7:/12 :+: 1:/12))
2:/3
HL9>!prr((19:/9 :+: 8:/9))
3:/1
HL9>!prr((2:/15 :+: 4:/5))
14:/15
HL9>!prr((3:/10 :+: 2:/15))
13:/30
HL9>!prr(((1:/3 :+: 1:/3) :+: 1:/3))
1:/1
HL9>!ARat r=0:/1;for(i=1;i<20;i+=4){j=i+2;r=(r :+: (4:/i :-: 4:/j));} prr(r)
44257352:/14549535
HL9>
-書きやすくなっていないかもしれませんが、まあちょっとだけ見やすくなったような気はします!
** 2023.04.27 Thu #1 [easy-C]
-今までは :< や :/ の前後は必ず1トークンにしなければいけないという制限がありました。このために、r=4:/(i+2);とは書けなくて、j=i+2;r=4:/j;と書く必要がありました。
-でもそんなことのために変数を作らなければいけないなんて苦痛です。できるだけ変数を作らずに済むほうがいいです。ということで、頑張ってかっこが使えるように直しました。
HL9>!ARat r=0:/1;for(i=1;i<20;i+=4){r=(r :+: (4:/i :-: 4:/(i+2)));} prr(r)
44257352:/14549535
-前から直したいと思っていたので、直せてうれしいです!
** 2023.04.29 Sat #1 [easy-C]
-今日の大反省。
-easy-Cには pr i; という命令があります。printf("%d", i);の代わりになるものです。
-pr 12; pr 34; pr 56; と書くと「123456」と表示されます。
-一方で、これだと数がくっついて読みにくいので、間にprsp();をいれて、スペースを入れて読み見やすくすれば、
「12 34 56」になります。そして、実際に小学生に教えてみると、こちらの利用頻度がとても高いので、
print-int&spaceということで、prisという命令を作りました。pris 1; pris 2; pris 3; と書くと「1 2 3」と表示されます。
-一方で、これだと数がくっついて読みにくいので、間にprsp();をいれて、スペースを入れて読み見やすくすれば、「12 34 56」になります。
-そして、実際に小学生に教えてみると、こちらの利用頻度がとても高いので、print-int&spaceということで、prisという命令を作りました。
-pris 12; pris 34; pris 56; と書くと「12 34 56」と表示されます。
-でもこれはいい方法ではありませんでした。・・・pr命令の方こそ、数とスペースを表示するべきだったのです。
-そうすると、スペースを入れたくない場合に対応できないよ?となるわけですが、それは本来のprintfをそのまま使わせればいいのです。
-prisとprとでは、文字数的には2文字違うだけですが、小学生の心理的な負担はかなり違うのです。
-prspみたいな命令も作るべきではありませんでした。「そういうことはprintfを使えばできるよ」にすればよかったのです。
-私の反省ポイントは、printfを使わずともあれこれできるようにしようとするあまり、命令がいまいちシンプルになりきれなかったということです。
* こめんと欄
-掲示板をご利用ください。→[[a23_bbs]]