onbc.math.nb: __sin()の修正

ローカル変数は、
関数内の自由な位置で(関数内の先頭側以外の位置でも)宣言できるようにしてあるつもりなのだが、
先頭側以外の位置(宣言以降で命令が登場した以降、つまりスタックが命令処理に使われた以降)
で宣言した場合に、コードは生成できるが、
それをosecpu-askaでビルドしてosecpuで実行した段階でセキュリティーエラーとなってしまうケースがある。

よくあるケースの

int j;
for (j = 0; j < 3; j = j + 1) {
        int i;
        for (i = 0; i < 3; i = i + 1) {

では、int i; の宣言が命令以降でも(スタックが命令に使われて以降でも)セキュリティーエラー等は生じないようだが、
一方、onbc.math.nb の __sin() で、
指数計算のバッファーとして a2, a3, a7, a9 を後ろの方で宣言して使っているが、
これがセキュリティーエラーとなるケースが時々生じてしまう。
対策としては、空の代入命令をあらかじめ行っておく等でもエラーは消せるのだが、
もっと単純に、宣言位置を先頭側にしてしまえば、それでもセキュリティーエラーは消せる。
ので、そのように対策した。

しかし、根本的には、どのような具体的メカニズムで
このようなケースでセキュリティーエラーが生じる場合があるのか?知ることが今後の為にも重要だが、
そこは調べてないので不明。
値によって、通る場合と通らない場合が生じることだけは分かってるが、
これがどのように作用してセキュリティーエラーとなるかのメカニズムは分かってない。
明示的型変換(文脈でのユーザーによるキャスト)を、実際に動作するように修正

明示的なキャストが実際には動作していなかったが、それを動作するように修正。
現状ではキャストできる型の要素は、変数の数値型と、ポインターの深さ、のみ。

配列型を、新たに別の配列型へとキャストすることは、未サポート。
キャストした場合は、(元の変数が配列型だとしても)スカラー型となる。
(すなわち通常の変数参照の場合と同様で、受け渡しの単位はスカラーなので)

別の配列型へのキャストが未サポートな理由は、単純に作者がC言語にあまり詳しくないから。
C言語での、別配列型へのキャストの書き方を知らない。
(int*[10][20])a; などとすれば、
int *a[10][20]; として宣言した場合と同じ変数となるのだろうか?
(たぶん無理な気がする)
実はC言語でのこの辺の文法をよく知らない。(無いのか有るのかも知らない)

もしかしたら、現状での型に対する認識が少し間違えてるのかもしれない。という気もする。
配列の添字も型のユニークさを決定する要素だと考えているが、
もしかしたら、配列の添字は型の要素には含めずに、型とは別の要素として考える必要があるのかもしれない。
というのも、
現状では int *a; に対して a[10] 等と添字によるアクセスが未サポートだが、
これはC言語では本来は可能であるべき。
ということは int* という型と、[1000] 等による領域設定は、別の概念として捉えるべきなのだろうか?
という気もする。
そうすれば、int *a; を a[10] としてアクセスすることに、考え方としては何も問題は無くなる。
現状では
int x[1000]; ならば、型は int[1000] であるし、
int *y; ならば、型は int* として考えている。
すなわち、xとyは別の型として考えている。
(そして、配列に添字無しでアクセスした場合はアドレスを返すという、例外的な動作が設定されている)
しかし、それよりも、もっとシンプルに、
どちらもポインターとして考えてしまえばどうだろうか? という気もしている。
これならば int x[1000] を int *x と考え、 int *y はそのまま int *y と考える。
すなわち、型としてはどちらも同じ int* 型となる。
そして、これら x, y で動作に差をつけなければならない箇所は、宣言時のメモリー確保の量だけである。
すなわち int x[1000] であれば、実際の動作としては、
長さ [1000] の無名のメモリー領域を確保し、int *x はその無名領域の先頭を指すように設定する。
一方、int *y では、そのようなメモリー確保の動作は必要無い。
x, y での動作の違いは宣言時のこのメモリー領域の確保&初期設定のみで、そこ以外でははどちらも
int* という同じ型だし、
また、これならば配列変数を添字無しでアクセスした場合にアドレスが返るという機構も、
極めて自然な帰着となるし、何も例外的な動作を用意する必要など無い!!
また、同様に、ポインターに対して添字によるアクセス y[10] 等も、それは自然な帰着となるし、
何も例外的な動作を用意する必要が無い!!
ということは、
直感的にだが、考え方としてはこちらの方(型に[]を含めない)が正しいような気がする。
(なぜなら、よりCに近い動作を、より単純に実装できるから)

しかしながら問題点もある。
上記の考え方は、配列が1次元配列のみサポートの場合であれば期待どおり動作するはずだが、
一方、多次元配列の場合だと問題が生じる。
多次元配列の場合は、複数次元の添字から実際のアドレスを計算する際に、各次元のサイズが必要となるので、
ポインター y に y[10][20] としても、1次元のサイズが不明なのでアドレスを計算できない。
([2次元][1次元] の場合に [2次元] のアドレスを計算するには、[1次元] のサイズが必要)
すなわち、最も浅い次元のサイズは型に含める必要は無いが、
一方、2次元目以降のサイズは型に含める必要がある。(でなければアドレスを計算できない)

1次元配列の場合に、言うなれば0次元のサイズに相当するのが、char=1byte, int=4byte 等の
基本型のサイズである。
ということは、
2次元配列の場合は、仮に int a[10][20] であれば、4 * 20 を 0次元サイズと考えれば、
すなわち int80 *a; のようなもので、
こう考えれば、多次元配列でもポインターとして考え方を共通化できるのではないか?
という気がしている。

しかし、これだと上手く行くのは2次元配列までで、3次元配列以上だと問題が生じる。
単純に
int a[10][20][30] だとして、int2400 *a としたとする。
a[3] とした場合、a[3][0][0] のアドレスを得られるが、では、
a[3][4] とした場合、どう処理すればいいのか? を考えると、
サイズを乗算して一つにするのは間違いな気がする。(20 * 30 * 4 = 2400 のように)
多次元配列の各次元は、(乗算でまとめずに)それぞれ個々の値のまま持つべきだろう。

すると、結局、型に[]を含めるのを省略できるのは、1次元目の添字だけという結論になった。
次の目標として、配列の扱い方に関する仕組みを、上記のような仕組みへと修正することを考えている。