From b359ea34e7b6429b0c2351dc6a13b743968ec5c2 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 26 Mar 2026 13:48:51 +0100 Subject: [PATCH 01/20] [ruby/rubygems] Skip checksum for the bundler gem if no `bundler.gem` exists on disk: - ### Problem This change is purely to fix a problem when developing Bundler. When we tried to release Bundler 4.0.9, we bumped the VERSION from `4.1.0.dev` to `4.0.9`, this condition now evaluates to false https://github.com/ruby/rubygems/blob/34d19fa8a3f84c50e2ba65e0f39c80045e7cbfb8/bundler/lib/bundler/lockfile_generator.rb#L106. We then ended up with a CI crash. ``` Errno::ENOENT: No such file or directory @ rb_sysopen - /Users/runner/work/rubygems/rubygems/cache/bundler-4.0.9.gem ``` ### Context Computing a checksum for the `bundler` gem is only possible when the `bundler.gem` binary exists on disk. When a regular user interacts with `bundler`, the spec is loaded from disk and an associated cached `bundler.gem` should exists. However, when developing Bundler, the spec doesn't come from disk but from a fake spec https://github.com/ruby/rubygems/blob/34d19fa8a3f84c50e2ba65e0f39c80045e7cbfb8/bundler/lib/bundler/source/metadata.rb#L22-L28 that with no associated `bundler.gem`. ### Solution To prevent CI from breaking whenever we make a release, we have to skip computing a checksum if no `bundler.gem` exists. https://github.com/ruby/rubygems/commit/01e0e61612 --- lib/bundler/lockfile_generator.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index e23263048c76c4..b56ae2e2b10487 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -105,9 +105,11 @@ def add_section(name, value) def bundler_checksum return [] if Bundler.gem_version.to_s.end_with?(".dev") + bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last + return [] unless File.exist?(bundler_spec.cache_file) + require "rubygems/package" - bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last package = Gem::Package.new(bundler_spec.cache_file) definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package)) From 1e91f56642808336efc06100a0252b10d9a810d4 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 26 Mar 2026 14:01:40 +0100 Subject: [PATCH 02/20] [ruby/rubygems] Fix test suite that will start to fail whenever we attempt a release: - ### Problem Whenever we bump the bundler `VERSION` to a non development release, like we recently did in [4.0.9](https://github.com/ruby/rubygems/pull/9426) The test suite will behave differently and will break due to the Bundler checksum feature introduced in https://github.com/ruby/rubygems/pull/9366 This is because the expectactions of tests that have a lockfile' will sometimes include a checksum line for the bundler gem itself depending on whether this condition evaluates to `true`. https://github.com/ruby/rubygems/blob/34d19fa8a3f84c50e2ba65e0f39c80045e7cbfb8/bundler/lib/bundler/lockfile_generator.rb#L106 ### Solution We can modify the spec helper `checksums_section_when_enabled` to automatically append a checksum entry for bundler whenever necesary. https://github.com/ruby/rubygems/commit/2a5019cac6 --- spec/bundler/commands/lock_spec.rb | 9 +++++---- spec/bundler/commands/update_spec.rb | 5 ++--- spec/bundler/support/checksums.rb | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index d19a610003bf1b..8ab3cc7e8dd6fe 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -2295,6 +2295,10 @@ bundle("lock --add-checksums", artifice: "endpoint") + checksums = checksums_section_when_enabled do |c| + c.no_checksum "warning", "18.0.0" + end + expect(lockfile).to eq <<~L GEM remote: https://gem.repo4/ @@ -2306,10 +2310,7 @@ DEPENDENCIES warning - - CHECKSUMS - warning (18.0.0) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 9bc5a8e1d187b7..03a3786d80a20d 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -1419,9 +1419,7 @@ #{lockfile_platforms} DEPENDENCIES - - CHECKSUMS - + #{checksums_section_when_enabled} RUBY VERSION #{Bundler::RubyVersion.system} @@ -1708,6 +1706,7 @@ checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo4, "myrack", "1.0") end + checksums.delete("bundler") expect(lockfile).to eq <<~L GEM diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb index 03147bf6f4638f..7b69bba6680b55 100644 --- a/spec/bundler/support/checksums.rb +++ b/spec/bundler/support/checksums.rb @@ -3,6 +3,8 @@ module Spec module Checksums class ChecksumsBuilder + attr_reader :bundler_registered + def initialize(enabled = true, &block) @enabled = enabled @checksums = {} @@ -15,6 +17,8 @@ def initialize_copy(original) end def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems") + @bundler_registered = true if name == "bundler" + name_tuple = Gem::NameTuple.new(name, version, platform) gem_file = File.join(repo, folder, "#{name_tuple.full_name}.gem") File.open(gem_file, "rb") do |f| @@ -50,8 +54,13 @@ def register(name_tuple, checksum) end end - def checksums_section(enabled = true, &block) - ChecksumsBuilder.new(enabled, &block) + def checksums_section(enabled = true, bundler_checksum: true, &block) + ChecksumsBuilder.new(enabled, &block).tap do |builder| + next if builder.bundler_registered || !bundler_checksum + + next if Bundler::VERSION.to_s.end_with?(".dev") + builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache") + end end def checksums_section_when_enabled(target_lockfile = nil, &block) @@ -64,7 +73,7 @@ def checksums_section_when_enabled(target_lockfile = nil, &block) end def checksum_to_lock(*args) - checksums_section do |c| + checksums_section(true, bundler_checksum: false) do |c| c.checksum(*args) end.to_s.sub(/^CHECKSUMS\n/, "").strip end From 6afed23aec9bfa0c4cdd24f7935f89c93277568e Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 26 Mar 2026 14:25:11 +0100 Subject: [PATCH 03/20] [ruby/rubygems] Prevent checksum for this test: - ### Problem This test breaks whenever checksums are computed. It didn't break before because the gemfile in this test doesn't include any gems (only git gems) and therefore checksum is skipped. ### Context This test was added in https://github.com/ruby/rubygems/commit/c19a9f2ff71 to ensure Bundler will not load "digest". I don't think this test really works as it will break as soon as you had a regular gem in the gemfile test. https://github.com/ruby/rubygems/blob/34d19fa8a3f84c50e2ba65e0f39c80045e7cbfb8/spec/runtime/setup_spec.rb#L1354-L1355 I'm being unlucky and it now fails because we compute a checksum for the "bundler" gem itself. Computing a checksum requires "rubygems/package", and down the line requires "digest". ### Solution For the sake of shipping this PR, I'm going to go workaround the problem and skip checksum so that "digest" is not loaded. https://github.com/ruby/rubygems/commit/699b01159c --- spec/bundler/runtime/setup_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index a1c656bc3c89cd..e3c0fc19ea01f1 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1355,7 +1355,7 @@ def lock_with(ruby_version = nil) gem "bar", :git => "#{lib_path("bar-1.0")}" G - bundle :install + bundle :install, env: { "BUNDLE_LOCKFILE_CHECKSUMS" => "false" } ruby <<-RUBY, artifice: nil require 'bundler/setup' From de1d14c0caea84baa106a356cc4c85ee3d38faca Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 17 Feb 2026 00:44:12 +0900 Subject: [PATCH 04/20] [ruby/openssl] Add const qualifiers for OpenSSL 4.0 compatibility OpenSSL's master branch is changing functions to return const pointers where the returned objects are not meant to be modified by the caller. Update ossl_*_new() to take const pointers accordingly. Unfortunately, *_dup() in older versions of OpenSSL and in LibreSSL/AWS-LC take non-const pointers, so const casts are required. https://github.com/ruby/openssl/commit/34c49e6c6c --- ext/openssl/ossl_ocsp.c | 5 ++--- ext/openssl/ossl_ts.c | 2 +- ext/openssl/ossl_x509.h | 12 ++++++------ ext/openssl/ossl_x509attr.c | 9 +++++---- ext/openssl/ossl_x509cert.c | 12 ++++++------ ext/openssl/ossl_x509crl.c | 10 +++++----- ext/openssl/ossl_x509ext.c | 19 ++++++++++++++----- ext/openssl/ossl_x509name.c | 5 +++-- ext/openssl/ossl_x509req.c | 4 ++-- ext/openssl/ossl_x509revoked.c | 7 ++++--- ext/openssl/ossl_x509store.c | 10 ++++------ 11 files changed, 52 insertions(+), 43 deletions(-) diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index ddb67fcf07a7ce..9dd4b466d28443 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -922,7 +922,7 @@ ossl_ocspbres_get_status(VALUE self) VALUE ext = rb_ary_new(); int ext_count = OCSP_SINGLERESP_get_ext_count(single); for (int j = 0; j < ext_count; j++) { - X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j); + const X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j); rb_ary_push(ext, ossl_x509ext_new(x509ext)); } rb_ary_push(ary, ext); @@ -1341,7 +1341,6 @@ static VALUE ossl_ocspsres_get_extensions(VALUE self) { OCSP_SINGLERESP *sres; - X509_EXTENSION *ext; int count, i; VALUE ary; @@ -1350,7 +1349,7 @@ ossl_ocspsres_get_extensions(VALUE self) count = OCSP_SINGLERESP_get_ext_count(sres); ary = rb_ary_new2(count); for (i = 0; i < count; i++) { - ext = OCSP_SINGLERESP_get_ext(sres, i); + const X509_EXTENSION *ext = OCSP_SINGLERESP_get_ext(sres, i); rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */ } diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c index b31a854a63e9ac..393e08acff0c1e 100644 --- a/ext/openssl/ossl_ts.c +++ b/ext/openssl/ossl_ts.c @@ -706,7 +706,7 @@ ossl_ts_resp_get_tsa_certificate(VALUE self) TS_RESP *resp; PKCS7 *p7; PKCS7_SIGNER_INFO *ts_info; - X509 *cert; + const X509 *cert; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) diff --git a/ext/openssl/ossl_x509.h b/ext/openssl/ossl_x509.h index d25167ee7b0868..71932ef1a9a647 100644 --- a/ext/openssl/ossl_x509.h +++ b/ext/openssl/ossl_x509.h @@ -29,7 +29,7 @@ void Init_ossl_x509(void); */ extern VALUE cX509Attr; -VALUE ossl_x509attr_new(X509_ATTRIBUTE *); +VALUE ossl_x509attr_new(const X509_ATTRIBUTE *); X509_ATTRIBUTE *GetX509AttrPtr(VALUE); void Init_ossl_x509attr(void); @@ -38,7 +38,7 @@ void Init_ossl_x509attr(void); */ extern VALUE cX509Cert; -VALUE ossl_x509_new(X509 *); +VALUE ossl_x509_new(const X509 *); X509 *GetX509CertPtr(VALUE); X509 *DupX509CertPtr(VALUE); void Init_ossl_x509cert(void); @@ -46,7 +46,7 @@ void Init_ossl_x509cert(void); /* * X509CRL */ -VALUE ossl_x509crl_new(X509_CRL *); +VALUE ossl_x509crl_new(const X509_CRL *); X509_CRL *GetX509CRLPtr(VALUE); void Init_ossl_x509crl(void); @@ -55,14 +55,14 @@ void Init_ossl_x509crl(void); */ extern VALUE cX509Ext; -VALUE ossl_x509ext_new(X509_EXTENSION *); +VALUE ossl_x509ext_new(const X509_EXTENSION *); X509_EXTENSION *GetX509ExtPtr(VALUE); void Init_ossl_x509ext(void); /* * X509Name */ -VALUE ossl_x509name_new(X509_NAME *); +VALUE ossl_x509name_new(const X509_NAME *); X509_NAME *GetX509NamePtr(VALUE); void Init_ossl_x509name(void); @@ -77,7 +77,7 @@ void Init_ossl_x509req(void); */ extern VALUE cX509Rev; -VALUE ossl_x509revoked_new(X509_REVOKED *); +VALUE ossl_x509revoked_new(const X509_REVOKED *); X509_REVOKED *DupX509RevokedPtr(VALUE); void Init_ossl_x509revoked(void); diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c index 4769e56e1e8501..b0773e7a7dca28 100644 --- a/ext/openssl/ossl_x509attr.c +++ b/ext/openssl/ossl_x509attr.c @@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509attr_type = { * Public */ VALUE -ossl_x509attr_new(X509_ATTRIBUTE *attr) +ossl_x509attr_new(const X509_ATTRIBUTE *attr) { X509_ATTRIBUTE *new; VALUE obj; obj = NewX509Attr(cX509Attr); - new = X509_ATTRIBUTE_dup(attr); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_ATTRIBUTE_dup((X509_ATTRIBUTE *)attr); if (!new) ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); SetX509Attr(obj, new); @@ -196,7 +197,7 @@ ossl_x509attr_set_value(VALUE self, VALUE value) ossl_raise(eX509AttrError, "attribute value must be ASN1::Set"); if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */ - ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); + const ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); X509_ATTRIBUTE *new_attr = X509_ATTRIBUTE_create_by_OBJ(NULL, obj, 0, NULL, -1); if (!new_attr) { sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free); @@ -240,7 +241,7 @@ ossl_x509attr_get_value(VALUE self) count = X509_ATTRIBUTE_count(attr); for (i = 0; i < count; i++) - sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i)); + sk_ASN1_TYPE_push(sk, (ASN1_TYPE *)X509_ATTRIBUTE_get0_type(attr, i)); if ((len = i2d_ASN1_SET_ANY(sk, NULL)) <= 0) { sk_ASN1_TYPE_free(sk); diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 95679c7d24ea72..de246759ab7af3 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509_type = { * Public */ VALUE -ossl_x509_new(X509 *x509) +ossl_x509_new(const X509 *x509) { X509 *new; VALUE obj; obj = NewX509(cX509Cert); - new = X509_dup(x509); + /* OpenSSL 1.1.1 takes a non-const pointer */ + new = X509_dup((X509 *)x509); if (!new) ossl_raise(eX509CertError, "X509_dup"); SetX509(obj, new); @@ -345,7 +346,7 @@ static VALUE ossl_x509_get_subject(VALUE self) { X509 *x509; - X509_NAME *name; + const X509_NAME *name; GetX509(self, x509); if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */ @@ -380,7 +381,7 @@ static VALUE ossl_x509_get_issuer(VALUE self) { X509 *x509; - X509_NAME *name; + const X509_NAME *name; GetX509(self, x509); if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */ @@ -603,14 +604,13 @@ ossl_x509_get_extensions(VALUE self) { X509 *x509; int count, i; - X509_EXTENSION *ext; VALUE ary; GetX509(self, x509); count = X509_get_ext_count(x509); ary = rb_ary_new_capa(count); for (i=0; i Date: Sun, 15 Mar 2026 03:24:45 +0900 Subject: [PATCH 05/20] [ruby/openssl] pkey: remove unnecessary prototype from ossl_pkey.h ossl_ec_new() was removed in commit https://github.com/ruby/openssl/commit/94aeab2f265d (pkey: simplify ossl_pkey_new(), 2017-03-16), but it forgot to remove the declaration while doing so. https://github.com/ruby/openssl/commit/faad7a0811 --- ext/openssl/ossl_pkey.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index 023361b90f2306..efba33b7529373 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -71,7 +71,6 @@ void Init_ossl_dh(void); * EC */ extern VALUE cEC; -VALUE ossl_ec_new(EVP_PKEY *); void Init_ossl_ec(void); #define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get) \ From b41b143018b5f4a2ef0a474d5b93f06fcfef539b Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 26 Feb 2026 21:33:19 +0900 Subject: [PATCH 06/20] [ruby/openssl] asn1: use new ASN1_BIT_STRING accessor functions with OpenSSL 4.0 ASN1_STRING has been made opaque in OpenSSL's master branch. Use the new accessor functions instead of accessing fields directly. Other uses of ASN1_STRING fields were already updated in . This patch converts the remaining ones, which require the new functions added in OpenSSL 4.0 and were not available at that time. https://github.com/ruby/openssl/commit/ebb505f217 --- ext/openssl/extconf.rb | 3 +++ ext/openssl/openssl_missing.h | 23 ++++++++++++++++++++++ ext/openssl/ossl_asn1.c | 37 +++++++++++++++++++---------------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index a897c86b657e9c..06ed4f6ac334dd 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -169,6 +169,9 @@ def find_openssl_library # added in 3.5.0 have_func("SSL_get0_peer_signature_name(NULL, NULL)", ssl_h) +# added in 4.0.0 +have_func("ASN1_BIT_STRING_set1(NULL, NULL, 0, 0)", "openssl/asn1.h") + Logging::message "=== Checking done. ===\n" # Append flags from environment variables. diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index 6592f9cceaf714..ed3b5b7c0f5611 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -29,4 +29,27 @@ # define EVP_PKEY_eq(a, b) EVP_PKEY_cmp(a, b) #endif +/* added in 4.0.0 */ +#ifndef HAVE_ASN1_BIT_STRING_SET1 +static inline int +ASN1_BIT_STRING_set1(ASN1_BIT_STRING *bitstr, const uint8_t *data, + size_t length, int unused_bits) +{ + if (length > INT_MAX || !ASN1_STRING_set(bitstr, data, (int)length)) + return 0; + bitstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07); + bitstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; + return 1; +} + +static inline int +ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, + int *unused_bits) +{ + *length = bitstr->length; + *unused_bits = bitstr->flags & 0x07; + return 1; +} +#endif + #endif /* _OSSL_OPENSSL_MISSING_H_ */ diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 18fa8edeb5777e..67c03b7f98a952 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -228,7 +228,7 @@ obj_to_asn1int(VALUE obj) } static ASN1_BIT_STRING* -obj_to_asn1bstr(VALUE obj, long unused_bits) +obj_to_asn1bstr(VALUE obj, int unused_bits) { ASN1_BIT_STRING *bstr; @@ -236,11 +236,11 @@ obj_to_asn1bstr(VALUE obj, long unused_bits) ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ "the range 0 to 7"); StringValue(obj); - if(!(bstr = ASN1_BIT_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj)); - bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */ - bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; + if (!(bstr = ASN1_BIT_STRING_new())) + ossl_raise(eASN1Error, "ASN1_BIT_STRING_new"); + if (!ASN1_BIT_STRING_set1(bstr, (uint8_t *)RSTRING_PTR(obj), + RSTRING_LEN(obj), unused_bits)) + ossl_raise(eASN1Error, "ASN1_BIT_STRING_set1"); return bstr; } @@ -364,22 +364,25 @@ decode_int(unsigned char* der, long length) } static VALUE -decode_bstr(unsigned char* der, long length, long *unused_bits) +decode_bstr(unsigned char* der, long length, int *unused_bits) { ASN1_BIT_STRING *bstr; const unsigned char *p; - long len; + size_t len; VALUE ret; + int state; p = der; - if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) - ossl_raise(eASN1Error, NULL); - len = bstr->length; - *unused_bits = 0; - if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT) - *unused_bits = bstr->flags & 0x07; - ret = rb_str_new((const char *)bstr->data, len); + if (!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) + ossl_raise(eASN1Error, "d2i_ASN1_BIT_STRING"); + if (!ASN1_BIT_STRING_get_length(bstr, &len, unused_bits)) { + ASN1_BIT_STRING_free(bstr); + ossl_raise(eASN1Error, "ASN1_BIT_STRING_get_length"); + } + ret = ossl_str_new((const char *)ASN1_STRING_get0_data(bstr), len, &state); ASN1_BIT_STRING_free(bstr); + if (state) + rb_jump_tag(state); return ret; } @@ -763,7 +766,7 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, { VALUE value, asn1data; unsigned char *p; - long flag = 0; + int flag = 0; p = *pp; @@ -820,7 +823,7 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, asn1data = rb_obj_alloc(klass); ossl_asn1_initialize(4, args, asn1data); if(tag == V_ASN1_BIT_STRING){ - rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag)); + rb_ivar_set(asn1data, sivUNUSED_BITS, INT2NUM(flag)); } } else { From 5973d619476e87e89835e2b0f38464ea897a49db Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 17 Feb 2026 16:16:54 +0900 Subject: [PATCH 07/20] [ruby/openssl] ssl: fix test_tmp_dh and test_tmp_dh_callback with OpenSSL 4.0 OpenSSL master added support for RFC 7919 groups in TLS 1.2. They are preferred over SSLContext#tmp_dh= or #tmp_dh_callback= values if the client advertises them in the supported_groups extension. https://github.com/ruby/openssl/commit/3e01c802c9 --- test/openssl/test_ssl.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index ce1b2c1e966fd0..e4fd581079c4dd 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1909,7 +1909,9 @@ def test_tmp_dh_callback } } start_server(ctx_proc: ctx_proc) do |port| - server_connect(port) { |ssl| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "P-256" # Exclude RFC 7919 groups + server_connect(port, ctx) { |ssl| assert called, "dh callback should be called" assert_equal dh.to_der, ssl.tmp_key.to_der } @@ -2172,7 +2174,9 @@ def test_tmp_dh ctx.tmp_dh = dh } start_server(ctx_proc: ctx_proc) do |port| - server_connect(port) { |ssl| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "P-256" # Exclude RFC 7919 groups + server_connect(port, ctx) { |ssl| assert_equal dh.to_der, ssl.tmp_key.to_der } end From 9c827a49ccefc0b945b25a479cdd9ba0a156a21c Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Thu, 26 Mar 2026 14:42:07 +0000 Subject: [PATCH 08/20] [ruby/openssl] test_pkey_rsa.rb: Fix test_private_encoding_encrypted in OpenSSL 4.0 FIPS OpenSSL 4.0.0 added a check for Password-Based Key Derivation Function 2 (PBKDF2) to require the minimal password length 8 in FIPS by the following commit. https://github.com/openssl/openssl/commit/71ed0fc8b3cdb33cd06059416686f8972ede0248 This commit fixes the following test failure in OpenSSL 4.0 FIPS by changing testing password length from 6 to 8.. ``` 1) Error: test_private_encoding_encrypted(OpenSSL::TestPKeyRSA): OpenSSL::PKey::PKeyError: i2d_PKCS8PrivateKey_bio: encrypt error /home/runner/work/openssl/openssl/test/openssl/test_pkey_rsa.rb:465:in `private_to_der' /home/runner/work/openssl/openssl/test/openssl/test_pkey_rsa.rb:465:in `test_private_encoding_encrypted' 462: 463: def test_private_encoding_encrypted 464: rsa = Fixtures.pkey("rsa2048") => 465: encoded = rsa.private_to_der("aes-128-cbc", "abcdef") 466: asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo 467: assert_kind_of OpenSSL::ASN1::Sequence, asn1 468: assert_equal 2, asn1.value.size Error: OpenSSL::PKey::PKeyError: i2d_PKCS8PrivateKey_bio: encrypt error ``` https://github.com/ruby/openssl/commit/69f8cd1af1 --- test/openssl/test_pkey_rsa.rb | 74 +++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 86f51cf4385a01..1716aef3807af9 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -462,54 +462,54 @@ def test_private_encoding def test_private_encoding_encrypted rsa = Fixtures.pkey("rsa2048") - encoded = rsa.private_to_der("aes-128-cbc", "abcdef") + encoded = rsa.private_to_der("aes-128-cbc", "abcdefgh") asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo assert_kind_of OpenSSL::ASN1::Sequence, asn1 assert_equal 2, asn1.value.size assert_not_equal rsa.private_to_der, encoded - assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef") - assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdef" } + assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh") + assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdefgh" } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") } - encoded = rsa.private_to_pem("aes-128-cbc", "abcdef") + encoded = rsa.private_to_pem("aes-128-cbc", "abcdefgh") assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0] - assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef") + assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh") # Use openssl instead of certtool due to https://gitlab.com/gnutls/gnutls/-/issues/1632 - # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdef + # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdefgh pem = <<~EOF - -----BEGIN ENCRYPTED PRIVATE KEY----- - MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIay5V8CDQi5oCAggA - MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBB6eyagcbsvdQlM1kPcH7kiBIIE - 0Ng1apIyoPAZ4BfC4kMNeSmeAv3XspxqYi3uWzXiNyTcoE6390swrwM6WvdpXvLI - /n/V06krxPZ9X4fBG2kLUzXt5f09lEvmQU1HW1wJGU5Sq3bNeXBrlJF4DzJE4WWd - whVVvNMm44ghdzN/jGSw3z+6d717N+waa7vrpBDsHjhsPNwxpyzUvcFPFysTazxx - kN/dziIBF6SRKi6w8VaJEMQ8czGu5T3jOc2e/1p3/AYhHLPS4NHhLR5OUh0TKqLK - tANAqI9YqCAjhqcYCmN3mMQXY52VfOqG9hlX1x9ZQyqiH7l102EWbPqouk6bCBLQ - wHepPg4uK99Wsdh65qEryNnXQ5ZmO6aGb6T3TFENCaNKmi8Nh+/5dr7J7YfhIwpo - FqHvk0hrZ8r3EQlr8/td0Yb1/IKzeQ34638uXf9UxK7C6o+ilsmJDR4PHJUfZL23 - Yb9qWJ0GEzd5AMsI7x6KuUxSuH9nKniv5Tzyty3Xmb4FwXUyADWE19cVuaT+HrFz - GraKnA3UXbEgWAU48/l4K2HcAHyHDD2Kbp8k+o1zUkH0fWUdfE6OUGtx19Fv44Jh - B7xDngK8K48C6nrj06/DSYfXlb2X7WQiapeG4jt6U57tLH2XAjHCkvu0IBZ+//+P - yIWduEHQ3w8FBRcIsTNJo5CjkGk580TVQB/OBLWfX48Ay3oF9zgnomDIlVjl9D0n - lKxw/KMCLkvB78rUeGbr1Kwj36FhGpTBw3FgcYGa5oWFZTlcOgMTXLqlbb9JnDlA - Zs7Tu0WTyOTV/Dne9nEm39Dzu6wRojiIpmygTD4FI7rmOy3CYNvL3XPv7XQj0hny - Ee/fLxugYlQnwPZSqOVEQY2HsG7AmEHRsvy4bIWIGt+yzAPZixt9MUdJh91ttRt7 - QA/8J1pAsGqEuQpF6UUINZop3J7twfhO4zWYN/NNQ52eWNX2KLfjfGRhrvatzmZ0 - BuCsCI9hwEeE6PTlhbX1Rs177MrDc3vlqz2V3Po0OrFjXAyg9DR/OC4iK5wOG2ZD - 7StVSP8bzwQXsz3fJ0ardKXgnU2YDAP6Vykjgt+nFI09HV/S2faOc2g/UK4Y2khl - J93u/GHMz/Kr3bKWGY1/6nPdIdFheQjsiNhd5gI4tWik2B3QwU9mETToZ2LSvDHU - jYCys576xJLkdMM6nJdq72z4tCoES9IxyHVs4uLjHKIo/ZtKr+8xDo8IL4ax3U8+ - NMhs/lwReHmPGahm1fu9zLRbNCVL7e0zrOqbjvKcSEftObpV/LLcPYXtEm+lZcck - /PMw49HSE364anKEXCH1cyVWJwdZRpFUHvRpLIrpHru7/cthhiEMdLgK1/x8sLob - DiyieLxH1DPeXT4X+z94ER4IuPVOcV5AXc/omghispEX6DNUnn5jC4e3WyabjUbw - MuO9lVH9Wi2/ynExCqVmQkdbTXuLwjni1fJ27Q5zb0aCmhO8eq6P869NCjhJuiUj - NI9XtGLP50YVWE0kL8KEJqnyFudky8Khzk4/dyixQFqin5GfT4vetrLunGHy7lRB - 3LpnFrpMOr+0xr1RW1k9vlmjRsJSiojJfReYO7gH3B5swiww2azogoL+4jhF1Jxh - OYLWdkKhP2jSVGqtIDtny0O4lBm2+hLpWjiI0mJQ7wdA - -----END ENCRYPTED PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ+Sg92Hgy8EgVPf7t +Hen1qwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB5UX2xdDO8/AKA8 ++Y5CZyUEggTQkArh4mMPpnAe3xOcDKMz8KCn5lrLb/6Dla7Rp9LHKGkUfyI11EZt +m+OIriwy9oDQquKyVuLQVGAxXKk+3pyxMqLB0i3hLYamT3vzoPctyVwjuRuKoU3E +CbF0YhCoxvWMvjHsolwYzx00DbLXouE4BGKvPjnhw5hwtdoZ9Px0ZnCXCxVXi8z/ +mlw7a2ptKEiHQVjuPPbttq+dA+ez7pbWonWVod5TMaPtyEZu5XfPD+0pMboceHZg +H8ehgUhV3mzEJiisFGg1q9hj+4BaFl5m4tvqp43inCCdShE78CNnOPzJ7WCjKJqi +jGvHjeMoVx3rZXHcZDAzfIZvDigp9uAfzjRJjpRG8sg5sDQVC7vdUhQDe5TorKT2 +Vb0tdVYxoEpMJ3dhU6Ds5JxMR6GTLjsjTqOkAl6db3HxulwfEpr7YjOpfODR+ttA +BeIcUcMLsDHayIaQaMLIftHxOkfX7UxoFW9CMG5UMQf/m3eEgVUwgK/E5sUJRUTo +yhRzJ4NAP4fgc4YH9tbzvUrhfdCXCBEOn6IlDQL66SZr8Mm+Ggu4Ij4TnKWXLrXL +nSTDDa42kPOvtedKqxC/uXE7rrfh+uyw6J6OjSl6u86TIebndLuDo5DTdWKh8rsg +fvZZ6332dfMp8JC9/4YnYIJdI7acInSoyHp52OB+2+dgYCr5OrZFjjKS7nELVfo7 +OxGy6uH3NHF9qyUEf3MN17TRHI7jP3zKbXcDTPSyxLQkWe/CU5B251CTmoTSidSW +EhKnPlGZYbpVQJ4KGEL5UeY8W9PXQo4Dl7TmXBGvuPqNF8kMB3XrPIph7GmihmX0 +nlJqLk9eiRFmUETS0IdAyKJrm4R9Hf6rjYCbXlaApylyVUdSZ2BxgeoTY9BA6Kgf +3xlgMv01MoUkXMx2+OLIc9MzhButQiDxh3mfS012CjKqUFrJhRSa8DOpUfVgmXpq +/HP4drWamLWYJR8FsmJS11ZYc1EK/ctJTSpqfewvoUGOSHomhh7zXn1Acb6+9/3p +bcrJjoR5K8Jg6NlG4dSNkpY/x92I7bFLXFqELIH5tteDrlQen5eASjaiyPPAoOw8 +IGfOmFS4VUPh1VP6g8Jtn5Hr2qXB3DoQoI6EvUZhJ6GJfi67mx5VKux6G9MzJkix +GU1cL4WzWK2DU0l39UxXjS+4TmOYbrqLVnVMjusX0fwb8LkDC/fVohbhLwhHNwu6 +nSTSEpS9zSDrv1JXFtAtPv6XCSFs6ssPWJMwGSdThn7EfV0GEhG2mCzTyVhwxxQo +6U/Suqq4oMZoracPUCZx0E4u/bb4KBoFA/eBNPJENTR18IiV+D7wAxlxauO3N1t4 +iJxwrrvSgQPmOGuxrh5LVD41UXYUWLtndzabnpByppFn2MbmvrqJgon0MSs84cTA +7scnbPu1V3PpKy/t67gtVw9Ue8hLjrskWB1JPFYr7vRWvJzYjfbflyroF+QEJ3TA +6rTfUC9+ePci6T+i9jF4xcmzqYzRtnGtp5nRUitJGw0uwBTDwzfI2WD6ltvvu7lc +pHuzvY5zEapuu1JhjHLUd+OE8rVVM999DUXo/IDLsWyRCphCiYfVXJNogd9rB0Ta +5AhVgpRhxkarBURZyLTYj7NRxCsbHq7XExJNrIdRG/KlBQfyEyIzZ7E= +-----END ENCRYPTED PRIVATE KEY----- EOF - assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdef") + assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdefgh") end def test_params From 0364a612166631ee20663391aefe122d374aff7e Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 31 Mar 2026 14:56:15 -0400 Subject: [PATCH 09/20] ZJIT: Clean up branching in HIR construction (#16616) We have the global register allocator now and don't need to thread everything through manually as block params. Simplify HIR construction. --- zjit/src/hir.rs | 147 ++++-------- zjit/src/hir/opt_tests.rs | 264 ++++++++++------------ zjit/src/hir/tests.rs | 462 ++++++++++++++++++-------------------- 3 files changed, 382 insertions(+), 491 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4e38b261308110..6dc0589f6366a0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2610,24 +2610,6 @@ impl Function { id } - fn new_branch_block( - &mut self, - insn_idx: u32, - exit_state: &FrameState, - locals_count: usize, - stack_count: usize, - ) -> (BlockId, InsnId, FrameState, InsnId) { - let block = self.new_block(insn_idx); - let self_param = self.push_insn(block, Insn::Param); - let mut state = exit_state.clone(); - state.locals.clear(); - state.stack.clear(); - state.locals.extend((0..locals_count).map(|_| self.push_insn(block, Insn::Param))); - state.stack.extend((0..stack_count).map(|_| self.push_insn(block, Insn::Param))); - let snapshot = self.push_insn(block, Insn::Snapshot { state: state.clone() }); - (block, self_param, state, snapshot) - } - fn remove_block(&mut self, block_id: BlockId) { if BlockId(self.blocks.len() - 1) != block_id { panic!("Can only remove the last block"); @@ -7587,44 +7569,39 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { None }; - let locals_count = state.locals.len(); - let stack_count = state.stack.len(); - let entry_args = state.as_args(self_param); - // `getblockparamproxy` has two semantic paths: // - modified: return the already-materialized block local from EP // - unmodified: inspect the block handler and produce proxy/nil - let (modified_block, modified_self_param, mut modified_state, ..) = - fun.new_branch_block(branch_insn_idx, &exit_state, locals_count, stack_count); - let (unmodified_block, unmodified_self_param, mut unmodified_state, unmodified_exit_id) = - fun.new_branch_block(branch_insn_idx, &exit_state, locals_count, stack_count); + let modified_block = fun.new_block(branch_insn_idx); + let unmodified_block = fun.new_block(branch_insn_idx); let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx)); + let join_result = fun.push_insn(join_block, Insn::Param); + let join_local = if level == 0 { Some(fun.push_insn(join_block, Insn::Param)) } else { None }; let ep = fun.push_insn(block, Insn::GetEP { level }); let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { ep }); - fun.push_insn(block, Insn::IfTrue { val: is_modified, target: BranchEdge { target: modified_block, args: entry_args.clone() }}); - fun.push_insn(block, Insn::Jump(BranchEdge { target: unmodified_block, args: entry_args })); + fun.push_insn(block, Insn::IfTrue { val: is_modified, target: BranchEdge { target: modified_block, args: vec![] }}); + fun.push_insn(block, Insn::Jump(BranchEdge { target: unmodified_block, args: vec![] })); // Push modified block: load the block local via EP. - let ep = fun.push_insn(modified_block, Insn::GetEP { level }); let modified_val = fun.get_local_from_ep(modified_block, ep, ep_offset, level, types::BasicObject); - if level == 0 { - modified_state.setlocal(ep_offset, modified_val); - } - modified_state.stack_push(modified_val); - fun.push_insn(modified_block, Insn::Jump(BranchEdge { target: join_block, args: modified_state.as_args(modified_self_param) })); + let mut modified_args = vec![modified_val]; + if level == 0 { modified_args.push(modified_val); } + fun.push_insn(modified_block, Insn::Jump(BranchEdge { target: join_block, args: modified_args })); // Push unmodified block: inspect the current block handler to // decide whether this path returns `nil` or `BlockParamProxy`. - let ep = fun.push_insn(unmodified_block, Insn::GetEP { level }); let block_handler = fun.push_insn(unmodified_block, Insn::LoadField { recv: ep, id: ID!(_env_data_index_specval), offset: SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, return_type: types::CInt64 }); + let original_local = if level == 0 { Some(state.getlocal(ep_offset)) } else { None }; match profiled_block_type { Some(ty) if ty.nil_p() => { - fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: SideExitReason::BlockParamProxyNotNil, state: unmodified_exit_id }); - unmodified_state.stack_push(fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(Qnil) })); - fun.push_insn(unmodified_block, Insn::Jump(BranchEdge { target: join_block, args: unmodified_state.as_args(unmodified_self_param) })); + fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: SideExitReason::BlockParamProxyNotNil, state: exit_id }); + let nil_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(Qnil) }); + let mut unmodified_args = vec![nil_val]; + if let Some(local) = original_local { unmodified_args.push(local); } + fun.push_insn(unmodified_block, Insn::Jump(BranchEdge { target: join_block, args: unmodified_args })); } _ => { // This handles two cases which are nearly identical @@ -7635,107 +7612,65 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { const _: () = assert!(RUBY_SYMBOL_FLAG & 1 == 0, "guard below rejects symbol block handlers"); // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: unmodified_exit_id }); + fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: exit_id }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing - unmodified_state.stack_push(fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) })); - fun.push_insn(unmodified_block, Insn::Jump(BranchEdge { target: join_block, args: unmodified_state.as_args(unmodified_self_param) })); + let proxy_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }); + let mut unmodified_args = vec![proxy_val]; + if let Some(local) = original_local { unmodified_args.push(local); } + fun.push_insn(unmodified_block, Insn::Jump(BranchEdge { target: join_block, args: unmodified_args })); } } // Continue compilation from the merged continuation block at the next // instruction. - queue.push_back((unmodified_state, join_block, insn_idx, local_inval)); - break; + if let Some(local_param) = join_local { + state.setlocal(ep_offset, local_param); + } + state.stack_push(join_result); + block = join_block; } YARVINSN_getblockparam => { - fn finish_getblockparam_branch( - fun: &mut Function, - block: BlockId, - self_param: InsnId, - state: &mut FrameState, - join_block: BlockId, - ep_offset: u32, - level: u32, - val: InsnId, - ) { - if level == 0 { - state.setlocal(ep_offset, val); - } - state.stack_push(val); - fun.push_insn(block, Insn::Jump(BranchEdge { - target: join_block, - args: state.as_args(self_param), - })); - } - let ep_offset = get_arg(pc, 0).as_u32(); let level = get_arg(pc, 1).as_u32(); let branch_insn_idx = exit_state.insn_idx as u32; // If the block param is already a Proc (modified), read it from EP. // Otherwise, convert it to a Proc and store it to EP. + let modified_block = fun.new_block(branch_insn_idx); + let unmodified_block = fun.new_block(branch_insn_idx); + let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx)); + let join_param = fun.push_insn(join_block, Insn::Param); + let ep = fun.push_insn(block, Insn::GetEP { level }); let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { ep }); - let locals_count = state.locals.len(); - let stack_count = state.stack.len(); - let entry_args = state.as_args(self_param); - - // Set up branch and join blocks. - let (modified_block, modified_self_param, mut modified_state, ..) = - fun.new_branch_block(branch_insn_idx, &exit_state, locals_count, stack_count); - let (unmodified_block, unmodified_self_param, mut unmodified_state, unmodified_exit_id) = - fun.new_branch_block(branch_insn_idx, &exit_state, locals_count, stack_count); - let join_block = insn_idx_to_block.get(&insn_idx).copied().unwrap_or_else(|| fun.new_block(insn_idx)); - fun.push_insn(block, Insn::IfTrue { val: is_modified, - target: BranchEdge { target: modified_block, args: entry_args.clone() }, + target: BranchEdge { target: modified_block, args: vec![] }, }); fun.push_insn(block, Insn::Jump(BranchEdge { target: unmodified_block, - args: entry_args, + args: vec![], })); // Push modified block: read Proc from EP. - let ep = fun.push_insn(modified_block, Insn::GetEP { level }); - let modified_val = fun.get_local_from_ep(modified_block, - ep, - ep_offset, - level, - types::BasicObject, - ); - finish_getblockparam_branch( - &mut fun, - modified_block, - modified_self_param, - &mut modified_state, - join_block, - ep_offset, - level, - modified_val, - ); + let modified_val = fun.get_local_from_ep(modified_block, ep, ep_offset, level, types::BasicObject); + fun.push_insn(modified_block, Insn::Jump(BranchEdge { target: join_block, args: vec![modified_val] })); // Push unmodified block: convert block handler to Proc. let unmodified_val = fun.push_insn(unmodified_block, Insn::GetBlockParam { ep_offset, level, - state: unmodified_exit_id, + state: exit_id, }); - finish_getblockparam_branch( - &mut fun, - unmodified_block, - unmodified_self_param, - &mut unmodified_state, - join_block, - ep_offset, - level, - unmodified_val, - ); + fun.push_insn(unmodified_block, Insn::Jump(BranchEdge { target: join_block, args: vec![unmodified_val] })); // Continue compilation from the join block at the next instruction. - queue.push_back((unmodified_state, join_block, insn_idx, local_inval)); - break; + if level == 0 { + state.setlocal(ep_offset, join_param); + } + state.stack_push(join_param); + block = join_block; } YARVINSN_pop => { state.stack_pop()?; } YARVINSN_dup => { state.stack_push(state.stack_top()?); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 68850a71395ab0..ec7c332fd10cf1 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4735,19 +4735,17 @@ mod hir_opt_tests { v7:BasicObject = LoadArg :block@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v23:CPtr = GetEP 0 - v24:CBool = IsBlockParamModified v23 - IfTrue v24, bb4(v9, v10, v9) - v30:CPtr = GetEP 0 - v31:CInt64 = LoadField v30, :_env_data_index_specval@0x1001 - v32:CInt64 = GuardAnyBitSet v31, CUInt64(1) - v33:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v9, v10, v9, v33) - bb4(v15:BasicObject, v16:BasicObject, v17:BasicObject): - v27:CPtr = GetEP 0 - v28:BasicObject = LoadField v27, :block@0x1010 - Jump bb6(v15, v28, v17, v28) - bb6(v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject): + v17:CPtr = GetEP 0 + v18:CBool = IsBlockParamModified v17 + IfTrue v18, bb4() + v23:CInt64 = LoadField v17, :_env_data_index_specval@0x1001 + v24:CInt64 = GuardAnyBitSet v23, CUInt64(1) + v25:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v25, v10) + bb4(): + v21:BasicObject = LoadField v17, :block@0x1010 + Jump bb6(v21, v21) + bb6(v15:BasicObject, v16:BasicObject): SideExit NoProfileSend recompile "); } @@ -4777,29 +4775,26 @@ mod hir_opt_tests { v9:NilClass = Const Value(nil) Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:NilClass): - v17:CPtr = GetEP 0 - v18:CBool = IsBlockParamModified v17 - IfTrue v18, bb4(v11, v12, v13) - v32:BasicObject = GetBlockParam :block, l0, EP@4 - Jump bb6(v11, v32, v13, v32) - bb4(v19:BasicObject, v20:BasicObject, v21:NilClass): - v29:CPtr = GetEP 0 - v30:BasicObject = LoadField v29, :block@0x1001 - Jump bb6(v19, v30, v21, v30) - bb6(v34:BasicObject, v35:BasicObject, v36:NilClass, v37:BasicObject): - v53:CPtr = GetEP 0 - v54:CBool = IsBlockParamModified v53 - IfTrue v54, bb7(v34, v35, v37, v34) - v60:CPtr = GetEP 0 - v61:CInt64 = LoadField v60, :_env_data_index_specval@0x1002 - v62:CInt64 = GuardAnyBitSet v61, CUInt64(1) - v63:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb9(v34, v35, v37, v34, v63) - bb7(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject): - v57:CPtr = GetEP 0 - v58:BasicObject = LoadField v57, :block@0x1001 - Jump bb9(v43, v58, v45, v46, v58) - bb9(v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject): + v18:CPtr = GetEP 0 + v19:CBool = IsBlockParamModified v18 + IfTrue v19, bb4() + v24:BasicObject = GetBlockParam :block, l0, EP@4 + Jump bb6(v24) + bb4(): + v22:BasicObject = LoadField v18, :block@0x1001 + Jump bb6(v22) + bb6(v17:BasicObject): + v32:CPtr = GetEP 0 + v33:CBool = IsBlockParamModified v32 + IfTrue v33, bb7() + v38:CInt64 = LoadField v32, :_env_data_index_specval@0x1002 + v39:CInt64 = GuardAnyBitSet v38, CUInt64(1) + v40:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb9(v40, v17) + bb7(): + v36:BasicObject = LoadField v32, :block@0x1001 + Jump bb9(v36, v36) + bb9(v30:BasicObject, v31:BasicObject): SideExit NoProfileSend recompile "); } @@ -4827,29 +4822,26 @@ mod hir_opt_tests { v6:NilClass = Const Value(nil) Jump bb3(v5, v6) bb3(v8:BasicObject, v9:NilClass): - v13:CPtr = GetEP 1 - v14:CBool = IsBlockParamModified v13 - IfTrue v14, bb4(v8, v9) - v26:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v8, v9, v26) - bb4(v15:BasicObject, v16:NilClass): - v23:CPtr = GetEP 1 - v24:BasicObject = LoadField v23, :block@0x1000 - Jump bb6(v15, v16, v24) - bb6(v28:BasicObject, v29:NilClass, v30:BasicObject): - v44:CPtr = GetEP 1 - v45:CBool = IsBlockParamModified v44 - IfTrue v45, bb7(v28, v30, v28) - v51:CPtr = GetEP 1 - v52:CInt64 = LoadField v51, :_env_data_index_specval@0x1001 - v53:CInt64 = GuardAnyBitSet v52, CUInt64(1) - v54:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb9(v28, v30, v28, v54) - bb7(v36:BasicObject, v37:BasicObject, v38:BasicObject): - v48:CPtr = GetEP 1 - v49:BasicObject = LoadField v48, :block@0x1000 - Jump bb9(v36, v37, v38, v49) - bb9(v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject): + v14:CPtr = GetEP 1 + v15:CBool = IsBlockParamModified v14 + IfTrue v15, bb4() + v20:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v20) + bb4(): + v18:BasicObject = LoadField v14, :block@0x1000 + Jump bb6(v18) + bb6(v13:BasicObject): + v27:CPtr = GetEP 1 + v28:CBool = IsBlockParamModified v27 + IfTrue v28, bb7() + v33:CInt64 = LoadField v27, :_env_data_index_specval@0x1001 + v34:CInt64 = GuardAnyBitSet v33, CUInt64(1) + v35:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb9(v35) + bb7(): + v31:BasicObject = LoadField v27, :block@0x1000 + Jump bb9(v31) + bb9(v26:BasicObject): SideExit NoProfileSend recompile "); } @@ -4873,18 +4865,17 @@ mod hir_opt_tests { v7:BasicObject = LoadArg :block@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v14:CPtr = GetEP 0 - v15:CBool = IsBlockParamModified v14 - IfTrue v15, bb4(v9, v10) - v27:BasicObject = GetBlockParam :block, l0, EP@3 - Jump bb6(v9, v27, v27) - bb4(v16:BasicObject, v17:BasicObject): - v24:CPtr = GetEP 0 - v25:BasicObject = LoadField v24, :block@0x1001 - Jump bb6(v16, v25, v25) - bb6(v29:BasicObject, v30:BasicObject, v31:BasicObject): + v15:CPtr = GetEP 0 + v16:CBool = IsBlockParamModified v15 + IfTrue v16, bb4() + v21:BasicObject = GetBlockParam :block, l0, EP@3 + Jump bb6(v21) + bb4(): + v19:BasicObject = LoadField v15, :block@0x1001 + Jump bb6(v19) + bb6(v14:BasicObject): CheckInterrupts - Return v31 + Return v14 "); } @@ -4908,18 +4899,17 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v10:CPtr = GetEP 1 - v11:CBool = IsBlockParamModified v10 - IfTrue v11, bb4(v6) - v21:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v6, v21) - bb4(v12:BasicObject): - v18:CPtr = GetEP 1 - v19:BasicObject = LoadField v18, :block@0x1000 - Jump bb6(v12, v19) - bb6(v23:BasicObject, v24:BasicObject): + v11:CPtr = GetEP 1 + v12:CBool = IsBlockParamModified v11 + IfTrue v12, bb4() + v17:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v17) + bb4(): + v15:BasicObject = LoadField v11, :block@0x1000 + Jump bb6(v15) + bb6(v10:BasicObject): CheckInterrupts - Return v24 + Return v10 "); } @@ -8018,22 +8008,20 @@ mod hir_opt_tests { Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): v14:ArrayExact = NewArray - v24:CPtr = GetEP 0 - v25:CBool = IsBlockParamModified v24 - IfTrue v25, bb4(v9, v10, v14) - v31:CPtr = GetEP 0 - v32:CInt64 = LoadField v31, :_env_data_index_specval@0x1001 - v33:CInt64 = GuardAnyBitSet v32, CUInt64(1) - v34:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v9, v10, v14, v34) - bb4(v16:BasicObject, v17:BasicObject, v18:ArrayExact): - v28:CPtr = GetEP 0 - v29:BasicObject = LoadField v28, :block@0x1010 - Jump bb6(v16, v29, v18, v29) - bb6(v36:BasicObject, v37:BasicObject, v38:ArrayExact, v39:BasicObject): - v42:BasicObject = Send v38, &block, :map, v39 # SendFallbackReason: Complex argument passing + v18:CPtr = GetEP 0 + v19:CBool = IsBlockParamModified v18 + IfTrue v19, bb4() + v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1001 + v25:CInt64 = GuardAnyBitSet v24, CUInt64(1) + v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v26, v10) + bb4(): + v22:BasicObject = LoadField v18, :block@0x1010 + Jump bb6(v22, v22) + bb6(v16:BasicObject, v17:BasicObject): + v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing CheckInterrupts - Return v42 + Return v29 "); } @@ -8058,22 +8046,20 @@ mod hir_opt_tests { Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): v14:ArrayExact = NewArray - v24:CPtr = GetEP 0 - v25:CBool = IsBlockParamModified v24 - IfTrue v25, bb4(v9, v10, v14) - v31:CPtr = GetEP 0 - v32:CInt64 = LoadField v31, :_env_data_index_specval@0x1001 - v33:CInt64[0] = GuardBitEquals v32, CInt64(0) - v34:NilClass = Const Value(nil) - Jump bb6(v9, v10, v14, v34) - bb4(v16:BasicObject, v17:BasicObject, v18:ArrayExact): - v28:CPtr = GetEP 0 - v29:BasicObject = LoadField v28, :block@0x1002 - Jump bb6(v16, v29, v18, v29) - bb6(v36:BasicObject, v37:BasicObject, v38:ArrayExact, v39:BasicObject): - v42:BasicObject = Send v38, &block, :map, v39 # SendFallbackReason: Complex argument passing + v18:CPtr = GetEP 0 + v19:CBool = IsBlockParamModified v18 + IfTrue v19, bb4() + v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1001 + v25:CInt64[0] = GuardBitEquals v24, CInt64(0) + v26:NilClass = Const Value(nil) + Jump bb6(v26, v10) + bb4(): + v22:BasicObject = LoadField v18, :block@0x1002 + Jump bb6(v22, v22) + bb6(v16:BasicObject, v17:BasicObject): + v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing CheckInterrupts - Return v42 + Return v29 "); } @@ -8099,22 +8085,20 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - v18:CPtr = GetEP 1 - v19:CBool = IsBlockParamModified v18 - IfTrue v19, bb4(v6, v10) - v25:CPtr = GetEP 1 - v26:CInt64 = LoadField v25, :_env_data_index_specval@0x1000 - v27:CInt64 = GuardAnyBitSet v26, CUInt64(1) - v28:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v6, v10, v28) - bb4(v12:BasicObject, v13:ArrayExact): - v22:CPtr = GetEP 1 - v23:BasicObject = LoadField v22, :block@0x1010 - Jump bb6(v12, v13, v23) - bb6(v30:BasicObject, v31:ArrayExact, v32:BasicObject): - v35:BasicObject = Send v31, &block, :map, v32 # SendFallbackReason: Complex argument passing + v13:CPtr = GetEP 1 + v14:CBool = IsBlockParamModified v13 + IfTrue v14, bb4() + v19:CInt64 = LoadField v13, :_env_data_index_specval@0x1000 + v20:CInt64 = GuardAnyBitSet v19, CUInt64(1) + v21:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v21) + bb4(): + v17:BasicObject = LoadField v13, :block@0x1010 + Jump bb6(v17) + bb6(v12:BasicObject): + v24:BasicObject = Send v10, &block, :map, v12 # SendFallbackReason: Complex argument passing CheckInterrupts - Return v35 + Return v24 "); } @@ -11588,22 +11572,20 @@ mod hir_opt_tests { Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): v14:ArrayExact = NewArray - v24:CPtr = GetEP 0 - v25:CBool = IsBlockParamModified v24 - IfTrue v25, bb4(v9, v10, v14) - v31:CPtr = GetEP 0 - v32:CInt64 = LoadField v31, :_env_data_index_specval@0x1001 - v33:CInt64 = GuardAnyBitSet v32, CUInt64(1) - v34:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v9, v10, v14, v34) - bb4(v16:BasicObject, v17:BasicObject, v18:ArrayExact): - v28:CPtr = GetEP 0 - v29:BasicObject = LoadField v28, :block@0x1010 - Jump bb6(v16, v29, v18, v29) - bb6(v36:BasicObject, v37:BasicObject, v38:ArrayExact, v39:BasicObject): - v42:BasicObject = Send v38, &block, :map, v39 # SendFallbackReason: Complex argument passing + v18:CPtr = GetEP 0 + v19:CBool = IsBlockParamModified v18 + IfTrue v19, bb4() + v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1001 + v25:CInt64 = GuardAnyBitSet v24, CUInt64(1) + v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v26, v10) + bb4(): + v22:BasicObject = LoadField v18, :block@0x1010 + Jump bb6(v22, v22) + bb6(v16:BasicObject, v17:BasicObject): + v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing CheckInterrupts - Return v42 + Return v29 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index b23c7068fbc5f9..c1460c686f918f 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2466,21 +2466,19 @@ pub(crate) mod hir_build_tests { bb3(v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:NilClass): v29:ArrayExact = ToArray v19 PatchPoint NoEPEscape(test) - v56:CPtr = GetEP 0 - v57:CBool = IsBlockParamModified v56 - IfTrue v57, bb4(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - Jump bb5(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - bb4(v34:BasicObject, v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject, v39:NilClass, v40:BasicObject, v41:BasicObject, v42:ArrayExact, v43:BasicObject): - v60:CPtr = GetEP 0 - v61:BasicObject = LoadField v60, :&@0x1004 - Jump bb6(v34, v35, v36, v37, v61, v39, v40, v41, v42, v43, v61) - bb5(v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:NilClass, v51:BasicObject, v52:BasicObject, v53:ArrayExact, v54:BasicObject): - v63:CPtr = GetEP 0 - v64:CInt64 = LoadField v63, :_env_data_index_specval@0x1005 - v65:CInt64 = GuardAnyBitSet v64, CUInt64(1) - v66:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v66) - bb6(v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject, v73:NilClass, v74:BasicObject, v75:BasicObject, v76:ArrayExact, v77:BasicObject, v78:BasicObject): + v36:CPtr = GetEP 0 + v37:CBool = IsBlockParamModified v36 + IfTrue v37, bb4() + Jump bb5() + bb4(): + v40:BasicObject = LoadField v36, :&@0x1004 + Jump bb6(v40, v40) + bb5(): + v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1005 + v43:CInt64 = GuardAnyBitSet v42, CUInt64(1) + v44:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v44, v21) + bb6(v34:BasicObject, v35:BasicObject): SideExit SplatKwNotProfiled "); } @@ -3342,20 +3340,19 @@ pub(crate) mod hir_build_tests { v7:BasicObject = LoadArg :block@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v14:CPtr = GetEP 0 - v15:CBool = IsBlockParamModified v14 - IfTrue v15, bb4(v9, v10) - Jump bb5(v9, v10) - bb4(v16:BasicObject, v17:BasicObject): - v24:CPtr = GetEP 0 - v25:BasicObject = LoadField v24, :block@0x1001 - Jump bb6(v16, v25, v25) - bb5(v19:BasicObject, v20:BasicObject): - v27:BasicObject = GetBlockParam :block, l0, EP@3 - Jump bb6(v19, v27, v27) - bb6(v29:BasicObject, v30:BasicObject, v31:BasicObject): + v15:CPtr = GetEP 0 + v16:CBool = IsBlockParamModified v15 + IfTrue v16, bb4() + Jump bb5() + bb4(): + v19:BasicObject = LoadField v15, :block@0x1001 + Jump bb6(v19) + bb5(): + v21:BasicObject = GetBlockParam :block, l0, EP@3 + Jump bb6(v21) + bb6(v14:BasicObject): CheckInterrupts - Return v31 + Return v14 "); } @@ -3379,24 +3376,22 @@ pub(crate) mod hir_build_tests { v7:BasicObject = LoadArg :block@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v23:CPtr = GetEP 0 - v24:CBool = IsBlockParamModified v23 - IfTrue v24, bb4(v9, v10, v9) - Jump bb5(v9, v10, v9) - bb4(v15:BasicObject, v16:BasicObject, v17:BasicObject): - v27:CPtr = GetEP 0 - v28:BasicObject = LoadField v27, :block@0x1001 - Jump bb6(v15, v28, v17, v28) - bb5(v19:BasicObject, v20:BasicObject, v21:BasicObject): - v30:CPtr = GetEP 0 - v31:CInt64 = LoadField v30, :_env_data_index_specval@0x1002 - v32:CInt64 = GuardAnyBitSet v31, CUInt64(1) - v33:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v19, v20, v21, v33) - bb6(v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject): - v41:BasicObject = Send v37, &block, :tap, v38 # SendFallbackReason: Uncategorized(send) + v17:CPtr = GetEP 0 + v18:CBool = IsBlockParamModified v17 + IfTrue v18, bb4() + Jump bb5() + bb4(): + v21:BasicObject = LoadField v17, :block@0x1001 + Jump bb6(v21, v21) + bb5(): + v23:CInt64 = LoadField v17, :_env_data_index_specval@0x1002 + v24:CInt64 = GuardAnyBitSet v23, CUInt64(1) + v25:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v25, v10) + bb6(v15:BasicObject, v16:BasicObject): + v28:BasicObject = Send v9, &block, :tap, v15 # SendFallbackReason: Uncategorized(send) CheckInterrupts - Return v41 + Return v28 "); } @@ -3425,36 +3420,33 @@ pub(crate) mod hir_build_tests { v9:NilClass = Const Value(nil) Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:NilClass): - v17:CPtr = GetEP 0 - v18:CBool = IsBlockParamModified v17 - IfTrue v18, bb4(v11, v12, v13) - Jump bb5(v11, v12, v13) - bb4(v19:BasicObject, v20:BasicObject, v21:NilClass): - v29:CPtr = GetEP 0 - v30:BasicObject = LoadField v29, :block@0x1001 - Jump bb6(v19, v30, v21, v30) - bb5(v23:BasicObject, v24:BasicObject, v25:NilClass): - v32:BasicObject = GetBlockParam :block, l0, EP@4 - Jump bb6(v23, v32, v25, v32) - bb6(v34:BasicObject, v35:BasicObject, v36:NilClass, v37:BasicObject): - v53:CPtr = GetEP 0 - v54:CBool = IsBlockParamModified v53 - IfTrue v54, bb7(v34, v35, v37, v34) - Jump bb8(v34, v35, v37, v34) - bb7(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject): - v57:CPtr = GetEP 0 - v58:BasicObject = LoadField v57, :block@0x1001 - Jump bb9(v43, v58, v45, v46, v58) - bb8(v48:BasicObject, v49:BasicObject, v50:BasicObject, v51:BasicObject): - v60:CPtr = GetEP 0 - v61:CInt64 = LoadField v60, :_env_data_index_specval@0x1002 - v62:CInt64 = GuardAnyBitSet v61, CUInt64(1) - v63:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb9(v48, v49, v50, v51, v63) - bb9(v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject): - v72:BasicObject = Send v68, &block, :tap, v69 # SendFallbackReason: Uncategorized(send) - CheckInterrupts - Return v72 + v18:CPtr = GetEP 0 + v19:CBool = IsBlockParamModified v18 + IfTrue v19, bb4() + Jump bb5() + bb4(): + v22:BasicObject = LoadField v18, :block@0x1001 + Jump bb6(v22) + bb5(): + v24:BasicObject = GetBlockParam :block, l0, EP@4 + Jump bb6(v24) + bb6(v17:BasicObject): + v32:CPtr = GetEP 0 + v33:CBool = IsBlockParamModified v32 + IfTrue v33, bb7() + Jump bb8() + bb7(): + v36:BasicObject = LoadField v32, :block@0x1001 + Jump bb9(v36, v36) + bb8(): + v38:CInt64 = LoadField v32, :_env_data_index_specval@0x1002 + v39:CInt64 = GuardAnyBitSet v38, CUInt64(1) + v40:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb9(v40, v17) + bb9(v30:BasicObject, v31:BasicObject): + v43:BasicObject = Send v11, &block, :tap, v30 # SendFallbackReason: Uncategorized(send) + CheckInterrupts + Return v43 "); } @@ -3481,36 +3473,33 @@ pub(crate) mod hir_build_tests { v6:NilClass = Const Value(nil) Jump bb3(v5, v6) bb3(v8:BasicObject, v9:NilClass): - v13:CPtr = GetEP 1 - v14:CBool = IsBlockParamModified v13 - IfTrue v14, bb4(v8, v9) - Jump bb5(v8, v9) - bb4(v15:BasicObject, v16:NilClass): - v23:CPtr = GetEP 1 - v24:BasicObject = LoadField v23, :block@0x1000 - Jump bb6(v15, v16, v24) - bb5(v18:BasicObject, v19:NilClass): - v26:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v18, v19, v26) - bb6(v28:BasicObject, v29:NilClass, v30:BasicObject): - v44:CPtr = GetEP 1 - v45:CBool = IsBlockParamModified v44 - IfTrue v45, bb7(v28, v30, v28) - Jump bb8(v28, v30, v28) - bb7(v36:BasicObject, v37:BasicObject, v38:BasicObject): - v48:CPtr = GetEP 1 - v49:BasicObject = LoadField v48, :block@0x1000 - Jump bb9(v36, v37, v38, v49) - bb8(v40:BasicObject, v41:BasicObject, v42:BasicObject): - v51:CPtr = GetEP 1 - v52:CInt64 = LoadField v51, :_env_data_index_specval@0x1001 - v53:CInt64 = GuardAnyBitSet v52, CUInt64(1) - v54:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb9(v40, v41, v42, v54) - bb9(v56:BasicObject, v57:BasicObject, v58:BasicObject, v59:BasicObject): - v62:BasicObject = Send v58, &block, :tap, v59 # SendFallbackReason: Uncategorized(send) - CheckInterrupts - Return v62 + v14:CPtr = GetEP 1 + v15:CBool = IsBlockParamModified v14 + IfTrue v15, bb4() + Jump bb5() + bb4(): + v18:BasicObject = LoadField v14, :block@0x1000 + Jump bb6(v18) + bb5(): + v20:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v20) + bb6(v13:BasicObject): + v27:CPtr = GetEP 1 + v28:CBool = IsBlockParamModified v27 + IfTrue v28, bb7() + Jump bb8() + bb7(): + v31:BasicObject = LoadField v27, :block@0x1000 + Jump bb9(v31) + bb8(): + v33:CInt64 = LoadField v27, :_env_data_index_specval@0x1001 + v34:CInt64 = GuardAnyBitSet v33, CUInt64(1) + v35:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb9(v35) + bb9(v26:BasicObject): + v38:BasicObject = Send v8, &block, :tap, v26 # SendFallbackReason: Uncategorized(send) + CheckInterrupts + Return v38 "); } @@ -3534,20 +3523,19 @@ pub(crate) mod hir_build_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v10:CPtr = GetEP 1 - v11:CBool = IsBlockParamModified v10 - IfTrue v11, bb4(v6) - Jump bb5(v6) - bb4(v12:BasicObject): - v18:CPtr = GetEP 1 - v19:BasicObject = LoadField v18, :block@0x1000 - Jump bb6(v12, v19) - bb5(v14:BasicObject): - v21:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v14, v21) - bb6(v23:BasicObject, v24:BasicObject): + v11:CPtr = GetEP 1 + v12:CBool = IsBlockParamModified v11 + IfTrue v12, bb4() + Jump bb5() + bb4(): + v15:BasicObject = LoadField v11, :block@0x1000 + Jump bb6(v15) + bb5(): + v17:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v17) + bb6(v10:BasicObject): CheckInterrupts - Return v24 + Return v10 "); } @@ -3640,21 +3628,19 @@ pub(crate) mod hir_build_tests { v9:BasicObject = LoadArg :b@2 Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): - v31:CPtr = GetEP 0 - v32:CBool = IsBlockParamModified v31 - IfTrue v32, bb4(v11, v12, v13, v11, v12) - Jump bb5(v11, v12, v13, v11, v12) - bb4(v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject): - v35:CPtr = GetEP 0 - v36:BasicObject = LoadField v35, :b@0x1002 - Jump bb6(v19, v20, v36, v22, v23, v36) - bb5(v25:BasicObject, v26:BasicObject, v27:BasicObject, v28:BasicObject, v29:BasicObject): - v38:CPtr = GetEP 0 - v39:CInt64 = LoadField v38, :_env_data_index_specval@0x1003 - v40:CInt64 = GuardAnyBitSet v39, CUInt64(1) - v41:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v25, v26, v27, v28, v29, v41) - bb6(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject): + v21:CPtr = GetEP 0 + v22:CBool = IsBlockParamModified v21 + IfTrue v22, bb4() + Jump bb5() + bb4(): + v25:BasicObject = LoadField v21, :b@0x1002 + Jump bb6(v25, v25) + bb5(): + v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1003 + v28:CInt64 = GuardAnyBitSet v27, CUInt64(1) + v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v29, v13) + bb6(v19:BasicObject, v20:BasicObject): SideExit SplatKwNotProfiled "); } @@ -3691,25 +3677,23 @@ pub(crate) mod hir_build_tests { bb3(v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:NilClass): v29:ArrayExact = ToArray v19 PatchPoint NoEPEscape(test) - v56:CPtr = GetEP 0 - v57:CBool = IsBlockParamModified v56 - IfTrue v57, bb4(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - Jump bb5(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - bb4(v34:BasicObject, v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject, v39:NilClass, v40:BasicObject, v41:BasicObject, v42:ArrayExact, v43:BasicObject): - v60:CPtr = GetEP 0 - v61:BasicObject = LoadField v60, :&@0x1004 - Jump bb6(v34, v35, v36, v37, v61, v39, v40, v41, v42, v43, v61) - bb5(v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:NilClass, v51:BasicObject, v52:BasicObject, v53:ArrayExact, v54:BasicObject): - v63:CPtr = GetEP 0 - v64:CInt64 = LoadField v63, :_env_data_index_specval@0x1005 - v65:CInt64[0] = GuardBitEquals v64, CInt64(0) - v66:NilClass = Const Value(nil) - Jump bb6(v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v66) - bb6(v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject, v73:NilClass, v74:BasicObject, v75:BasicObject, v76:ArrayExact, v77:BasicObject, v78:BasicObject): - v81:NilClass = GuardType v77, NilClass - v83:BasicObject = Send v74, &block, :foo, v75, v76, v81, v78 # SendFallbackReason: Uncategorized(send) + v36:CPtr = GetEP 0 + v37:CBool = IsBlockParamModified v36 + IfTrue v37, bb4() + Jump bb5() + bb4(): + v40:BasicObject = LoadField v36, :&@0x1004 + Jump bb6(v40, v40) + bb5(): + v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1005 + v43:CInt64[0] = GuardBitEquals v42, CInt64(0) + v44:NilClass = Const Value(nil) + Jump bb6(v44, v21) + bb6(v34:BasicObject, v35:BasicObject): + v47:NilClass = GuardType v20, NilClass + v49:BasicObject = Send v17, &block, :foo, v18, v29, v47, v34 # SendFallbackReason: Uncategorized(send) CheckInterrupts - Return v83 + Return v49 "); } @@ -3737,25 +3721,23 @@ pub(crate) mod hir_build_tests { v9:BasicObject = LoadArg :b@2 Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): - v31:CPtr = GetEP 0 - v32:CBool = IsBlockParamModified v31 - IfTrue v32, bb4(v11, v12, v13, v11, v12) - Jump bb5(v11, v12, v13, v11, v12) - bb4(v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject): - v35:CPtr = GetEP 0 - v36:BasicObject = LoadField v35, :b@0x1002 - Jump bb6(v19, v20, v36, v22, v23, v36) - bb5(v25:BasicObject, v26:BasicObject, v27:BasicObject, v28:BasicObject, v29:BasicObject): - v38:CPtr = GetEP 0 - v39:CInt64 = LoadField v38, :_env_data_index_specval@0x1003 - v40:CInt64 = GuardAnyBitSet v39, CUInt64(1) - v41:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v25, v26, v27, v28, v29, v41) - bb6(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject): - v51:HashExact = GuardType v47, HashExact - v53:BasicObject = Send v46, &block, :foo, v51, v48 # SendFallbackReason: Uncategorized(send) + v21:CPtr = GetEP 0 + v22:CBool = IsBlockParamModified v21 + IfTrue v22, bb4() + Jump bb5() + bb4(): + v25:BasicObject = LoadField v21, :b@0x1002 + Jump bb6(v25, v25) + bb5(): + v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1003 + v28:CInt64 = GuardAnyBitSet v27, CUInt64(1) + v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v29, v13) + bb6(v19:BasicObject, v20:BasicObject): + v32:HashExact = GuardType v12, HashExact + v34:BasicObject = Send v11, &block, :foo, v32, v19 # SendFallbackReason: Uncategorized(send) CheckInterrupts - Return v53 + Return v34 "); } @@ -3783,25 +3765,23 @@ pub(crate) mod hir_build_tests { v9:BasicObject = LoadArg :b@2 Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): - v31:CPtr = GetEP 0 - v32:CBool = IsBlockParamModified v31 - IfTrue v32, bb4(v11, v12, v13, v11, v12) - Jump bb5(v11, v12, v13, v11, v12) - bb4(v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject): - v35:CPtr = GetEP 0 - v36:BasicObject = LoadField v35, :b@0x1002 - Jump bb6(v19, v20, v36, v22, v23, v36) - bb5(v25:BasicObject, v26:BasicObject, v27:BasicObject, v28:BasicObject, v29:BasicObject): - v38:CPtr = GetEP 0 - v39:CInt64 = LoadField v38, :_env_data_index_specval@0x1003 - v40:CInt64 = GuardAnyBitSet v39, CUInt64(1) - v41:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v25, v26, v27, v28, v29, v41) - bb6(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject): - v51:HashExact = GuardType v47, HashExact - v53:BasicObject = Send v46, &block, :foo, v51, v48 # SendFallbackReason: Uncategorized(send) + v21:CPtr = GetEP 0 + v22:CBool = IsBlockParamModified v21 + IfTrue v22, bb4() + Jump bb5() + bb4(): + v25:BasicObject = LoadField v21, :b@0x1002 + Jump bb6(v25, v25) + bb5(): + v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1003 + v28:CInt64 = GuardAnyBitSet v27, CUInt64(1) + v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v29, v13) + bb6(v19:BasicObject, v20:BasicObject): + v32:HashExact = GuardType v12, HashExact + v34:BasicObject = Send v11, &block, :foo, v32, v19 # SendFallbackReason: Uncategorized(send) CheckInterrupts - Return v53 + Return v34 "); } @@ -3839,21 +3819,19 @@ pub(crate) mod hir_build_tests { bb3(v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:NilClass): v29:ArrayExact = ToArray v19 PatchPoint NoEPEscape(test) - v56:CPtr = GetEP 0 - v57:CBool = IsBlockParamModified v56 - IfTrue v57, bb4(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - Jump bb5(v17, v18, v19, v20, v21, v22, v17, v18, v29, v20) - bb4(v34:BasicObject, v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject, v39:NilClass, v40:BasicObject, v41:BasicObject, v42:ArrayExact, v43:BasicObject): - v60:CPtr = GetEP 0 - v61:BasicObject = LoadField v60, :&@0x1004 - Jump bb6(v34, v35, v36, v37, v61, v39, v40, v41, v42, v43, v61) - bb5(v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject, v49:BasicObject, v50:NilClass, v51:BasicObject, v52:BasicObject, v53:ArrayExact, v54:BasicObject): - v63:CPtr = GetEP 0 - v64:CInt64 = LoadField v63, :_env_data_index_specval@0x1005 - v65:CInt64[0] = GuardBitEquals v64, CInt64(0) - v66:NilClass = Const Value(nil) - Jump bb6(v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v66) - bb6(v68:BasicObject, v69:BasicObject, v70:BasicObject, v71:BasicObject, v72:BasicObject, v73:NilClass, v74:BasicObject, v75:BasicObject, v76:ArrayExact, v77:BasicObject, v78:BasicObject): + v36:CPtr = GetEP 0 + v37:CBool = IsBlockParamModified v36 + IfTrue v37, bb4() + Jump bb5() + bb4(): + v40:BasicObject = LoadField v36, :&@0x1004 + Jump bb6(v40, v40) + bb5(): + v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1005 + v43:CInt64[0] = GuardBitEquals v42, CInt64(0) + v44:NilClass = Const Value(nil) + Jump bb6(v44, v21) + bb6(v34:BasicObject, v35:BasicObject): SideExit SplatKwPolymorphic "); } @@ -3884,21 +3862,19 @@ pub(crate) mod hir_build_tests { v9:BasicObject = LoadArg :block@2 Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): - v31:CPtr = GetEP 0 - v32:CBool = IsBlockParamModified v31 - IfTrue v32, bb4(v11, v12, v13, v11, v12) - Jump bb5(v11, v12, v13, v11, v12) - bb4(v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject): - v35:CPtr = GetEP 0 - v36:BasicObject = LoadField v35, :block@0x1002 - Jump bb6(v19, v20, v36, v22, v23, v36) - bb5(v25:BasicObject, v26:BasicObject, v27:BasicObject, v28:BasicObject, v29:BasicObject): - v38:CPtr = GetEP 0 - v39:CInt64 = LoadField v38, :_env_data_index_specval@0x1003 - v40:CInt64 = GuardAnyBitSet v39, CUInt64(1) - v41:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb6(v25, v26, v27, v28, v29, v41) - bb6(v43:BasicObject, v44:BasicObject, v45:BasicObject, v46:BasicObject, v47:BasicObject, v48:BasicObject): + v21:CPtr = GetEP 0 + v22:CBool = IsBlockParamModified v21 + IfTrue v22, bb4() + Jump bb5() + bb4(): + v25:BasicObject = LoadField v21, :block@0x1002 + Jump bb6(v25, v25) + bb5(): + v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1003 + v28:CInt64 = GuardAnyBitSet v27, CUInt64(1) + v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb6(v29, v13) + bb6(v19:BasicObject, v20:BasicObject): SideExit SplatKwNotNilOrHash "); } @@ -4576,33 +4552,31 @@ pub(crate) mod hir_build_tests { bb3(v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:NilClass): v27:BasicObject = InvokeBuiltin dir_s_open, v18, v19, v20 PatchPoint NoEPEscape(open) - v47:CPtr = GetEP 0 - v48:CBool = IsBlockParamModified v47 - IfTrue v48, bb5(v18, v19, v20, v21, v22, v27) - Jump bb6(v18, v19, v20, v21, v22, v27) - bb5(v33:BasicObject, v34:BasicObject, v35:BasicObject, v36:BasicObject, v37:BasicObject, v38:BasicObject): - v51:CPtr = GetEP 0 - v52:BasicObject = LoadField v51, :block@0x1004 - Jump bb7(v33, v34, v35, v36, v52, v38, v52) - bb6(v40:BasicObject, v41:BasicObject, v42:BasicObject, v43:BasicObject, v44:BasicObject, v45:BasicObject): - v54:CPtr = GetEP 0 - v55:CInt64 = LoadField v54, :_env_data_index_specval@0x1005 - v56:CInt64 = GuardAnyBitSet v55, CUInt64(1) - v57:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) - Jump bb7(v40, v41, v42, v43, v44, v45, v57) - bb7(v59:BasicObject, v60:BasicObject, v61:BasicObject, v62:BasicObject, v63:BasicObject, v64:BasicObject, v65:BasicObject): - CheckInterrupts - v69:CBool = Test v65 - v70:Falsy = RefineType v65, Falsy - IfFalse v69, bb4(v59, v60, v61, v62, v63, v64) - v72:Truthy = RefineType v65, Truthy - v76:BasicObject = InvokeBlock, v64 # SendFallbackReason: InvokeBlock: not yet specialized - v79:BasicObject = InvokeBuiltin dir_s_close, v59, v64 - CheckInterrupts - Return v76 - bb4(v85:BasicObject, v86:BasicObject, v87:BasicObject, v88:BasicObject, v89:BasicObject, v90:BasicObject): - CheckInterrupts - Return v90 + v35:CPtr = GetEP 0 + v36:CBool = IsBlockParamModified v35 + IfTrue v36, bb5() + Jump bb6() + bb5(): + v39:BasicObject = LoadField v35, :block@0x1004 + Jump bb7(v39, v39) + bb6(): + v41:CInt64 = LoadField v35, :_env_data_index_specval@0x1005 + v42:CInt64 = GuardAnyBitSet v41, CUInt64(1) + v43:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) + Jump bb7(v43, v22) + bb7(v33:BasicObject, v34:BasicObject): + CheckInterrupts + v47:CBool = Test v33 + v48:Falsy = RefineType v33, Falsy + IfFalse v47, bb4(v18, v19, v20, v21, v34, v27) + v50:Truthy = RefineType v33, Truthy + v54:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized + v57:BasicObject = InvokeBuiltin dir_s_close, v18, v27 + CheckInterrupts + Return v54 + bb4(v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject): + CheckInterrupts + Return v68 "); } From bcddc56fb89b877c4986370fbaca10a4485285a6 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 31 Mar 2026 12:43:02 -0700 Subject: [PATCH 10/20] vm: avoid allocator lookup for singleton class initialize methods This fixes a SIGABRT of Assertion Failed: RCLASS_ALLOCATOR:!RCLASS_SINGLETON_P(klass) --- test/ruby/test_class.rb | 10 ++++++++++ vm_insnhelper.c | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index cc2e1ab2324e33..61182b990b3abf 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -888,6 +888,16 @@ class C; end end; end + def test_define_singleton_initialize + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class C + def self.initialize + end + end + end; + end + def test_singleton_cc_invalidation assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") begin; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 2c9bb027528b13..e3f627ae896cff 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6048,7 +6048,9 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv rb_add_method_iseq(klass, id, (const rb_iseq_t *)iseqval, cref, visi); // Set max_iv_count on klasses based on number of ivar sets that are in the initialize method - if (id == idInitialize && klass != rb_cObject && RB_TYPE_P(klass, T_CLASS) && (rb_get_alloc_func(klass) == rb_class_allocate_instance)) { + if (id == idInitialize && klass != rb_cObject && RB_TYPE_P(klass, T_CLASS) && + !RCLASS_SINGLETON_P(klass) && + (rb_get_alloc_func(klass) == rb_class_allocate_instance)) { RCLASS_SET_MAX_IV_COUNT(klass, rb_estimate_iv_count(klass, (const rb_iseq_t *)iseqval)); } From c9ae44e109b1673057496d15c9414319365d7e6f Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 31 Mar 2026 13:14:33 -0700 Subject: [PATCH 11/20] set: fix Set#to_set block behavior and add regression test --- set.c | 2 +- test/ruby/test_set.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/set.c b/set.c index 6bfded02a414ee..8f889529e7e7d3 100644 --- a/set.c +++ b/set.c @@ -655,7 +655,7 @@ set_i_to_set(VALUE set) return set; } - return rb_funcall_passing_block(rb_cSet, id_new, 0, NULL); + return rb_funcall_passing_block(rb_cSet, id_new, 1, &set); } /* diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 70a61aa3b5d5fe..46d649ee737b0a 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -978,7 +978,8 @@ def test_to_set assert_equal([-10,-8,-6,-4,-2], set.sort) assert_same set, set.to_set - assert_not_same set, set.to_set { |o| o } + transformed = set.to_set { |o| o + 1 } + assert_equal([-9,-7,-5,-3,-1], transformed.sort) end class MyEnum From e191769eacb30ac4e86e1e4573f04867cc7d3593 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 16:03:57 -0700 Subject: [PATCH 12/20] auto_request_review.yml: Clarify the meaning of the "Skip" comment by explicitly adding no-op entries, rather than leaving the line empty, which may seem like lines that have been deleted. --- .github/auto_request_review.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 38496d5cebbebe..390e4817cbee99 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -12,6 +12,8 @@ files: 'tool/zjit_bisect.rb': [team:jit] 'doc/jit/*': [team:jit] # Skip github workflow files because the team don't necessarily need to review dependabot updates for GitHub Actions. It's noisy in notifications, and they're auto-merged anyway. + '.github/workflows/yjit-*.yml': [] + '.github/workflows/zjit-*.yml': [] options: ignore_draft: true # This currently doesn't work as intended. We want to skip reviews when only From 3a0b003ae2f99491b628d8a4774d7e272dc21f83 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 16:01:28 -0700 Subject: [PATCH 13/20] auto_request_review.yml: Fix and use last_files_match_only https://github.com/necojackarc/auto-request-review/pull/135 fixes last_files_match_only. Let's use a fork to fix it until it gets merged. --- .github/auto_request_review.yml | 5 +---- .github/workflows/auto_request_review.yml | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 390e4817cbee99..b82cd3fbda5508 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -16,7 +16,4 @@ files: '.github/workflows/zjit-*.yml': [] options: ignore_draft: true - # This currently doesn't work as intended. We want to skip reviews when only - # cruby_bingings.inc.rs is modified, but this skips reviews even when other - # files are modified as well. To be enabled after fixing the behavior. - #last_files_match_only: true + last_files_match_only: true diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml index 207315a084cc59..e5389bcf22b7bf 100644 --- a/.github/workflows/auto_request_review.yml +++ b/.github/workflows/auto_request_review.yml @@ -14,7 +14,8 @@ jobs: if: ${{ github.repository == 'ruby/ruby' && github.base_ref == 'master' }} steps: - name: Request review based on files changes and/or groups the author belongs to - uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0 + # Using a fork until https://github.com/necojackarc/auto-request-review/pull/135 is merged + uses: k0kubun/auto-request-review@0df295a0ff5c5d302770f589497280132131c63d # master with: # scope: public_repo token: ${{ secrets.MATZBOT_AUTO_REQUEST_REVIEW_TOKEN }} From 78f11bdee13a6d436b5e009154408e88a763ce27 Mon Sep 17 00:00:00 2001 From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:32:34 +0900 Subject: [PATCH 14/20] ZJIT: Support VM_OPT_NEWARRAY_SEND_PACK (#16596) --- zjit/src/codegen.rs | 14 +++++++---- zjit/src/codegen_tests.rs | 33 +++++++++++++++++++++++++ zjit/src/hir.rs | 25 ++++++++++++++----- zjit/src/hir/tests.rs | 51 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 111 insertions(+), 12 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index aadb2da8cff9c4..2cde7388fbe127 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -747,7 +747,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(jit, asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven { lep } => gen_is_block_given(asm, opnd!(lep)), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), - Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)), + Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), (*buffer).map(|buffer| opnd!(buffer)), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), Insn::ArrayHash { elements, state } => gen_opt_newarray_hash(jit, asm, opnds!(elements), &function.frame_state(*state)), &Insn::IsA { val, class } => gen_is_a(jit, asm, opnd!(val), opnd!(class)), @@ -2047,7 +2047,7 @@ fn gen_array_pack_buffer( asm: &mut Assembler, elements: Vec, fmt: Opnd, - buffer: Opnd, + buffer: Option, state: &FrameState, ) -> lir::Opnd { gen_prepare_non_leaf_call(jit, asm, state); @@ -2055,9 +2055,13 @@ fn gen_array_pack_buffer( let array_len: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); // After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack. - // The elements are at the bottom of the virtual stack, followed by the fmt, followed by the buffer. + // The elements are at the bottom of the virtual stack, followed by the fmt, and optionally the buffer. // Get a pointer to the first element on the Ruby stack. - let stack_bottom = state.stack().len() - elements.len() - 2; + let stack_bottom = if buffer.is_some() { + state.stack().len() - elements.len() - 2 + } else { + state.stack().len() - elements.len() - 1 + }; let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32)); unsafe extern "C" { @@ -2066,7 +2070,7 @@ fn gen_array_pack_buffer( asm_ccall!( asm, rb_vm_opt_newarray_pack_buffer, - EC, array_len.into(), elements_ptr, fmt, buffer + EC, array_len.into(), elements_ptr, fmt, buffer.unwrap_or_else(|| Qundef.into()) ) } diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 4dbc42a5b41192..c15fa3c8c4aa66 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -2399,6 +2399,39 @@ fn test_opt_duparray_send_include_p_redefined() { "), @"[:true, :false]"); } +#[test] +fn test_opt_newarray_send_pack() { + eval(r#" + def test(num) + [num].pack('C') + end + test(65) + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles(r#" + [test(65), test(66), test(67)] + "#), @r#"["A", "B", "C"]"#); +} + +#[test] +fn test_opt_newarray_send_pack_redefined() { + eval(r#" + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + "override:#{old_pack(fmt, buffer: buffer)}" + end + end + def test(num) + [num].pack('C') + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles(r#" + [test(65), test(66), test(67)] + "#), @r#"["override:A", "override:B", "override:C"]"#); +} + #[test] fn test_opt_newarray_send_pack_buffer() { eval(r#" diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6dc0589f6366a0..47b7915424e963 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -820,7 +820,7 @@ pub enum Insn { ArrayMax { elements: Vec, state: InsnId }, ArrayMin { elements: Vec, state: InsnId }, ArrayInclude { elements: Vec, target: InsnId, state: InsnId }, - ArrayPackBuffer { elements: Vec, fmt: InsnId, buffer: InsnId, state: InsnId }, + ArrayPackBuffer { elements: Vec, fmt: InsnId, buffer: Option, state: InsnId }, DupArrayInclude { ary: VALUE, target: InsnId, state: InsnId }, /// Extend `left` with the elements from `right`. `left` and `right` must both be `Array`. ArrayExtend { left: InsnId, right: InsnId, state: InsnId }, @@ -1179,7 +1179,9 @@ macro_rules! for_each_operand_impl { Insn::ArrayPackBuffer { elements, fmt, buffer, state, .. } => { $visit_many!(elements); $visit_one!(fmt); - $visit_one!(buffer); + if let Some(buffer) = buffer { + $visit_one!(buffer); + } $visit_one!(state); } Insn::DupArrayInclude { target, state, .. } => { @@ -1831,7 +1833,11 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { for element in elements { write!(f, "{element}, ")?; } - write!(f, "fmt: {fmt}, buf: {buffer}") + write!(f, "fmt: {fmt}")?; + if let Some(buffer) = buffer { + write!(f, ", buf: {buffer}")?; + } + Ok(()) } Insn::DupArrayInclude { ary, target, .. } => { write!(f, "DupArrayInclude {} | {}", ary.print(self.ptr_map), target) @@ -2917,7 +2923,7 @@ impl Function { &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &ArrayMin { ref elements, state } => ArrayMin { elements: find_vec!(elements), state: find!(state) }, &ArrayInclude { ref elements, target, state } => ArrayInclude { elements: find_vec!(elements), target: find!(target), state: find!(state) }, - &ArrayPackBuffer { ref elements, fmt, buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: find!(buffer), state: find!(state) }, + &ArrayPackBuffer { ref elements, fmt, ref buffer, state } => ArrayPackBuffer { elements: find_vec!(elements), fmt: find!(fmt), buffer: (*buffer).map(|buffer| find!(buffer)), state: find!(state) }, &DupArrayInclude { ary, target, state } => DupArrayInclude { ary, target: find!(target), state: find!(state) }, &ArrayHash { ref elements, state } => ArrayHash { elements: find_vec!(elements), state }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, @@ -6161,7 +6167,9 @@ impl Function { } Insn::ArrayPackBuffer { ref elements, fmt, buffer, .. } => { self.assert_subtype(insn_id, fmt, types::BasicObject)?; - self.assert_subtype(insn_id, buffer, types::BasicObject)?; + if let Some(buffer) = buffer { + self.assert_subtype(insn_id, buffer, types::BasicObject)?; + } for &element in elements { self.assert_subtype(insn_id, element, types::BasicObject)?; } @@ -7141,11 +7149,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let elements = state.stack_pop_n(count - 1)?; (BOP_INCLUDE_P, Insn::ArrayInclude { elements, target, state: exit_id }) } + VM_OPT_NEWARRAY_SEND_PACK => { + let fmt = state.stack_pop()?; + let elements = state.stack_pop_n(count - 1)?; + (BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer: None, state: exit_id }) + } VM_OPT_NEWARRAY_SEND_PACK_BUFFER => { let buffer = state.stack_pop()?; let fmt = state.stack_pop()?; let elements = state.stack_pop_n(count - 2)?; - (BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer, state: exit_id }) + (BOP_PACK, Insn::ArrayPackBuffer { elements, fmt, buffer: Some(buffer), state: exit_id }) } _ => { // Unknown opcode; side-exit into the interpreter diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index c1460c686f918f..bcb05f0f196d0d 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2808,7 +2808,56 @@ pub(crate) mod hir_build_tests { v26:BasicObject = Send v16, :+, v17 # SendFallbackReason: Uncategorized(opt_plus) v32:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v33:StringExact = StringCopy v32 - SideExit UnhandledNewarraySend(PACK) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK) + v36:String = ArrayPackBuffer v16, v17, fmt: v33 + PatchPoint NoEPEscape(test) + v43:ArrayExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v44:ArrayExact = ArrayDup v43 + v46:BasicObject = Send v15, :puts, v44 # SendFallbackReason: Uncategorized(opt_send_without_block) + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v36 + "); + } + + #[test] + fn test_opt_newarray_send_pack_redefined() { + eval(r#" + class Array + def pack(fmt, buffer: nil) = 5 + end + def test(a,b) + sum = a+b + result = [a,b].pack 'C' + puts [1,2,3] + result + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @" + fn test@:6: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + v4:BasicObject = LoadField v2, :b@0x1001 + v5:NilClass = Const Value(nil) + v6:NilClass = Const Value(nil) + Jump bb3(v1, v3, v4, v5, v6) + bb2(): + EntryPoint JIT(0) + v9:BasicObject = LoadArg :self@0 + v10:BasicObject = LoadArg :a@1 + v11:BasicObject = LoadArg :b@2 + v12:NilClass = Const Value(nil) + v13:NilClass = Const Value(nil) + Jump bb3(v9, v10, v11, v12, v13) + bb3(v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:NilClass, v19:NilClass): + v26:BasicObject = Send v16, :+, v17 # SendFallbackReason: Uncategorized(opt_plus) + v32:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v33:StringExact = StringCopy v32 + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_PACK)) "); } From d926f4077568c30eec1342f3cf522667509a5a13 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 30 Mar 2026 19:18:41 -0400 Subject: [PATCH 15/20] [ruby/mmtk] Add moving_gc_count to GC.stat Outputs the number of GC cycles that are moving. https://github.com/ruby/mmtk/commit/fef8f04186 --- gc/mmtk/mmtk.c | 9 +++++++-- gc/mmtk/mmtk.h | 2 +- gc/mmtk/src/abi.rs | 2 +- gc/mmtk/src/collection.rs | 6 ++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 9b09116cd8d0fe..1dacd95ab5dedc 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -21,6 +21,7 @@ struct objspace { bool gc_stress; size_t gc_count; + size_t moving_gc_count; size_t total_gc_time; size_t total_allocated_objects; @@ -132,7 +133,7 @@ rb_mmtk_stop_the_world(void) } static void -rb_mmtk_resume_mutators(void) +rb_mmtk_resume_mutators(bool current_gc_may_move) { struct objspace *objspace = rb_gc_get_objspace(); @@ -143,6 +144,7 @@ rb_mmtk_resume_mutators(void) objspace->world_stopped = false; objspace->gc_count++; + if (current_gc_may_move) objspace->moving_gc_count++; pthread_cond_broadcast(&objspace->cond_world_started); if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) { @@ -492,7 +494,7 @@ rb_mmtk_gc_thread_bug(const char *msg, ...) rb_gc_print_backtrace(); fprintf(stderr, "\n"); - rb_mmtk_resume_mutators(); + rb_mmtk_resume_mutators(false); sleep(5); @@ -1459,6 +1461,7 @@ rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE hash_or_key) enum gc_stat_sym { gc_stat_sym_count, + gc_stat_sym_moving_gc_count, gc_stat_sym_time, gc_stat_sym_total_allocated_objects, gc_stat_sym_total_bytes, @@ -1478,6 +1481,7 @@ setup_gc_stat_symbols(void) if (gc_stat_symbols[0] == 0) { #define S(s) gc_stat_symbols[gc_stat_sym_##s] = ID2SYM(rb_intern_const(#s)) S(count); + S(moving_gc_count); S(time); S(total_allocated_objects); S(total_bytes); @@ -1514,6 +1518,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) rb_hash_aset(hash, gc_stat_symbols[gc_stat_sym_##name], SIZET2NUM(attr)); SET(count, objspace->gc_count); + SET(moving_gc_count, objspace->moving_gc_count); SET(time, objspace->total_gc_time / (1000 * 1000)); SET(total_allocated_objects, objspace->total_allocated_objects); SET(total_bytes, mmtk_total_bytes()); diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index 20d268419ddea0..ee338c87efe15e 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -60,7 +60,7 @@ typedef struct MMTk_RubyUpcalls { void (*init_gc_worker_thread)(struct MMTk_GCThreadTLS *gc_worker_tls); bool (*is_mutator)(void); void (*stop_the_world)(void); - void (*resume_mutators)(void); + void (*resume_mutators)(bool gc_may_move); void (*block_for_gc)(MMTk_VMMutatorThread tls); void (*before_updating_jit_code)(void); void (*after_updating_jit_code)(void); diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 2a0e9113fad2f2..c880302080307b 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -301,7 +301,7 @@ pub struct RubyUpcalls { pub init_gc_worker_thread: extern "C" fn(gc_worker_tls: *mut GCThreadTLS), pub is_mutator: extern "C" fn() -> bool, pub stop_the_world: extern "C" fn(), - pub resume_mutators: extern "C" fn(), + pub resume_mutators: extern "C" fn(gc_may_move: bool), pub block_for_gc: extern "C" fn(tls: VMMutatorThread), pub before_updating_jit_code: extern "C" fn(), pub after_updating_jit_code: extern "C" fn(), diff --git a/gc/mmtk/src/collection.rs b/gc/mmtk/src/collection.rs index 83d046aef43092..28daa4f9914256 100644 --- a/gc/mmtk/src/collection.rs +++ b/gc/mmtk/src/collection.rs @@ -48,11 +48,13 @@ impl Collection for VMCollection { } fn resume_mutators(_tls: VMWorkerThread) { - if CURRENT_GC_MAY_MOVE.load(Ordering::Relaxed) { + let current_gc_may_move = CURRENT_GC_MAY_MOVE.load(Ordering::Relaxed); + + if current_gc_may_move { (upcalls().after_updating_jit_code)(); } - (upcalls().resume_mutators)(); + (upcalls().resume_mutators)(current_gc_may_move); } fn block_for_gc(tls: VMMutatorThread) { From 6a5a2612fd6b0b8b707e0285d1a172e01469078c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 17:18:39 -0700 Subject: [PATCH 16/20] variable.c: Fix -Wmaybe-uninitialized warnings in rb_gvar_set/rb_gvar_get Initialize `entry` and `var` to NULL. They are assigned inside RB_VM_LOCKING() but used after it, and the compiler cannot prove the locked block always executes. --- variable.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variable.c b/variable.c index 9d0e4e4a2b9eac..1856095f8c33a5 100644 --- a/variable.c +++ b/variable.c @@ -1008,7 +1008,7 @@ VALUE rb_gvar_set(ID id, VALUE val) { VALUE retval; - struct rb_global_entry *entry; + struct rb_global_entry *entry = NULL; const rb_box_t *box = rb_current_box(); bool use_box_tbl = false; @@ -1041,8 +1041,8 @@ rb_gvar_get(ID id) VALUE retval, gvars, key; const rb_box_t *box = rb_current_box(); bool use_box_tbl = false; - struct rb_global_entry *entry; - struct rb_global_variable *var; + struct rb_global_entry *entry = NULL; + struct rb_global_variable *var = NULL; // TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist) RB_VM_LOCKING() { entry = rb_global_entry(id); From 1389a36b51542f08cb031bd192d35122a0a70c07 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 17:18:42 -0700 Subject: [PATCH 17/20] sprintf.c: Fix -Wmaybe-uninitialized warnings in rb_str_format Initialize `c` and `encidx` to 0. They are assigned inside `if (n >= 0)` and the following `if (n <= 0)` calls rb_raise, but the compiler cannot see through the noreturn guarantee. --- sprintf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sprintf.c b/sprintf.c index de88a9f4b35a20..234aff76f5d3a1 100644 --- a/sprintf.c +++ b/sprintf.c @@ -440,8 +440,8 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt) { VALUE val = GETARG(); VALUE tmp; - unsigned int c; - int n, encidx; + unsigned int c = 0; + int n, encidx = 0; tmp = rb_check_string_type(val); if (!NIL_P(tmp)) { From f6ddb9c6f1138588be9ee27af036f6e5e047af15 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 17:19:03 -0700 Subject: [PATCH 18/20] prism_compile.c: Fix -Wmaybe-uninitialized warning in pm_compile_call Initialize `end_cursor` to 0. It is guarded by `end_found` but the compiler cannot prove `end_found` is always true when `end_cursor` is read. --- prism_compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index 59514697d0c3c4..85e8a2cdfa40d7 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3749,7 +3749,7 @@ pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *c if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { if (PM_BRANCH_COVERAGE_P(iseq)) { - uint32_t end_cursor; + uint32_t end_cursor = 0; bool end_found = false; if (call_node->closing_loc.length > 0) { From 88efe89d568f9fa7b3c263e0f4c1d67b935ca27d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 17:29:07 -0700 Subject: [PATCH 19/20] post_push.yml: Use continue-on-error for flaky steps "Sync git.ruby-lang.org" and "Push PR notes to GitHub" steps can fail due to transient issues, which blocks execution of all subsequent steps. Use continue-on-error so the rest of the workflow still runs, and add a "Check for failures" step at the end to ensure the overall CI status is still red when either step fails. example: https://github.com/ruby/ruby/actions/runs/23825370964/job/69447008480 https://github.com/ruby/ruby/actions/runs/23671724888/job/68966478448 --- .github/workflows/post_push.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 29736240ae3158..237679ebb1a810 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -15,6 +15,8 @@ jobs: if: ${{ github.repository == 'ruby/ruby' }} steps: - name: Sync git.ruby-lang.org + id: sync-git + continue-on-error: true run: | mkdir -p ~/.ssh (umask 066; printenv RUBY_GIT_SYNC_PRIVATE_KEY > ~/.ssh/id_ed25519) @@ -73,6 +75,8 @@ jobs: if: ${{ github.ref == 'refs/heads/master' }} - name: Push PR notes to GitHub + id: pr-notes + continue-on-error: true run: ruby tool/notes-github-pr.rb "$(pwd)/.git" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master env: GITHUB_OLD_SHA: ${{ github.event.before }} @@ -83,6 +87,10 @@ jobs: EMAIL: svn-admin@ruby-lang.org if: ${{ github.ref == 'refs/heads/master' }} + - name: Check for failures + run: exit 1 + if: ${{ steps.sync-git.outcome == 'failure' || steps.pr-notes.outcome == 'failure' }} + - uses: ./.github/actions/slack with: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot From 7683ab7585b9a1b214e837fbe8a4b4fba27a6c4a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 31 Mar 2026 17:36:22 -0700 Subject: [PATCH 20/20] ZJIT: Side-exit sends with blocks to non-block methods (#16624) When the caller passes a block to a method that either rejects blocks (`&nil` parameter, `accepts_no_block`) or doesn't use them (`use_block` is false), ZJIT now falls back to the interpreter instead of compiling a direct send. This ensures: 1. Methods defined with `&nil` properly raise ArgumentError 2. Unused block warnings are properly emitted Previously, ZJIT would compile these sends directly, skipping the block validation and warning logic that the interpreter handles. --- zjit/src/codegen_tests.rs | 34 ++++++++++++++++++++++++++++++++++ zjit/src/hir.rs | 18 ++++++++++++++---- zjit/src/hir/opt_tests.rs | 24 ++++++++++++------------ zjit/src/stats.rs | 2 ++ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index c15fa3c8c4aa66..40278d112e9a4b 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -5419,3 +5419,37 @@ fn test_ep_escape_preserves_keyword_default() { target("x") "#), @"[]"); } + +#[test] +fn test_send_block_to_accepts_no_block() { + // Methods with &nil should raise ArgumentError when called with a block + assert_snapshot!(inspect(" + def m(a, &nil); a end + + def test + m(1) {} + rescue ArgumentError => e + e.message + end + + test + test + "), @r#""no block accepted""#); +} + +#[test] +fn test_send_block_to_method_not_using_block() { + // Passing a block to a method that doesn't use it should still work correctly. + // ZJIT falls back to the interpreter for this case so that unused block + // warnings are properly emitted. + assert_snapshot!(inspect(" + def m_no_block = 42 + + def test + m_no_block {} + end + + test + test + "), @"42"); +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 47b7915424e963..e8db2b6af2649a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2357,7 +2357,7 @@ pub enum ValidationError { } /// Check if we can do a direct send to the given iseq with the given args. -fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId]) -> bool { +fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId], has_block: bool) -> bool { let mut can_send = true; let mut count_failure = |counter| { can_send = false; @@ -2376,6 +2376,16 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq { count_failure(complex_arg_pass_param_block) } if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } + // If the caller passes a block (literal or &block), we need to fall back to the + // interpreter for two cases it handles that we don't: + // 1. Methods with &nil reject blocks with ArgumentError + // 2. Methods that don't use blocks emit "unused block" warnings + let caller_passes_block = has_block || caller_passes_block_arg; + if caller_passes_block && 0 != params.flags.accepts_no_block() + { count_failure(complex_arg_pass_accepts_no_block) } + if caller_passes_block && 0 == params.flags.use_block() + { count_failure(complex_arg_pass_does_not_use_block) } + if !can_send { function.set_dynamic_send_reason(send_insn, ComplexArgPass); return false; @@ -3723,7 +3733,7 @@ impl Function { // Only specialize positional-positional calls // TODO(max): Handle other kinds of parameter passing let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; - if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice()) { + if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block) { self.push_insn_id(block, insn_id); continue; } @@ -3762,7 +3772,7 @@ impl Function { let capture = unsafe { proc_block.as_.captured.as_ref() }; let iseq = unsafe { *capture.code.iseq.as_ref() }; - if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice()) { + if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block) { self.push_insn_id(block, insn_id); continue; } @@ -4135,7 +4145,7 @@ impl Function { // If not, we can't do direct dispatch. let super_iseq = unsafe { get_def_iseq_ptr((*super_cme).def) }; // TODO: pass Option to can_direct_send when we start specializing `super { ... }`. - if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice()) { + if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice(), false) { self.push_insn_id(block, insn_id); self.set_dynamic_send_reason(insn_id, SuperTargetComplexArgsPass); continue; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index ec7c332fd10cf1..3cf9a8f3fa69e8 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -11433,6 +11433,8 @@ mod hir_opt_tests { #[test] fn test_inline_send_with_block_with_no_params() { + // Passing a block to a method that doesn't use it falls back to the + // interpreter so that unused block warnings are properly emitted. eval(r#" def callee = 123 def test @@ -11452,16 +11454,16 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] - v21:Fixnum[123] = Const Value(123) + v11:BasicObject = Send v6, 0x1000, :callee # SendFallbackReason: Complex argument passing CheckInterrupts - Return v21 + Return v11 "); } #[test] fn test_inline_send_with_block_with_one_param() { + // Passing a block to a method that doesn't use it falls back to the + // interpreter so that unused block warnings are properly emitted. eval(r#" def callee = 123 def test @@ -11481,16 +11483,16 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] - v21:Fixnum[123] = Const Value(123) + v11:BasicObject = Send v6, 0x1000, :callee # SendFallbackReason: Complex argument passing CheckInterrupts - Return v21 + Return v11 "); } #[test] fn test_inline_send_with_block_with_multiple_params() { + // Passing a block to a method that doesn't use it falls back to the + // interpreter so that unused block warnings are properly emitted. eval(r#" def callee = 123 def test @@ -11510,11 +11512,9 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] - v21:Fixnum[123] = Const Value(123) + v11:BasicObject = Send v6, 0x1000, :callee # SendFallbackReason: Complex argument passing CheckInterrupts - Return v21 + Return v11 "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index db6aef5d8cc31c..61a94b53d5bb74 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -424,6 +424,8 @@ make_counters! { complex_arg_pass_param_kwrest, complex_arg_pass_param_block, complex_arg_pass_param_forwardable, + complex_arg_pass_accepts_no_block, + complex_arg_pass_does_not_use_block, // Unsupported caller side features complex_arg_pass_caller_splat,