`RubyVM::ISeq.compile_option` 変更時のカスタムコンパイラオプション同期を修正

rails/bootsnap

BootsnapのISeqキャッシュにおいて、RubyVM::InstructionSequence.compile_option が実行時に変更された際、カスタムコンパイラのオプションが追従しない問題を修正しました。

背景

RubyVM::InstructionSequence.compile_option はRubyのバイトコードコンパイル設定をグローバルに変更できるAPIで、tailcall_optimizationfrozen_string_literal などのオプションを制御します。Bootsnapはコンパイル済みISeqをキャッシュする際にこの設定値をキャッシュキーの一部として利用しており、設定が変わればキャッシュが無効化される仕組みになっています。

しかし、Bootsnapが独自の カスタムコンパイラBootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL など)を使用している場合、コンパイラ初期化時に固定されたオプションを保持するため、後から compile_option がミューテート(変更)されても、カスタムコンパイラ側のオプションが更新されないまま古いキャッシュが使われ続ける可能性がありました。

技術的な変更

Compiler クラスへの update_options メソッド追加と、enable_frozen_string_literal の実装変更という2つの修正が行われています。

lib/bootsnap/compile_cache/iseq.rbCompiler クラスに update_options メソッドが追加されました。このメソッドは、コンストラクタで渡されたオプション(@options)と現在の RubyVM::InstructionSequence.compile_option の関係を評価し、@compile_options を動的に更新します。

変更前:

class Compiler
  attr_reader :namespace, :compile_options

  def initialize(namespace = nil, compile_options = nil)
    @namespace = namespace
    @compile_options = compile_options
  end
end

変更後:

class Compiler
  attr_reader :namespace

  def initialize(namespace = nil, compile_options = nil)
    @namespace = namespace
    @options = compile_options.freeze
    update_options
  end

  def update_options
    @compile_options = if @options.nil? || @options < RubyVM::InstructionSequence.compile_option
      nil
    else
      RubyVM::InstructionSequence.compile_option.merge(@options).freeze
    end
  end
end

update_options の内部では、@options < RubyVM::InstructionSequence.compile_option という比較が鍵になります。これはHashの部分集合チェックで、カスタムオプションが現在のグローバル設定に包含されている(つまり上書き不要)ならば nil(通常のコンパイルパス)を使い、そうでなければグローバル設定にカスタムオプションをマージして使用するという判断です。

lib/bootsnap.rbenable_frozen_string_literal も合わせて修正されました。以前は compiler_selectornil に設定し FROZEN_STRING_LITERAL コンパイラを直接セットするアプローチを取っていましたが、変更後は RubyVM::InstructionSequence.compile_option 自体に frozen_string_literal: true をマージして設定する方式に変わっています。

変更前:

Bootsnap::CompileCache::ISeq.compiler_selector = nil
Bootsnap::CompileCache::ISeq.default_compiler = Bootsnap::CompileCache::ISeq::FROZEN_STRING_LITERAL

変更後:

options = RubyVM::InstructionSequence.compile_option.merge(frozen_string_literal: true)
RubyVM::InstructionSequence.compile_option = options

テストも追加されており、test_key_compile_option_custom_compiler では FROZEN_STRING_LITERAL コンパイラ使用中に compile_option を変更した際、キャッシュキーが正しく変化することを検証しています。

設計判断

カスタムオプションをコンパイラ内部に保持する代わりに、グローバルな compile_option を信頼の源泉(source of truth)として扱う設計に統一されました。

enable_frozen_string_literal の変更がその考え方を明確に示しています。以前はBootsnapの内部コンパイラオブジェクトを差し替えることで frozen_string_literal を有効化していましたが、この方法はグローバルオプションとコンパイラオプションが別々に管理される状態を生み出していました。新しい実装では RubyVM::InstructionSequence.compile_option に直接書き込むことで、Bootstrapのキャッシュキー更新機構(compile_option_updated)が自然に機能するようになります。

update_options メソッドを Compiler インスタンスに持たせることで、compile_option が変更されるたびに各コンパイラが自身のオプションを再計算できる拡張点を設けています。Hashの部分集合演算子(<)を利用した判定は、「グローバル設定が既にカスタムオプションを包含しているなら追加オプション不要」というロジックを簡潔に表現しており、冗長なマージ操作を避ける実用的な選択です。

まとめ

この変更は、Bootsnapのカスタムコンパイラとグローバルな compile_option の同期問題を、内部状態の二重管理を解消することで根本的に修正しています。frozen_string_literal などのオプションを compile_option 経由で統一管理する設計への移行により、実行時に compile_option がミューテートされるケースでもキャッシュの整合性が保たれるようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
342b13b1

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細(各論)→まとめ(結論)という記事構成が明確に守られています。また、任意項目である「設計判断」セクションも含まれており、変更の意図を深く理解できる構成になっています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```ruby:path```)およびGitHubのPRへのリンク記法([PR #551](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Bootsnapの内部実装やRubyのISeqコンパイルオプションに関する内容であり、専門知識を持つエンジニアという対象読者に適合しています。不要な初心者向け解説はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まっています。1段落1トピックの原則が守られ、段落の長さも適切で、非常に読みやすい構造です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックは、提供されたDiff情報と正確に一致しています。変更前後のコードを対比させることで、変更点が明確に示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`RubyVM::InstructionSequence.compile_option`、`ISeqキャッシュ`、`カスタムコンパイラ`などの技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「Hashの部分集合チェック」に関する説明や、`enable_frozen_string_literal`の変更による設計方針の転換についての解説など、技術的な説明は正確で論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべて、PRのタイトル、Description、およびDiff内のコード変更によって裏付けられています。根拠のない推測や創作(ハルシネーション)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#551)が正しく記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「`RubyVM::ISeq.compile_option` 変更時のカスタムコンパイラオプション同期を修正」は、PRのタイトル「Better support `RubyVM::ISeq.compile_option` being mutated」の内容をより具体的に、かつ正確に表現しており、主題と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事に含まれる情報はすべてPRから得られるものであり、PRに記載のないバージョンサポート情報やリリース日程などの外部知識の追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「以前は」「変更後は」といった時間表現が、PRによる変更の前後関係を正確に反映しています。時間表現の歪曲はありません。