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の仮想マシンは作成していたのがあったので、

  1. NetBSD 8.0 RELEASEのバイナリをダウンロード
  2. pkgsrc/pkgtoolspkg_comp1libkverを使ってNetBSD 8.0のchrootな環境を用意
  3. 後々のためにNetBSD 8.2_STABLEに更新して、最終的に3つのpkg_comp1によるchrootを用意
    • NetBSD 8.0 RELEASE
    • NetBSD 8.1_STABLE
    • NetBSD 8.2_STABLE

問題の確認

追試すると、確かにbulk buildと同じ現象で作成できない問題を確認できました。

  1. ruby30-base: ext/-test-/cxxanyargs/cxxanyargs.cppのコンパイルで謎のエラー
  2. 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で定義されているプリプロセッサーのマクロで、いくつもの条件で定義が異なるので、どれが使用されているかすぐにはわかりませんでした(辛い)。仕方がないので、

  1. cxxanyargs.cppをプリプロセッサを通した結果をきちんとコンパイルできる環境とそうでない環境を比較
  2. static_assert.hに適当にコメントを加えて使用しているマクロを判定

といったベタな手段で確認していくと、

  • うまくいっているNetBSD 9ではRBIMPL_STATIC_ASSERT0static_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.odtrace -G的な処置をすれば済んだのかもしれません。

戻る

コメントを追加