Skip to content

Commit

Permalink
[GR-18163] Emit performance warning on redefining optimised core methods
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/4245
  • Loading branch information
andrykonchin committed Apr 24, 2024
2 parents ee6ef62 + 9271204 commit 1ee958f
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Compatibility:
* Fix evaluation order for multi-assignment and evaluate left-hand-side before right-hand-side (@andrykonchin).
* Add `Regexp.linear_time?` method (#3039, @andrykonchin).
* Allow null encoding pointer in `rb_enc_interned_str_cstr` (@thomasmarshall).
* Allow anonymous memberless Struct (@simonlevasseur).

Performance:
* Fix inline caching for Regexp creation from Strings (#3492, @andrykonchin, @eregon).
Expand Down
28 changes: 28 additions & 0 deletions spec/ruby/core/warning/performance_warning_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative '../../spec_helper'


describe "Performance warnings" do
guard -> { ruby_version_is("3.4") || RUBY_ENGINE == "truffleruby" } do
# Optimising Integer, Float or Symbol methods is kind of implementation detail
# but multiple implementations do so. So it seems reasonable to have a test case
# for at least one such common method.
# See https://bugs.ruby-lang.org/issues/20429
context "when redefined optimised methods" do
it "emits performance warning for redefining Integer#+" do
code = <<~CODE
Warning[:performance] = true
class Integer
ORIG_METHOD = instance_method(:+)
def +(...)
ORIG_METHOD.bind(self).call(...)
end
end
CODE

ruby_exe(code, args: "2>&1").should.include?("warning: Redefining 'Integer#+' disables interpreter and JIT optimizations")
end
end
end
end
1 change: 1 addition & 0 deletions spec/tags/core/warning/performance_warning_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
slow:Performance warnings when redefined optimised methods emits performance warning for redefining Integer#+
2 changes: 2 additions & 0 deletions spec/tags/truffle/redefining_optimized_core_methods_tags.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
slow:Redefining optimized core methods emits performance warning for redefining core classes methods
slow:Prepending a module into a class with optimised methods emits performance warning
247 changes: 247 additions & 0 deletions spec/truffle/redefining_optimized_core_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. This
# code is released under a tri EPL/GPL/LGPL license. You can use it,
# redistribute it and/or modify it under the terms of the:
#
# Eclipse Public License version 2.0, or
# GNU General Public License version 2, or
# GNU Lesser General Public License version 2.1.

require_relative '../ruby/spec_helper'

# Similar MRI tests are in test/mri/tests/ruby/test_optimization.rb and spec/ruby/core/warning/performance_warning_spec.rb
describe "Redefining optimized core methods" do
it "emits performance warning for redefining core classes methods" do
# Only Integer, Float, NilClass and Symbol classes have optimised methods.
# They are listed in org.truffleruby.core.inlined.CoreMethodAssumptions.

prolog = "Warning[:performance] = true"

code_for_integer_class = <<~CODE
class Integer
ORIG_METHOD_PLUS = instance_method(:+)
def +(...)
ORIG_METHOD_PLUS.bind(self).call(...)
end
ORIG_METHOD_MINUS = instance_method(:-)
def -(...)
ORIG_METHOD_MINUS.bind(self).call(...)
end
ORIG_METHOD_UMINUS = instance_method(:-@)
def -@(...)
ORIG_METHOD_UMINUS.bind(self).call(...)
end
ORIG_METHOD_MULT = instance_method(:*)
def *(...)
ORIG_METHOD_MULT.bind(self).call(...)
end
ORIG_METHOD_DIVISION = instance_method(:/)
def /(...)
ORIG_METHOD_DIVISION.bind(self).call(...)
end
ORIG_METHOD_MODULO = instance_method(:%)
def %(...)
ORIG_METHOD_MODULO.bind(self).call(...)
end
ORIG_METHOD_COMPARE = instance_method(:<=>)
def <=>(...)
ORIG_METHOD_COMPARE.bind(self).call(...)
end
ORIG_METHOD_SHIFTRIGHT = instance_method(:>>)
def >>(...)
ORIG_METHOD_SHIFTRIGHT.bind(self).call(...)
end
ORIG_METHOD_SHIFTLEFTL = instance_method(:<<)
def <<(...)
ORIG_METHOD_SHIFTLEFTL.bind(self).call(...)
end
ORIG_METHOD_OR = instance_method(:|)
def |(...)
ORIG_METHOD_OR.bind(self).call(...)
end
ORIG_METHOD_AND = instance_method(:&)
def &(...)
ORIG_METHOD_AND.bind(self).call(...)
end
ORIG_METHOD_CASECOMPARE = instance_method(:===)
def ===(...)
ORIG_METHOD_CASECOMPARE.bind(self).call(...)
end
ORIG_METHOD_EQUAL = instance_method(:==)
def ==(...)
ORIG_METHOD_EQUAL.bind(self).call(...)
end
ORIG_METHOD_LT = instance_method(:<)
def <(...)
ORIG_METHOD_LT.bind(self).call(...)
end
ORIG_METHOD_LTE = instance_method(:<=)
def <=(...)
ORIG_METHOD_LTE.bind(self).call(...)
end
ORIG_METHOD_GT = instance_method(:>)
def >(...)
ORIG_METHOD_GT.bind(self).call(...)
end
ORIG_METHOD_GTE = instance_method(:>=)
def >=(...)
ORIG_METHOD_GTE.bind(self).call(...)
end
end
CODE

code_for_float_class = <<~CODE
class Float
ORIG_METHOD_PLUS = instance_method(:+)
def +(...)
ORIG_METHOD_PLUS.bind(self).call(...)
end
ORIG_METHOD_MINUS = instance_method(:-)
def -(...)
ORIG_METHOD_MINUS.bind(self).call(...)
end
ORIG_METHOD_UMINUS = instance_method(:-@)
def -@(...)
ORIG_METHOD_UMINUS.bind(self).call(...)
end
ORIG_METHOD_MULT = instance_method(:*)
def *(...)
ORIG_METHOD_MULT.bind(self).call(...)
end
ORIG_METHOD_DIVISION = instance_method(:/)
def /(...)
ORIG_METHOD_DIVISION.bind(self).call(...)
end
ORIG_METHOD_MODULO = instance_method(:%)
def %(...)
ORIG_METHOD_MODULO.bind(self).call(...)
end
ORIG_METHOD_COMPARE = instance_method(:<=>)
def <=>(...)
ORIG_METHOD_COMPARE.bind(self).call(...)
end
ORIG_METHOD_EQUAL = instance_method(:==)
def ==(...)
ORIG_METHOD_EQUAL.bind(self).call(...)
end
ORIG_METHOD_LT = instance_method(:<)
def <(...)
ORIG_METHOD_LT.bind(self).call(...)
end
ORIG_METHOD_LTE = instance_method(:<=)
def <=(...)
ORIG_METHOD_LTE.bind(self).call(...)
end
ORIG_METHOD_GT = instance_method(:>)
def >(...)
ORIG_METHOD_GT.bind(self).call(...)
end
ORIG_METHOD_GTE = instance_method(:>=)
def >=(...)
ORIG_METHOD_GTE.bind(self).call(...)
end
end
CODE

code_for_nil_class = <<~CODE
class NilClass
ORIG_METHOD_NIL = instance_method(:nil?)
def nil?(...)
ORIG_METHOD_NIL.bind(self).call(...)
end
end
CODE

code_for_symbol_class = <<~CODE
class Symbol
ORIG_METHOD_TO_PROC = instance_method(:to_proc)
def to_proc(...)
ORIG_METHOD_TO_PROC.bind(self).call(...)
end
end
CODE

code = [prolog, code_for_integer_class, code_for_float_class, code_for_nil_class, code_for_symbol_class].join("\n")
output = ruby_exe(code, args: "2>&1")

output.should.include?("warning: Redefining 'Integer#+' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#-' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#-@' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#*' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#/' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#%' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#<=>' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#>>' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#<<' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#|' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#&' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#===' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#==' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#<' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#<=' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#>' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Integer#>=' disables interpreter and JIT optimizations")

output.should.include?("warning: Redefining 'Float#+' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#-' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#-@' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#*' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#/' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#%' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#<=>' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#==' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#<' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#<=' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#>' disables interpreter and JIT optimizations")
output.should.include?("warning: Redefining 'Float#>=' disables interpreter and JIT optimizations")

output.should.include?("warning: Redefining 'NilClass#nil?' disables interpreter and JIT optimizations")

output.should.include?("warning: Redefining 'Symbol#to_proc' disables interpreter and JIT optimizations")
end
end

describe "Prepending a module into a class with optimised methods" do
it "emits performance warning" do
code = <<~CODE
Warning[:performance] = true
module M
end
class Integer
prepend M
end
CODE

ruby_exe(code, args: "2>&1").should.include?("warning: Prepending a module to Integer disables interpreter and JIT optimizations")
end
end
1 change: 1 addition & 0 deletions src/main/java/org/truffleruby/RubyContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ public TruffleLogger getLogger() {
return logger;
}

@TruffleBoundary
public ConsoleHolder getConsoleHolder() {
if (consoleHolder == null) {
synchronized (this) {
Expand Down

0 comments on commit 1ee958f

Please sign in to comment.