diff --git a/CHANGELOG.md b/CHANGELOG.md index 217c7554..a797e2e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +* Only disable the compile cache for source files impacted by [Ruby 3.0.3 [Bug 18250]](https://bugs.ruby-lang.org/issues/18250). + This should keep the performance loss to a minimum. + # 1.9.2 * Disable compile cache if Ruby 3.0.3's ISeq cache bug is detected. diff --git a/lib/bootsnap.rb b/lib/bootsnap.rb index 0109a1ae..06a32e85 100644 --- a/lib/bootsnap.rb +++ b/lib/bootsnap.rb @@ -57,15 +57,9 @@ def self.setup( "If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended." end - if compile_cache_iseq - if iseq_cache_tracing_bug? - warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommended " \ - "to turn `compile_cache_iseq` off on Ruby 2.5" - end - - if iseq_cache_anonymous_params_bug? - warn "Your version of Ruby 3.1.0-dev has a bug that break bytecode caching (https://github.com/ruby/ruby/pull/4961)." - end + if compile_cache_iseq && !iseq_cache_supported? + warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \ + "to turn `compile_cache_iseq` off on Ruby 2.5" end Bootsnap::LoadPathCache.setup( @@ -81,28 +75,11 @@ def self.setup( ) end - def self.iseq_cache_tracing_bug? - return @iseq_cache_tracing_bug if defined? @iseq_cache_tracing_bug + def self.iseq_cache_supported? + return @iseq_cache_supported if defined? @iseq_cache_supported ruby_version = Gem::Version.new(RUBY_VERSION) - @iseq_cache_tracing_bug = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0') - end - - def self.iseq_cache_anonymous_params_bug? - return @iseq_cache_anonymous_params_bug if defined? @iseq_cache_anonymous_params_bug - begin - if defined? RubyVM::InstructionSequence - RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end").to_binary - RubyVM::InstructionSequence.compile("def foo(**); ->{ super }; end").to_binary - end - false - rescue TypeError - true - end - end - - def self.iseq_cache_supported? - !(iseq_cache_tracing_bug? || iseq_cache_anonymous_params_bug?) + @iseq_cache_supported = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0') end def self.default_setup diff --git a/lib/bootsnap/compile_cache/iseq.rb b/lib/bootsnap/compile_cache/iseq.rb index 08c15fe4..e4681beb 100644 --- a/lib/bootsnap/compile_cache/iseq.rb +++ b/lib/bootsnap/compile_cache/iseq.rb @@ -9,10 +9,35 @@ class << self attr_accessor(:cache_dir) end - def self.input_to_storage(_, path) - RubyVM::InstructionSequence.compile_file(path).to_binary - rescue SyntaxError - raise(Uncompilable, 'syntax error') + has_ruby_bug_18250 = begin # https://bugs.ruby-lang.org/issues/18250 + if defined? RubyVM::InstructionSequence + RubyVM::InstructionSequence.compile("def foo(*); ->{ super }; end; def foo(**); ->{ super }; end").to_binary + end + false + rescue TypeError + true + end + + if has_ruby_bug_18250 + def self.input_to_storage(_, path) + iseq = begin + RubyVM::InstructionSequence.compile_file(path) + rescue SyntaxError + raise(Uncompilable, 'syntax error') + end + + begin + iseq.to_binary + rescue TypeError + raise(Uncompilable, 'ruby bug #18250') + end + end + else + def self.input_to_storage(_, path) + RubyVM::InstructionSequence.compile_file(path).to_binary + rescue SyntaxError + raise(Uncompilable, 'syntax error') + end end def self.storage_to_output(binary, _args) diff --git a/test/compile_cache/iseq_cache_test.rb b/test/compile_cache/iseq_cache_test.rb new file mode 100644 index 00000000..59e6d9d6 --- /dev/null +++ b/test/compile_cache/iseq_cache_test.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +require('test_helper') + +class CompileCacheISeqTest < Minitest::Test + include(TmpdirHelper) + + def test_ruby_bug_18250 + Help.set_file('a.rb', 'def foo(*); ->{ super }; end; def foo(**); ->{ super }; end', 100) + Bootsnap::CompileCache::ISeq.fetch('a.rb') + end +end