Ruby 3.0 and 3.1 on NetBSD 8
(コメント: 0)
長らく放置していましたが、突然少し書いてみます。
pkgsrcのpkgsrc-2022Q1ブランチをNetBSD 8.0でのbulk buildでruby30-baseとruby31-baseが壊れているけど直せるか?
と、個人宛にメールをいただきました。確かに5月3日のレポートを見ると壊してる犯人のトップ2を占めていました。
少しだけ補足します。
- pkgsrc: NetBSDのパッケージシステムだけど、他のOSもサポート
- pkgsrc-2022Q1: pkgsrcは年に4回リリースに相当するブランチを作成して、次のブランチを作成するまで「安定版」的な位置付けで保守します。pkgsrc-2022Q1は2022年3月29日(UTC)に作成された最新のブランチです。
- bulk build: pkgsrcのパッケージを定期的に全部作成して壊れてるものを確認、バイナリパッケージも作成
NetBSDの最新のリリースのバージョンは9.2で、バージョン8の系列は最新の1つ前なのでサポートされているリリースです。でも、8の系列の最新版は8.2が最新リリースなのに何で8.0、と一瞬思いましたが、連休を利用して調べてみました。
必ずしも以下に書いた順番で話を進めたわけではありません。
環境の整備
幸いなことに、NetBSD 8.1_STABLEの仮想マシンは作成していたのがあったので、
- NetBSD 8.0 RELEASEのバイナリをダウンロード
pkgsrc/pkgtools
のpkg_comp1
とlibkver
を使ってNetBSD 8.0のchrootな環境を用意- 後々のためにNetBSD 8.2_STABLEに更新して、最終的に3つのpkg_comp1によるchrootを用意
- NetBSD 8.0 RELEASE
- NetBSD 8.1_STABLE
- NetBSD 8.2_STABLE
問題の確認
追試すると、確かにbulk buildと同じ現象で作成できない問題を確認できました。
ruby30-base
:ext/-test-/cxxanyargs/cxxanyargs.cpp
のコンパイルで謎のエラーruby31-base
: mini-rubyのリンク時に未定義のシンボルの参照でエラー
いずれもブランチではない最新のpkgsrcのツリーでも変わらない(ちょっとRubyのバージョンを上げたからといって直るものではなない)ことは確かそうな感触でした。
改めて確認すると、1.の問題はNetBSD 8.1_STABLEでは起きなません! 2.の問題はNetBSD 9まで来るとやはり起きません、そもそもNetBSD 9で起きるくらいなら、とっとと気が付いていたはずです。
未定義シンボルの問題
Ruby 3.1だけの問題と思っていたので、こちらから着手しました。具体的なエラーは次の様になります。
linking miniruby yjit.o: In function `full_cfunc_return': yjit.c:(.text+0xae6): undefined reference to `__dtraceenabled_ruby___cmethod__return' yjit.c:(.text+0xbb4): undefined reference to `__dtrace_ruby___cmethod__return' *** Error code 1
これはうまくいっているNetBSD 9で未定義シンボルは本来はどこで定義されているのか確認して、NetBSD 8.0の環境と比較すれば、自ずとわかるはずです。うまくいっているNetBSD 9で出来上がっているオブジェクトファイルにnm(1)を実行して、どこで定義されているべきかはすぐに判明しました。答えはvm.o
で、その一部を抜粋します。
0000000000000000 A __dtrace_ruby___array__create 0000000000000000 A __dtrace_ruby___cmethod__entry 0000000000000000 A __dtrace_ruby___cmethod__return 0000000000000000 A __dtrace_ruby___hash__create 0000000000000000 A __dtrace_ruby___method__entry 0000000000000000 A __dtrace_ruby___method__return 0000000000000000 A __dtraceenabled_ruby___array__create 0000000000000000 A __dtraceenabled_ruby___cmethod__entry 0000000000000000 A __dtraceenabled_ruby___cmethod__return 0000000000000000 A __dtraceenabled_ruby___hash__create 0000000000000000 A __dtraceenabled_ruby___method__entry 0000000000000000 A __dtraceenabled_ruby___method__return
一方、NetBSD 8上では以下の様になりました。
0000000000000000 A __dtrace_ruby___array-create 0000000000000000 A __dtrace_ruby___cmethod-entry 0000000000000000 A __dtrace_ruby___cmethod-return 0000000000000000 A __dtrace_ruby___hash-create 0000000000000000 A __dtrace_ruby___method-entry 0000000000000000 A __dtrace_ruby___method-return 0000000000000000 A __dtraceenabled_ruby___array-create 0000000000000000 A __dtraceenabled_ruby___cmethod-entry 0000000000000000 A __dtraceenabled_ruby___cmethod-return 0000000000000000 A __dtraceenabled_ruby___hash-create 0000000000000000 A __dtraceenabled_ruby___method-entry 0000000000000000 A __dtraceenabled_ruby___method-return
これでは未定義のエラーとなって当然と思いました。ここで、
% rm vm.o % make vm.o
とか実行してみると、シンボルのタイプがA
の内容がそもそも存在していません。
作成されているMakefileの内容を確認すると、どうやらRuby 3.1ではdtrace -G
といった魔法でオブジェクトファイルを後から書き換えているようですが、これはDTraceのサポートを有効にした場合に行うようです。
そこで、取り敢えずruby31-baseは(NetBSD 8.2_STABLEを含めた)NetBSD 8ではDtraceのサポートを無効にして対処しました。
すると、途中でエラーとなっていたために見えていなかったコンパイルエラーの問題はruby31-baseでも発生したのでした!
コンパイルエラーの問題
こちらは具体的には、次の様なエラーでした。
compiling cxxanyargs.cpp In file included from ../../.././include/ruby/internal/value.h:23:0, from ../../.././include/ruby/internal/intern/class.h:24, from ../../.././include/ruby/internal/anyargs.h:76, from ../../.././include/ruby/ruby.h:24, from cxxanyargs.cpp:1: ../../.././include/ruby/internal/static_assert.h:70:26: error: expected constructor, destructor, or type conversion before '(' token RBIMPL_STATIC_ASSERT0(expr, # name ": " # expr) ^ ../../.././include/ruby/internal/value.h:62:1: note: in expansion of macro 'RBIMPL_STATIC_ASSERT' RBIMPL_STATIC_ASSERT(sizeof_int, SIZEOF_INT == sizeof(int));
RBIMPL_STATIC_ASSERT0
というのはRubyのソースコードではinclude/ruby/internal/static_assert.h
で定義されているプリプロセッサーのマクロで、いくつもの条件で定義が異なるので、どれが使用されているかすぐにはわかりませんでした(辛い)。仕方がないので、
cxxanyargs.cpp
をプリプロセッサを通した結果をきちんとコンパイルできる環境とそうでない環境を比較static_assert.h
に適当にコメントを加えて使用しているマクロを判定
といったベタな手段で確認していくと、
- うまくいっているNetBSD 9では
RBIMPL_STATIC_ASSERT0
はstatic_assert
と定義されている。 - うまくいっていないNetBSD 8では
RBIMPL_STATIC_ASSERT0
は_Static_assert
と定義されている。 - static_assertはシステムの
<assert.h>
で定義されている。
まさか、と思ってNetBSDのsrc/include/assert.h
のコミットログを確認すると、NetBSD 8.0と8.1の間で変更されていました。該当箇所をcvsweb.netbsd.orgから引用します。
Revision 1.22.6.1 / (download) - annotate - [select for diffs], Wed May 29 16:00:02 2019 UTC (2 years, 11 months ago) by martin Branch: netbsd-8 CVS Tags: netbsd-8-2-RELEASE, netbsd-8-1-RELEASE Changes since 1.22: +2 -2 lines Diff to previous 1.22 (colored) next main 1.23 (colored) Pull up following revision(s) (requested by maya in ticket #1275): include/assert.h: revision 1.23 Limit static_assert visibility to C11. The existing definition caused issues as GCC only provides _Static_assert when building C11 code. This follows the C standard: static_assert available since C11. Fixes https://rt.perl.org/Public/Bug/Display.html?id=134023
仕方がないので、これはNetBSD 8.0ではinclude/ruby/internal/static_assert.hで敢えてstatic_assertを使用しない様に、かなりやっつけな方法で対処しました。
DTrace問題の謎
ここまで作業を終えて、さらに変更点をpkgsrc-2022Q1に反映する依頼も済ませました。
さて、ここまで来たところでruby30-baseではDTraceのサポートは特に無効にしていないので、どうなっているか確認したところ、vm.o
をnm(1)で確認した結果は同じ様にNetBSD 9とは差異がありました。
ということは未定義シンボルを参照している側、yjit.o
にdtrace -G
的な処置をすれば済んだのかもしれません。
コメントを追加