macOSでの動的ロード問題まとめ

主な問題

最近のRuby+Xcodeでは以下の4つの問題があるっぽい

  • dynamic_lookupがXcode 14で警告が出る
  • two-level namespace vs. flat namespace
  • 別の拡張ライブラリのシンボルを参照できない
  • disable-sharedなrubyがfat binaryを読めない

dynamic_lookupがXcode 14で警告が出る

https://bugs.ruby-lang.org/issues/19005 Xcode 14にて以下の警告が出て、拡張ライブラリのコンパイルに失敗する。

ld: warning: -undefined dynamic_lookup may not work with chained fixups

Ventura同梱のRubyは警告を無視している: https://ruby.slack.com/archives/C02A3SL0U/p1667539569008269?thread_ts=1663959379.534979&cid=C02A3SL0U 警告を無視する案: https://github.com/nobu/ruby/tree/cc-wrap

chained fixupsが問題になるのは、シンボル参照時に参照先のシンボルがまだ読み込まれていないとき = アプリ起動時で複数のオブジェクトを一気に読むときだけなので、dynamic_lookupをdlopenによる動的ロードでのみ用いるRubyの場合は問題にならない: https://www.emergetools.com/blog/posts/iOS15LaunchTime https://github.com/ruby/ruby/pull/6193

使わなくする場合には bundle_loader で ruby/librubyを指定すればよい → fat binary問題と別の拡張ライブラリ問題

two-level namespace vs. flat namespace

Executing Mach-O Files

  • two level namespaceだと、他のライブラリと名前が衝突しても死ぬことがなくなる
    • その代わり、-undefined dynamic_lookupをつけないと他のライブラリの関数を呼べなくなる
  • flat namespaceだと他のシンボルと衝突

別の拡張ライブラリのシンボルを参照できない

disable-sharedなrubyがfat binaryを読めない

--enable-sharedついてない環境でビルドした拡張ライブラリが--enable-shared付きの環境で動かないよ〜 fat gemで困るよ〜という話が :cry: https://github.com/rake-compiler/rake-compiler-dock/issues/87 https://ruby.slack.com/archives/C02A3SL0U/p1671524923910499

--disable-shared な ruby 実行ファイルには rb_cObject などのシンボルがあるので -bundle_loader でビルドできるが、それでできた .so を --enable-shared な ruby 実行ファイルには rb_cObject などがない(libruby.so にある)ので require に失敗する、みたいな? https://ruby.slack.com/archives/C02A3SL0U/p1671527703349829

そもそもfat binaryは遅い https://ruby.slack.com/archives/C02A3SL0U/p1671533399524569 https://www.clear-code.com/blog/2019/11/22.html https://github.com/ruby/bigdecimal/pull/148

https://bugs.ruby-lang.org/issues/18912

歴史的には様々なパッケージマネージャがバイナリ配布へと舵を切っていたので、fat binaryはやるしかないと思っているが…git

まとめ

解決案としては代替以下の通りになるんじゃないでしょうか

  • dynamic_lookupがXcode 14で警告が出る → 警告を無視する
  • two-level namespace vs. flat namespace → flat namespace
  • 別の拡張ライブラリのシンボルを参照できない → 用途としてはレアなので、そういうgemにはもうちょっと手間をかけてもらってもいい気がする
  • disable-sharedなrubyがfat binaryを読めない → enable sharedのみにする?

あと、前回の記事も考慮すると、

  • 拡張ライブラリ読み込み用の dlopen に RTLD_GLOBAL でなく RTLD_LOCAL を渡す もやっていい気がする

追記

kateiさん曰く

これ思い出して最新のCLT(for Xcode 14.3)で試したら dynamic_lookup 使っても警告でなくなってました。バイナリ見る限りdynamic_lookupを指定するとchained-fixup自体が無効になるっぽいです。

とのこと。hsbtさんも Xcode 14.3 では Apple Silicon なマシンで Ruby 2.4 以降もビルドできるようになっているぽい と書かれており、どうやら解決したっぽい。Python側で大騒ぎしてくれたおかげなようなので感謝…。