1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_shapes.rb
Jemma Issroff 913979bede
Make inline cache reads / writes atomic with object shapes
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>
2022-10-11 08:40:56 -07:00

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