summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2026-01-11 14:49:36 +0100
committerJean Boussier <jean.boussier@gmail.com>2026-04-29 11:26:55 +0900
commit3345854fa882f6bd70bbd7853010b43b9c8fe73d (patch)
tree04af393893cc4d109bfb0d6831fcb18807020082 /test
parent23215de78eafecaee48ff481321531ce194d1cd4 (diff)
Add YJIT test for outdated comment
As of Ruby 4.0, the YJIT comment isn't quite correct because we now store the reference to the `imemo/fields` object inline. Which means that a Struct of precisely 78 members (max embeddable on 64bit archs) can cohexist in embedded and heap shapes as demonstrated by the added tests. If the comment was correct I would expect we could easily crash YJIT, but I'm unable to, so I suspect there is a guard I'm not seeing that already handle that.
Diffstat (limited to 'test')
-rw-r--r--test/ruby/test_struct.rb8
-rw-r--r--test/ruby/test_yjit.rb34
2 files changed, 41 insertions, 1 deletions
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 294498c30f..b37f4dba97 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -41,8 +41,14 @@ module TestStruct
end
end
+ MAX_EMBEDDED_MEMBERS = (
+ GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]
+ ) / RbConfig::SIZEOF["void*"]
+
def test_larger_than_largest_pool
- count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1
+ count = MAX_EMBEDDED_MEMBERS + 1
list = Array(0..count)
klass = @Struct.new(*list.map { |i| :"a_#{i}"})
struct = klass.new(*list)
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 721d070399..0d7fe66e1c 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -644,6 +644,40 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ STRUCT_MAX_EMBEDDED_MEMBERS = (
+ GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]
+ ) / RbConfig::SIZEOF["void*"]
+
+ def test_spilled_struct_aref
+ omit("FIXME: https://github.com/Shopify/ruby/issues/977")
+ assert_compiles(<<~RUBY)
+ LargeStruct = Struct.new(:foo, :bar, *(#{STRUCT_MAX_EMBEDDED_MEMBERS} - 2).times.map { :"m_\#{it}" })
+
+ def foo(obj)
+ foo = obj.foo
+ raise "Expected 1, got: \#{foo}" unless foo == 1
+ bar = obj.bar
+ raise "Expected 2, got: \#{bar}" unless bar == 2
+ end
+
+ embedded_struct = LargeStruct.new(1, 2)
+ # Bump RCLASS_MAX_IV_COUNT for LargeStruct
+ embedded_struct.instance_variable_set(:@test, 1)
+
+ # Next allocation reserves space for the imemo/fields reference.
+ heap_struct = LargeStruct.new(1, 2)
+
+ RubyVM::YJIT.reset_stats!
+
+ foo(embedded_struct)
+ foo(embedded_struct)
+ foo(heap_struct)
+ foo(heap_struct)
+ RUBY
+ end
+
def test_struct_aset
assert_compiles(<<~RUBY)
def foo(obj)