emacsの64bit化を阻むもの
自宅サーバ(NetBSD 64bit)には、32bit Linux emulationでオムロンソフトウェアのWnn7が動作しています。そして、これを自宅サーバ(NetBSD)・開発マシン(Mac mini)・サブマシン(Macbook air)上のemacsから使用しています。この構成だと学習した情報をサーバー側で一元管理できるので、学習データを共有するための同期処理をどうしようかと悩む必要がなくて重宝しているのですが、以前、各マシンのemacsを64bit化しようと目論んだのだけれども、64bit化するとwnn7eggがbackend timeoutというエラーを吐いてしまい断念してしまいました。
デバッグ
まず64bit版 Emacsを"--debug"を付けて立ち上げてbackend timeoutするときのBacktraceを取って、不具合のありそうな関数に当たりをつけて32bit番の動きと違いがないか一個ずつ調べてみました。Elispのデバッグはしたことがかなったので、凄く効率が悪く面倒臭いデバッグ方法になってしまいました。
上記のやり方で調査していくと、 wnn7rpc-get-autolearning-dic関数で違いが出ることがわかりました。
(defun wnn7rpc-get-autolearning-dic (env type)
"Get id of auto learning dictionary on the server.
Return dictionary id + 1 on success, 0 on no dictionary, negate-encoded
error code on faiulure."
(wnn7rpc-call-with-environment env (result)
(comm-format (u u u) (wnn-const JS_GET_AUTOLEARNING_DIC)
env-id type)
(wnn7rpc-get-result
(comm-unpack (u) result)
(print result)
(1+ result))))
上記のソースのように(print result)を付加してresultの値を表示すると32bitと64bitで違いが発生します
;; 32bit Emacsで正常の場合
Wnn: connecting to jserver at foo.rfc2606.invalid.jp(22273)...done
ホスト foo.rfc2606.invalid.jp の Wnn を起動しました
Loading /Users/bar/.eggrc-wnn7...
-1
32
-1
34
Loading /Users/bar/.eggrc-wnn7...done
;; 64bit Emacsでbackend timeoutの場合
Wnn: connecting to jserver at foo.rfc2606.invalid.jp(22273)...done
ホスト foo.rfc2606.invalid.jp の Wnn を起動しました
Loading /Users/bar/.eggrc-wnn7...
4294967295
4294967295
もしかして変数のbit幅の問題?
32bit Emacsでは-1を取り、64bit Emacsでは4294967295を取る。これを16進数に直すと興味深い値が出てきます。
;; 32bit Emacs場合
(format "%x" -1)
1fffffff
(format "%x" 4294967295)
1fffffff
;; 64bit Emacs場合
(format "%x" -1)
3fffffffffffffff
(format "%x" 4294967295)
ffffffff
え〜っ!? 32bit Emacsの変数のbit幅って、29bitだったんですか! 長年Emacsを使用していたけれどしらなかった。しかも64bit Emacsは62bit? なんでこんなに中途半端なbit数なんでしょうね
上記の結果より処理結果が536870911(#x1fffffff)以上の値をとると32bit Emacsと64bit Emacsで評価内容が変わってしまう事がわかりました。
comm-unpack関数って何しているの?
次に、32bitと64bitで異なった値を返してくるcomm-unpack関数を調べてみました。comm-unpack自体は引数の値によって適切な関数を呼び出すディスパッチ処理を行い、呼び出した関数の値を返すだけの簡単な仕事しかしていませんでした。
(comm-unpack (u) result)のように引数に"u"をつけるとcomm-unpack-u32関数が呼ばれます。
comm-unpack-u32関数って何しているの?
EmacsはJServerと直接通信をしてコマンド/レスポンスのやりとりをしている訳ですが、受信したデータは*Wnn7*バッファに入ってきます。
これを適切なbit長で読み出してコマンドを再構築するのが、comm-unpack関数で受信したレスポンスを32bitにアンパックしてする場合にcomm-unpack-u32関数が呼ばれます。
(defun comm-unpack-u32 ()
(progn
(comm-require-process-output 4)
(+ (lsh (comm-following+forward-char) 24)
(lsh (comm-following+forward-char) 16)
(lsh (comm-following+forward-char) 8)
(comm-following+forward-char))))
この時に、0xffffffffというデータを受信していると32bit Emacsと64bit Emacsで結果が変わってしまいます。
では、どんな値が*Wnn7*バッファ入ってきているのでしょう?ダンプしてみました
はい、見事に0xffffffffを受信しています。ありがとうございました。
原因
- 32bit Emacsの整数bit幅は29bit
- 62bit Emacsの整数bit幅は62bit
- jserverから0xffffffffを受信すると
- 32bit Emacsは-1と評価する。
- 62bit Emacsは4294967295と評価する。
- 62bit Emacsの場合、不明なレスポンスしかこないので、backend timeoutで落ちる。
対策
原因が判明したので、comm-unpack-u32関数を修正して整数が29bit幅の以外の時は、変数の値を62bit幅に拡張する処理を入れてあげればよいと思われる。整数のbit幅が他の値をとることはあるのかな?考えないことにしておく。
wnn7egg-edep.el
Emacsの依存性があるコードはここに書かれるようなので、下記のコードを追加
(defconst interger_width_29bit (logxor (lsh (lsh 1 29) -29) 1))
interger_width_29bitは整数が29bit幅ならば1、29bit幅以上ならば0を取ります。
wnn7egg-com.el
comm-unpack-u32関数を下記のように修正。
(defun comm-unpack-u32 ()
(progn
(comm-require-process-output 4)
(if (= interger_width_29bit 1)
(+ (lsh (comm-following+forward-char) 24)
(lsh (comm-following+forward-char) 16)
(lsh (comm-following+forward-char) 8)
(comm-following+forward-char))
;; for 62bit width
(ash (lsh (+ (lsh (comm-following+forward-char) 24)
(lsh (comm-following+forward-char) 16)
(lsh (comm-following+forward-char) 8)
(comm-following+forward-char)) 30) -30))))
変数の値が62bit幅の場合は、31bit目のデータを符号ビットの場所までシフトしてから、算出シフトで元の位置まで戻してあげます。(ソースでは30bitシフトしていますが、33bitシフトの方がよいかもしれません。)
ソースとパッチ
ソースとパッチをおいておきます。ご自由にお使いください。
- wnn7egg-edep.el
- wnn7egg-com.el
- wnn7-egg-backend-timeout.patch