mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Prior to this commit, we were reading and writing ivar index and shape ID in inline caches in two separate instructions when getting and setting ivars. This meant there was a race condition with ractors and these caches where one ractor could change a value in the cache while another was still reading from it. This commit instead reads and writes shape ID and ivar index to inline caches atomically so there is no longer a race condition. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org> Co-Authored-By: John Hawthorn <john@hawthorn.email>
182 lines
5.2 KiB
Ruby
182 lines
5.2 KiB
Ruby
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
|
|
# These test the functionality of object shapes
|
|
class TestShapes < Test::Unit::TestCase
|
|
class Example
|
|
def initialize
|
|
@a = 1
|
|
end
|
|
end
|
|
|
|
class RemoveAndAdd
|
|
def add_foo
|
|
@foo = 1
|
|
end
|
|
|
|
def remove
|
|
remove_instance_variable(:@foo)
|
|
end
|
|
|
|
def add_bar
|
|
@bar = 1
|
|
end
|
|
end
|
|
|
|
# RubyVM::Shape.of returns new instances of shape objects for
|
|
# each call. This helper method allows us to define equality for
|
|
# shapes
|
|
def assert_shape_equal(shape1, shape2)
|
|
assert_equal(shape1.id, shape2.id)
|
|
assert_equal(shape1.parent_id, shape2.parent_id)
|
|
assert_equal(shape1.depth, shape2.depth)
|
|
assert_equal(shape1.type, shape2.type)
|
|
end
|
|
|
|
def refute_shape_equal(shape1, shape2)
|
|
refute_equal(shape1.id, shape2.id)
|
|
end
|
|
|
|
def test_iv_index
|
|
example = RemoveAndAdd.new
|
|
shape = RubyVM::Shape.of(example)
|
|
assert_equal 0, shape.iv_count
|
|
|
|
example.add_foo # makes a transition
|
|
new_shape = RubyVM::Shape.of(example)
|
|
assert_equal([:@foo], example.instance_variables)
|
|
assert_equal(shape.id, new_shape.parent.id)
|
|
assert_equal(1, new_shape.iv_count)
|
|
|
|
example.remove # makes a transition
|
|
remove_shape = RubyVM::Shape.of(example)
|
|
assert_equal([], example.instance_variables)
|
|
assert_equal(new_shape.id, remove_shape.parent.id)
|
|
assert_equal(1, remove_shape.iv_count)
|
|
|
|
example.add_bar # makes a transition
|
|
bar_shape = RubyVM::Shape.of(example)
|
|
assert_equal([:@bar], example.instance_variables)
|
|
assert_equal(remove_shape.id, bar_shape.parent.id)
|
|
assert_equal(2, bar_shape.iv_count)
|
|
end
|
|
|
|
def test_new_obj_has_root_shape
|
|
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(Object.new))
|
|
end
|
|
|
|
def test_frozen_new_obj_has_frozen_root_shape
|
|
assert_shape_equal(
|
|
RubyVM::Shape.frozen_root_shape,
|
|
RubyVM::Shape.of(Object.new.freeze)
|
|
)
|
|
end
|
|
|
|
def test_str_has_root_shape
|
|
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(""))
|
|
end
|
|
|
|
def test_array_has_root_shape
|
|
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
|
|
end
|
|
|
|
def test_hash_has_root_shape
|
|
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of({}))
|
|
end
|
|
|
|
def test_true_has_frozen_root_shape
|
|
assert_shape_equal(RubyVM::Shape.frozen_root_shape, RubyVM::Shape.of(true))
|
|
end
|
|
|
|
def test_nil_has_frozen_root_shape
|
|
assert_shape_equal(RubyVM::Shape.frozen_root_shape, RubyVM::Shape.of(nil))
|
|
end
|
|
|
|
def test_basic_shape_transition
|
|
obj = Example.new
|
|
refute_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(obj))
|
|
assert_shape_equal(RubyVM::Shape.root_shape.edges[:@a], RubyVM::Shape.of(obj))
|
|
assert_equal(obj.instance_variable_get(:@a), 1)
|
|
end
|
|
|
|
def test_different_objects_make_same_transition
|
|
obj = Example.new
|
|
obj2 = ""
|
|
obj2.instance_variable_set(:@a, 1)
|
|
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
|
end
|
|
|
|
def test_duplicating_objects
|
|
obj = Example.new
|
|
obj2 = obj.dup
|
|
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
|
end
|
|
|
|
def test_freezing_and_duplicating_object
|
|
obj = Object.new.freeze
|
|
obj2 = obj.dup
|
|
refute_predicate(obj2, :frozen?)
|
|
# dup'd objects shouldn't be frozen, and the shape should be the
|
|
# parent shape of the copied object
|
|
assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
|
|
end
|
|
|
|
def test_freezing_and_duplicating_object_with_ivars
|
|
obj = Example.new.freeze
|
|
obj2 = obj.dup
|
|
refute_predicate(obj2, :frozen?)
|
|
refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
|
assert_equal(obj2.instance_variable_get(:@a), 1)
|
|
end
|
|
|
|
def test_freezing_and_duplicating_string_with_ivars
|
|
str = "str"
|
|
str.instance_variable_set(:@a, 1)
|
|
str.freeze
|
|
str2 = str.dup
|
|
refute_predicate(str2, :frozen?)
|
|
refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
|
|
assert_equal(str2.instance_variable_get(:@a), 1)
|
|
end
|
|
|
|
def test_freezing_and_cloning_objects
|
|
obj = Object.new.freeze
|
|
obj2 = obj.clone(freeze: true)
|
|
assert_predicate(obj2, :frozen?)
|
|
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
|
end
|
|
|
|
def test_freezing_and_cloning_object_with_ivars
|
|
obj = Example.new.freeze
|
|
obj2 = obj.clone(freeze: true)
|
|
assert_predicate(obj2, :frozen?)
|
|
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
|
|
assert_equal(obj2.instance_variable_get(:@a), 1)
|
|
end
|
|
|
|
def test_freezing_and_cloning_string
|
|
str = "str".freeze
|
|
str2 = str.clone(freeze: true)
|
|
assert_predicate(str2, :frozen?)
|
|
assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
|
|
end
|
|
|
|
def test_freezing_and_cloning_string_with_ivars
|
|
str = "str"
|
|
str.instance_variable_set(:@a, 1)
|
|
str.freeze
|
|
str2 = str.clone(freeze: true)
|
|
assert_predicate(str2, :frozen?)
|
|
assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
|
|
assert_equal(str2.instance_variable_get(:@a), 1)
|
|
end
|
|
|
|
def test_out_of_bounds_shape
|
|
assert_raise ArgumentError do
|
|
RubyVM::Shape.find_by_id(RubyVM::Shape.next_shape_id)
|
|
end
|
|
assert_raise ArgumentError do
|
|
RubyVM::Shape.find_by_id(-1)
|
|
end
|
|
end
|
|
end
|