Javaはバイナリをごりごりやるのに弱い

 プログラミングのジャンルは広いもので、しばらくいじってなかったジャンルを久々に触ると記憶違いとかしてて記憶をリフレッシュさせにゃいかんなぁとか思う今日この頃、みなさまいかがお過ごしでしょうか。僕は風邪で寝込んでました。

 PCエンジンエミュレータ移植の件 - Aoisomeの以下略で、僕は何を勘違いしたかJavaのbyteをunsignedだと記憶違いしてて恥をかいたところ。そういえば誰かもFlexのバイナリをJavaでごりごり扱おうとしたらsignedで苦労したみたいなこと言ってたっけ。

 Javaの仕様を再確認。short、char、byteは演算時に自動的にintにワイドニングして処理される。なので、単純なbyte型変数同士の演算をbyte型に代入するにもキャストが必要になる。

byte a = 1;
byte b = 2;
byte c = (byte)(a + b);

面倒くさいよね。

 この辺、JavaVMの仕様を見ると分かってくる。

There is no direct support for integer arithmetic on values of the byte, short, and char types (§3.11.1), or for values of the boolean type; those operations are handled by instructions operating on type int.

Java SE Specifications

同じ箇所を日本語版の書籍「Java仮想マシン仕様」から引用すると、

byte型、short型、char型の値に対する整数計算(§3.11.1)、およびboolean型の値に対する操作は直接サポートされていない。こういった操作はint型に対して操作を行う命令によって取り扱われる。

とあって、なるほど、VMがintの32bit演算以下をサポートしてないからかー、というか1996年登場の言語にしては思い切りいいなーという感想を持つ。このあたり抽象度の高いVM上で動く言語だからだろうか。確かに日常プログラミングしているなかで困ることはあまりない。電文みたいなのをごりごりやると不便なのはそうなのだけど。

 VMがintに満たない演算をサポートしてないんじゃ、C言語からの移植とか苦労するだろうな。少なくとも16bitとか8bitの演算はVMの命令1つではどうにもならないわけだし、32bit演算に比べてパフォーマンス的にも悪い。VM命令では四則演算はTadd、Tsub、Tmul、Tdivで、Tにはi,l,f,dがある*1。int型、long型、float型、double型だね。この時点で32bit以下をサポートする気のなさが伺えるし、unsignedもサポートする気がないことが伺える。

unsigned charなので、値が255のときに1を足したら0に戻るんです。
M6502は、この動作を前提としたソースになっています。

しかし、Javaにはunsigned char型みたいな、符号無し8bitの型は無いんです。
Javaの場合は、演算に& 0xFFをつけて、0〜255の範囲で循環するようにしてやる必要があります。

レジスタ演算している箇所全部に& 0xFFをつけないとダメなんですよ!
死ぬほどめんどくせー!!

PCエンジンエミュレータ移植の件 - Aoisomeの以下略

みたいなケースでは確かに& 0xFFで8bit演算をエミュレートするのが一番無難か。

 単純な足し算がiadd命令とiand命令になってしまうのがどうにかならないかをちょっと考えてみたのだけど、8bitないし16bitのデータをJavaのint型に格納する際に、下位8bit、16bitに格納するのではなく、上位8bit、16bitに格納したらどうだろう?

 この場合、iadd命令だけでiand命令が不要になるのではないか。isubの場合も同等ではないだろうか。

 でもimulとかidivとかやるときには>>24とか>>16とかでシフトして演算しないといけないから、加減算ばかりのところはともかく、積算・除算が入るとむしろ遅そう。それにiinc命令による定数インクリメントも使えなくなる。iinc命令では符号付の1byteの定数を加算できるが、上位bitにデータがあるとこれが用いれない。全部iadd命令でやらないといけなくなる。

 結局のところ、素直に& 0xFFしてバイトコートでiadd命令とiand命令にしておいたほうが、JITコンパイラとかがなんとかしてくれそうな気がする。トリッキーなことはやらないほうがいいのかもしれない。