* lbstk00の説明 -(by [[K]], 2013.03.28) ** lbstk00とは? -簡単に言うと、OSECPUのアセンブラを支援するためのツールで、これがあると煩雑なラベル管理を肩代わりしてくれます。 --x86などの一般的なCPUの開発ツールでいうと、リンカに相当すると思われます。 -語源はlabel-stackです。 -C言語の標準ライブラリだけで書いた場合は150行くらいの非常に簡単なものです。できればOSECPUに移植したいと思っています。 -最初のバージョンはosecpu010aにバンドルされています。 ** きっかけ -OSECPUでは関数を呼ぶときに関数呼び出しの命令があるわけではなく、ただ目的の関数へJMPしています。しかしそれでは当然戻ってこられません。ということで、JMPの直前に、P1Eレジスタに「終わったらここへJMPしてください」というアドレスを格納する命令を置きます。 PLIMM(P1E, 4); JMP(12); LB0(4); --この例では、LB0(12);の関数を呼んでいます。 -もしこの関数を連続で2度呼ぶとしたら、こうなります。 PLIMM(P1E, 4); JMP(12); LB0(4); PLIMM(P1E, 5); JMP(12); LB0(5); -つまり関数呼び出しのたびにラベルを作る必要があって、しかもその番号は重複してはいけないのです。このラベル番号の管理はプログラムが長くなってくると非常に面倒になってきます。 -このような仕様なのには理由があります。OSECPUはラベルのあるところ以外には絶対に分岐できないようになっています。どこでも好きなところに分岐できてしまう普通のCPUとは違います。好きなところに分岐できてしまうことはセキュリティ的に危険ですし(だからx86にはコールゲートなどの仕組みを使って、分岐先を制限する仕組みを持っているわけです)、プログラム上のどこで他から処理が合流するか事前に分かるこの仕様はJITコンパイラにとっては有利です。 -それにこのような仕様だからこそ、OSECPUでは分岐命令はJMP命令(=P00への代入)だけで済んでいて、JITコンパイラがシンプルになっています。 -それならosecpu.exeがもっとがんばってうまく処理すればいいではないかという指摘もあるかと思います(CALLの直後に自動でラベルを発行するとか)。しかしそれはosecpu.exeが複雑になってしまいます。osecpu.exeは移植しやすい規模を維持するべきです。・・・とまあそんなわけで、この問題を解決するためのツールをosecpu.exeの外に作ることになりました。 ** 仕組み -上記の関数呼び出しを次のように書くことにしました。 lbstk1(2); PLIMM(P1E, lbstk2()); JMP(12); LB0(lbstk2()); -これは次のような意味を持ちます。 --lbstk1(2); : 新規にラベル用の番号を一つ発行し、その番号を2回スタックにつみます。 ---2回つむのは、このラベル番号を2回参照するからです。 --lbstk2() : スタックからラベル番号を一つ取ってきます。 --この命令自身は、ラベルの発行を促す以上の意味を持たないので、出力ソースには現れません(消滅します)。 --lbstk2() : スタックからラベル番号を一つ取ってきます。この記述はとってきたラベル番号と置換されて出力されます。 -このような機構により、以下のように何度書いても問題なくなりました。 lbstk1(2); PLIMM(P1E, lbstk2()); JMP(12); LB0(lbstk2()); lbstk1(2); PLIMM(P1E, lbstk2()); JMP(12); LB0(lbstk2()); -このスタック操作は、ソースの書き換えの際にラベル番号を求めるために仮想的に行うものであって、アプリの実行時に行うわけではありません。 ** 応用 -osecpu_asm.hには以下のような記述があります。 #define LOOPSTART(n) lbstk1(n); LB0(lbstk2()) #define CONTINUE lbstk2() -これがあると以下のような表記が可能になります。 #define sum R00 #define i R01 #define const1 R30 #define const10001 R31 LIMM(const1, 1); LIMM(const10001, 10001); LIMM(i, 0); CP(sum, i); LOOPSTART(2); /* CONTINUEを使う回数+1を書く */ ADD2(sum, i); ADD2(i, const1); CMPJNE(i, const10001, CONTINUE); -このようにループを書く際にラベル番号を気にしないで済みます。またこのループは入れ子にしていても問題なく機能します。 ** 発展 -他にlbstk0(n);とlbstk3(n);とlbstk4(i)という表記をサポートしています。これは関数内でネストしないローカルなラベルにうまくラベル番号を振るためのものです。 -まずlbstk0(n); はlbstk00.exeが発行するラベル番号の開始番号を指定するためのものです。この番号よりも小さな数字でグローバルラベルを定義します。グローバルラベルというのは、関数の外からも参照可能なラベル番号のことです(publicな関数の呼び出しポイントなど)。 -lbstk3(n);は関数の先頭で宣言されるべきもので、関数内でいくつのローカルラベルを使うかを規定します。このローカルラベルには、先のlbstk1(n);で発行する分を含めません。その分についてはlbstk00.exeが自動で数えてうまくやってくれます。 -lbstk4(i)はi番目のローカルラベル番号を返します。iはlbstk3(n);で指定したnよりも小さくなければいけません。0から(n-1)が有効です。 -これらについてはosecpu_asm.hで以下のように整備されています。 #define GLOBALLABELS(n) lbstk0(n) #define LOCALLABELS(n) lbstk3(n) #define LOCAL(i) lbstk4(i) ** こめんと欄 -このページにこめんと欄はありません。このページの内容にコメントしたいときは[[impressions]]にお願いします。