* printfの%d的なAPIについて
-(by [[K]], 2013.07.22)
** (0) はじめに
-OSECPUは[[page0040]]で、もはや病的なほどにサイズにこだわっているのに、しかしまだprintfの%dみたいなものがなく、[[page0044]]の(4)ではputDecなどという関数まで作っています。
-しかし数字の表示なんて実に典型的でありふれた処理です。こんなに使用頻度の高いものをAPIとして提供しないのはなぜでしょう。それは[[page0040]]の考え方とは矛盾しているのではないでしょうか?
** (1) Kは10進数が嫌い
-実は僕は10進数が嫌いです。こんな進数は人間の指がたまたま10本だったからで、それにはさしたる必然性がないのです。今、社会で10進数が標準的に使われているのは、単に歴史的経緯によってそうしているだけです。QWERTYキーボードと同じです。
-こんなくだらないものはいつか廃(すた)れると思います。だからそんなものをAPIに入れたくはありません。まあいつかといっても1000年後くらいになるかもしれないですが・・・。
-僕は2進数や4進数や16進数が好きです。これらの進数には必然性があります。特に16進数はもっとも便利な進数だと信じています。

~
-こういう妙なこだわりがあるので、なかなか%d的なものをAPIに入れることができませんでした。
** (2) 言い訳でしかない
-しかしこれは実は言い訳でしかありません。というのは、上記論法によれば%dがない理由は説明できますが、OSECPUには%x的なものもないからです。

~
-ということで、ver.0.62からは%d的な機能をAPIでサポートします!・・・しました。
-その結果、[[page0054]]の(4)の[4-1]のようなプログラムが34バイトで書けるようになりました。やったね!

** (3) 隠れた問題・・・それは可変長引数問題
-%d的なものを実装しようとすると、関数の可変長引数がほしくなります。ここではその話を書こうと思います。

~
-まずOSECPUは本質的には可変長引数をサポートしていません。引数はレジスタ渡しで、しかも使えるレジスタはR30~R3Bまでです。P31~P3Bもありますが、とにかく整数を渡そうと思ったら12個までしか渡せません。
-これはいいのか、・・・一見するとよくない仕様のようにも見えます。GCCなどでは基本的にはスタック渡しで、引数が少ないときのみレジスタ渡し、みたいな感じになっています。・・・僕もこの仕様を決めるまでには相当に悩みました。

-僕の考えはこうです。
--大多数の関数は固定長引数で記述できる。
--あらかじめ引数の型と順番が分かっていれば、それらを短い機械語に割り当てることが可能になる(実際、OSECPUのAPI呼び出し用バイトコードは驚異的に短いです)。
--固定長であれば、レジスタ渡しの方が(特にOSECPUでは)効率がいい。
-ということで、まずは固定長引数関数をメインでサポートしました。

~
-一方で、可変長引数のサポートも必要です。これをどうしたらいいのかを考えました。そして出た結論は、「配列のポインタとサイズを渡す」モードを作ることでした。・・・つまりこのタイプの関数は、P3xとR3xを一つずつ受け取けとる固定長引数の関数なのです。もちろんその引数の他にも、追加の固定長の引数をR3xやP3xで受け取ることができます。
-一方で、可変長引数のサポートも必要です。これをどうしたらいいのかを考えました。そして出た結論は、「配列のポインタとサイズを渡す」方法を作ることでした。・・・つまりこのタイプの関数は、P3xとR3xを一つずつ受け取けとる固定長引数の関数なのです。もちろんその引数の他にも、追加の固定長の引数をR3xやP3xで受け取ることができます。
-OSECPUでは配列を引数として渡したいときに、関数の引数リストの中に配列の要素も一緒に並べてしまうことができます。というか、文字列表示の
 5 1 0 5 68 65 6C 6C 6F
-などという記述はまさにこれを使っています。68 65 6C 6C 6F という5バイトの配列をスタック上に作って、それのポインタがP31に入り、これの文字長5がR31に入って、それで呼び出されているだけなのです。
-ここでは定数ばかりを配列に代入していますが、もちろんレジスタの値も入れられます。その自由度は普通の引数と何ら変わりありません。
-ここまでをまとめると、OSECPUでは可変長引数を本質的にはサポートしない代わりに、配列を引数に渡すときにその内容をインライン展開して記述できるようにしたのです。おかげで実質上は可変長引数があるのと同じになりました。

~
-配列を渡すときに、インライン展開ではなくて、すでに作った配列のポインタと長さを渡したいこともあるでしょう。また、定数ばかりの配列なのか、変数も混ざっているのかで、バイトコードのエンコード方式も変えたいと思いました。
-ということで、配列を渡すときにはまずモード番号を記述します。上記の例はモード0を指定しています。・・・モード1なら、配列の値として負の値やレジスタの値も指定できます。モード2なら、既存の配列のポインタと長さを指定できます。モード3なら長さ指定をする必要はなく、終端コード(この場合は0)で終わるだけでいいです。
--このモード3を指定していたとしても、結局長さはJITコンパイル時には決定できるので(モード3は配列のインライン展開モードなので)、関数に渡る配列長はただの定数です。つまり実行時にstrlen的なことをしているわけではありませんし、スタック内のデータに終端コードは書き込まれていません。
-他にもモードはありますが、まあきりがないのでこれくらいにしておきます。

-このモード方式に到達するまでも、いろいろ悩みました。モード番号を書けば、それだけで0.5バイトが必要になります。モード番号など書かずにもっとも汎用性のあるモードを一つ作って、それだけを使わせるほうがいいのではないかとも思いました。そうすれば0.5バイトは節約できますし、JITコンパイラも単純化できます。
-しかし結局は、モード番号制にしました。今ではこれが一番効率が良いと確信しています。

~
-この節の冒頭で、%dのためには可変長引数が必要だとか書きましたが、まあこんなわけで、可変長引数的なものはすでに完成していたのです。だから実はそんなことは問題ではないのです。
-しかし・・・%d的なことをしようと思ったら、今の文字列に加えて他に可変長引数が必要です。文字列の後に足してもいいですが、文字列はUInt8の配列でしかないので、整数変数32ビットを入れられません。8ビットずつ区切って無理やり入れますか?それはうまい方法と言えるのでしょうか?これが悩ましいのです。

-それでどうしたのかと言えば、配列引数を2つとれるようにしました。まあ仕様上は3つでも4つでもいいのですが、現状の実装では2つまでをサポートしています。文字列はUInt8の配列ですが、残りの引数の可変長引数はSInt32の配列です。ここに表示したい値を入れるのはもちろんのこと、%4dの4の部分の数字や、進法指定、+や0などのフラグ指定などを全部入れました。こうすることで、関数側は文字列の中に埋まった数値をパースしてデコードする必要がありません。また文字列の中で10を指定すると2バイトも食いますが、SInt32の配列の中なら1バイトで済みます。つまりこの仕様はアプリの実行ファイルを短くする役にも立つわけです。

-今回は%d的なものしか配慮していませんが、将来は%fや%sや%pのサポートも必要になると思います。その時は、UInt8, SInt32, Flt64, UInt8, VPtrの5配列版を作ろうと思っています。こうやってAPIが増えていくのはちょっと良くないと思っているので、次に追加するときまでにはヌル配列指定を可能にして、APIの乱発を食い止めたいと思います。
--整数の表示は利用頻度が高そうで、そうであれば汎用的なものひとつだけではなく専用的なものがあったほうがいいのではないかと思うので、これはこれでいいと思っています。・・・まあjunkApiなのでそもそもそんなに気にしなくてもいいかもしれませんが。

* こめんと欄
#comment

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS