C#2.0時代のゲームプログラミング(50)


monoとは言うまでもなく、.NET Frameworkオープンソースによる実装である。Linux環境下でC#で書いたプログラムを動かしたい時にお世話になる。monoとMicrosoft製の.NET Frameworkとの互換性はしばしば問題となる。


例えばBinaryFormatterでシリアライズされたデータの互換性を確保するか否かである。
.NETではそんな部分まで仕様として定められているわけではないので、すなわち実装依存である。「WindowsシリアライズしたデータをLinux環境下でデシリアライズできない」とキチガイ利便性を重視する人がクレームをつけてくるわけだ。これはmonoの開発者にとって頭の痛い問題である。


先日Yanesdk.NETを64bit環境のFedora5で動作させるテストを行なっていた。しかしVC#2005でbuildされたYanesdk.NETのdllでは実行時に落ちるのだ。mcs(monoのC#コンパイラ)でコンパイルしたYanesdk.NETのdllは正常に動作する。一見するとVC#2005のコード生成のbugのように思えるが、そうではない。


調査してみたところ原因はすぐに特定できた。

    void* p = &n;
    Console.WriteLine("p: {0:x16}", ( (long)p ));

と書いてみたが、VC#2005で生成したコードを64bit環境のmono上で実行すると上位32bitが切り捨てられているのだ。これがbugの原因である。この手のコードを生成する部分(例えばdllを呼び出すときにIntPtrで渡す部分)はすべてうまく動作しない。


monoのソースを見てみたところ、ILのconvの実装が見るからにおかしい。いまごろになってmonoのJITにまだbugが残っているなんてにわかに信じられなかったが、ソースを見れば一目瞭然である。

mono/mini/mini-amd64.c

    case CEE_CONV_U8:
    case CEE_CONV_U:
        /* Clean out the upper word */
        amd64_mov_reg_reg_size (code, ins->dreg, ins->sreg1, 4);
        break;
    case CEE_CONV_I8:
    case CEE_CONV_I:
        amd64_movsxd_reg_reg (code, ins->dreg, ins->sreg1);
        break;

何故conv.uに上位をクリアするコードが必要なのだ?そもそもconv.i8とconv.u8の実装が非対称になるはずが無く、こんなものは見ただけで誤りだとわかるのに…。これを書いたコーダは何故そんなことも気づかないのだ!


monoのJITが誤ったコードを生成していることは間違いない。mcsはたまたまconv.u命令を生成しないからこのbugが露呈しなかったに過ぎない。昨年末にこれをmono公式のほうにこのbugをレポートしておいたものの、このbugは先日リリースされた mono 1.2.3でも直っていなかった。


再現するのにVC#が必要だから無視されたのか、それとも、dllの呼び出しの細かい規約をMicrosoftが定めていないためキチガイ扱いされてrejectされたのかは知らないが、VC#2005でコンパイルしたdllをそのままLinux環境に持って行っても正常に動作しないというのは非常に残念な事実である。


しかし、「残念な事実である」などと言うこと自体が、状況をわきまえないキチガイ発言なのだろうか?冒頭で書いたシリアライズの互換性を確保せよ、という人のように…。