Javaの引数の型によるオーバーロード(追記あり)

JavaVMの仕様書と最近ずっとお友達です。今なら「一週間で出来る!JavaVMとJavacのつくり方!」なんつー本がかけますよ(1週間って、ここ2ヶ月毎週言ってないか(^^;)?)
で。
とりあえず判ったこととして「JavaVM仕様書」の「Javaは強く型付けされた言語である」ってのは、嘘は言ってないけどJavaVMについてそう書くのは違うんじゃないかなーって感じ。

  • その1
class Hoge
{
  static void hoge(Object anObject)
    {
  //その1
    }

  static void hoge(String aString)
    {
  //その2
    }
  
  public static void main(String[] args)
    {
      Object anObject;
      anObject=args[0];
      hoge(anObject);
    }
}

ええもう、当然のように「その2」の側が実行されます。なぜかというと、JavaVMの「局所変数」には型がないから。前半intだったはずの局所変数が途中からObjectになるなんてのも簡単に実現できます。局所変数ブロックスコープで、なおかつ「同時に最大使用される分」をコンパイルしようとする傾向があるので。


でも、メソッドとフィールドには型シグネチャが着いてます。型シグネチャがないとオーバーロードできませんから。とはいえ、実装系によって「floatになりがち」な実装系と(Dalvikはそう)「intになりがちな実装系」(HotSpotはそう)があったりして、シグネチャも当てにならないのですが、「シグネチャを持ってるはずのフィールド」でも、動的な型の側が優先されます。

  • その2
class Hoge
{
  static Object m_Object;

  static void hoge(Object anObject)
    {
  //その1
    }

  static void hoge(String aString)
    {
  //その2
    }

  public static void main(String[] args)
    {
      m_Object=args[0];
      hoge(m_Object);
    }
}

m_Objectはjava.lang.Objectなのは明らかなのに、実行時の型に引きずられるんですね。というわけで、HotSpotJavaVMのメソッドのinvokeが妙に速いのは別に静的型で最適化してるわけではないようです。FlashのAVMと比べるとその差は歴然なんだけど。


ちなみに、「java.lang.ほげほげ」は実は組み込み型じゃなくて後付なので(なので、JavaCのバージョンによってStringリテラルの実装方法が違う! これはちょっとびっくり)、Classfileをいじると本気で動的言語が作れます。言語組み込みでバイトコード依存なのは配列だけかな? 配列はjava.lang.Object派生の型のくせに、クラスを持っていません。ヴェリファイヤですらも通しちゃう(っていうか、スタックとローカル、ジャンプ先は見てるけど、たぶん型見てない)のは豪快ですね。
とはいえ、thisContextないので、Smalltalkは作りづらそう。
それにしても、longとdoubleはびみょーに誤魔化されてるなぁ。こんなスタック(たとえばi2dでスタックポインタが変わらない)変化の訳ないんだけどなー。localとコンスタントプールは32bitx2で扱ってるの(コンスタントプールなんか、不定長のくせにlongとdoubleが入るとIDが1ずれる)に、スタックは「64bitでもいい」ような作りに見える。dup2xとかどうするんだろう?
謎が多い今日この頃です。
さあ、AOTでやれることはおおむねやり尽くしたので、次はJITかな。text領域以外をマシン語のコードが動くのはお行儀悪いけど、x86は最近まで無視してたからなー。ARMもMMUのページに実行ビットないっぽいし。


(追記)
staticつけないと動かない、というか、動かすと「その1」の側が実行されます。
これは、

   new     #2; //class java/lang/String
   dup;
   astore #1; 
   invokestatic    #5; //Method fga:(Ljava/lang/Object;)V

と、「invokestatic」が「コンパイル時に決まっているから」呼び出し先が固定されます。でも、コンパイルされたあとのコードにはデータのシグネチャが残らない、ということを言いたかったのが、サンプルが間違っているせいで言いたいことは伝わらないわとひどいことになってます。
言語仕様的には「コンパイル時に型情報を元にオーバーロード先を決める」のが正しい挙動です。念のため。Javaのメソッドは「メソッド名」と「シグネチャ」でワンセットで、それにバイトコードが付随している作りですので、コンパイル時に確定しない型のメソッドは呼び出せません(これを実現するためにinterfaceがある)。
が、当の型は「フィールド」や「ローカル」や「オペランドスタック」には依存しません、ということを言いたかったのです。
Java言語では当然

String hoge =new Vector();

なんてのは全然ダメですが、

   new     #2; //class java/lang/Vector
   dup;
   astore #1; 
   invokestatic    #5; //Method fga:(Ljava/lang/String;)V

とか、

  • フィールド版
   new     #2; //class java/lang/Vector
   dup
   putstatic       #4; //Field m_Object:Ljava/lang/String;
   getstatic       #4; //Field m_Object:Ljava/lang/String;
   invokestatic    #5; //Method fga:(Ljava/lang/String;)V

は、なにげに動いちゃうんだぜー、ということを言いたかったのでした。
判りづらくてごめんなさい。ちなみに、Java言語を使ってると「確かに強い型付けだよなー」とは思います。