diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml deleted file mode 100644 index 3e8c355ffdffe1..00000000000000 --- a/.github/workflows/check_dependencies.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Check Dependencies -on: - push: - paths-ignore: - - 'doc/**' - - '**/man/*' - - '**.md' - - '**.rdoc' - - '**/.document' - - '.*.yml' - pull_request: - merge_group: - -concurrency: - group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} - cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} - -permissions: - contents: read - -jobs: - update-deps: - name: Dependency checks (ubuntu-latest) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 - with: - ruby-version: '3.1' - bundler: none - - - run: ruby tool/mkdepend.rb --scope=all --sources --check - - - uses: ./.github/actions/slack - with: - label: Dependencies need to update - SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - if: ${{ failure() }} diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 837537203b491f..601363d8304ade 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -64,7 +64,9 @@ jobs: - name: Check for bash specific substitution in configure.ac run: | - git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || : + git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac | sed "s/^/::error::/" + test ${PIPESTATUS[0]} != 0 + if: always() - name: Check for header macros run: | @@ -72,16 +74,33 @@ jobs: for header in ruby/*.h; do git grep -l -F -e $header -e HAVE_`echo $header | tr a-z./ A-Z__` -- . > /dev/null && continue fail=1 - echo $header + echo "::error::$header" done exit $fail working-directory: include + if: always() + + - name: Check for __has_builtin functions + run: | + missing=() + for check in $(git grep -h --no-line-number -o '__has_builtin(__[_a-zA-Z0-9]*)' | sort -u); do + func="${check#__has_builtin(}" + func="${func%)}" + git -P grep -w "RBIMPL_HAS_BUILTIN_$func" > /dev/null || missing+=("-e" "$check") + done + [ ${#missing[@]} = 0 ] || { + echo "::error::Missing __has_builtin fallbacks:" + git -P grep -F "${missing[@]}" | sed 's/^/::error::/' | head + false + } + if: always() - id: now run: | date +"mon=%-m"%n"day=%-d" >> $GITHUB_OUTPUT env: TZ: Asia/Tokyo + if: always() - id: deprecation run: | @@ -92,12 +111,17 @@ jobs: true fi continue-on-error: ${{ steps.now.outputs.mon < 12 }} + if: always() + + - run: make check-depends + if: always() - name: Check if to generate documents id: rdoc run: | set -- $(sed 's/#.*//;/^rdoc /!d' gems/bundled_gems) { echo version=$2; echo ref=$4; } >> $GITHUB_OUTPUT + if: always() - name: Checkout rdoc uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -106,7 +130,7 @@ jobs: ref: ${{ steps.rdoc.outputs.ref }} path: .bundle/gems/rdoc-${{ steps.rdoc.outputs.version }} persist-credentials: false - if: ${{ steps.rdoc.outputs.ref != '' }} + if: ${{ always() && steps.rdoc.outputs.ref != '' }} - name: Generate rdoc scripts run: | @@ -118,11 +142,12 @@ jobs: bundle install --jobs 4 bundle exec rake generate working-directory: .bundle/gems/rdoc-${{ steps.rdoc.outputs.version }} - if: ${{ steps.rdoc.outputs.ref != '' }} + if: ${{ always() && steps.rdoc.outputs.ref != '' }} - name: Core docs coverage run: | make XRUBY=ruby RDOC_DEPENDS= RBCONFIG=update-rbconfig rdoc-coverage + if: always() - name: Generate docs id: docs @@ -131,19 +156,19 @@ jobs: echo htmlout=ruby-html-${GITHUB_SHA:0:10} >> $GITHUB_OUTPUT # Generate only when document commit/PR if: >- - ${{false + ${{always() && (false || contains(github.event.head_commit.message, '[ruby/rdoc]') || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') || contains(github.event.pull_request.labels.*.name, 'Documentation') - }} + )}} - name: Upload docs uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: path: html name: ${{ steps.docs.outputs.htmlout }} - if: ${{ steps.docs.outcome == 'success' }} + if: ${{ always() && steps.docs.outcome == 'success' }} - uses: ./.github/actions/slack with: diff --git a/NEWS.md b/NEWS.md index ca413c616f6ece..c3599f0dbcbef4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -244,6 +244,12 @@ Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for det * SunOS 4 (Solaris, i.e. SunOS 5, is unaffected) * BSD/OS (BSDi) +* Windows 10 1703 or later no longer needs the `LongPathsEnabled` registry + value to use paths longer than 260 characters. This applies to any process + running the interpreter, including a program which embeds libruby. Each path + component is still limited to 255 characters, and a child process still + starts with the `MAX_PATH` limited current directory. [[Bug #18947]] + ## Compatibility issues * `Kernel#at_exit` and `END {}` now raise `Ractor::IsolationError` when called @@ -327,6 +333,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. ## JIT +[Bug #18947]: https://bugs.ruby-lang.org/issues/18947 [Feature #8948]: https://bugs.ruby-lang.org/issues/8948 [Feature #9779]: https://bugs.ruby-lang.org/issues/9779 [Feature #15330]: https://bugs.ruby-lang.org/issues/15330 diff --git a/configure.ac b/configure.ac index 125dd098147a4a..06a4491f228726 100644 --- a/configure.ac +++ b/configure.ac @@ -2366,6 +2366,7 @@ RUBY_CHECK_BUILTIN_FUNC(__builtin_assume_aligned, [__builtin_assume_aligned((voi RUBY_CHECK_BUILTIN_FUNC(__builtin_bswap16, [__builtin_bswap16(0)]) RUBY_CHECK_BUILTIN_FUNC(__builtin_bswap32, [__builtin_bswap32(0)]) RUBY_CHECK_BUILTIN_FUNC(__builtin_bswap64, [__builtin_bswap64(0)]) +test "$ac_cv_sizeof___int128" = 0 || RUBY_CHECK_BUILTIN_FUNC(__builtin_bswap128, [__builtin_bswap128(0)]) RUBY_CHECK_BUILTIN_FUNC(__builtin_popcount, [__builtin_popcount(0)]) RUBY_CHECK_BUILTIN_FUNC(__builtin_popcountll, [__builtin_popcountll(0)]) RUBY_CHECK_BUILTIN_FUNC(__builtin_clz, [__builtin_clz(0)]) diff --git a/defs/jit.mk b/defs/jit.mk index 2c1e819684939b..3ebf3858979407 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -81,12 +81,12 @@ rustc-version-check: target/.rustc-version target/.rustc-version: PHONY $(eval prev_version := $(if $(wildcard $@),$(shell cat $@))) - $(eval curr_version := $(shell $(RUSTC) -V | cut -d' ' -f2)) + $(eval curr_version := $(shell $(RUSTC) -V)) $(eval clean := $(filter-out $(prev_version),$(curr_version))) - $(if $(clean),$(ECHO) Cleaning $(@D) for rustc $(curr_version)) + $(if $(clean),$(ECHO) "Cleaning $(@D) for $(curr_version)") $(if $(clean),$(Q)$(RMALL) $(@D)) $(if $(clean),$(Q)$(MAKEDIRS) $(@D)) - $(if $(clean),$(Q)echo $(curr_version) > $@) + $(if $(clean),$(Q)echo "$(curr_version)" > $@) # For Darwin only: a list of symbols that we want the glommed Rust static lib to export. # Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least diff --git a/eval.c b/eval.c index 8c61ceb6a7ee77..e23be408ada3c9 100644 --- a/eval.c +++ b/eval.c @@ -76,6 +76,9 @@ ruby_setup(void) */ #if defined(__linux__) && defined(PR_SET_THP_DISABLE) prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); +#endif +#if defined(_WIN32) + rb_w32_init_long_paths(); #endif Init_BareVM(); rb_vm_encoded_insn_data_table_init(); diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 6438d96fdf9519..d14265c09329c6 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -754,7 +754,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * cert.not_before = Time.now * cert.not_after = Time.now + 3600 * - * cert.public_key = key.public_key + * cert.public_key = key * cert.subject = name * * === Certificate Extensions @@ -836,7 +836,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * ca_cert.not_before = Time.now * ca_cert.not_after = Time.now + 86400 * - * ca_cert.public_key = ca_key.public_key + * ca_cert.public_key = ca_key * ca_cert.subject = ca_name * ca_cert.issuer = ca_name * @@ -878,7 +878,7 @@ ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) * csr = OpenSSL::X509::Request.new * csr.version = 0 * csr.subject = name - * csr.public_key = key.public_key + * csr.public_key = key * csr.sign key, OpenSSL::Digest.new('SHA1') * * A CSR is saved to disk and sent to the CA for signing. diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index eb9933948ff748..eb4fce59611400 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -336,7 +336,7 @@ ossl_spki_verify(VALUE self, VALUE key) * key = OpenSSL::PKey::RSA.new 2048 * spki = OpenSSL::Netscape::SPKI.new * spki.challenge = "RandomChallenge" - * spki.public_key = key.public_key + * spki.public_key = key * spki.sign(key, OpenSSL::Digest.new('SHA256')) * #send a request containing this to a server generating a certificate * === Verifying an SPKI request diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 5cbc0d195f4ccb..7528d2d2638f34 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -1192,7 +1192,7 @@ ossl_pkey_compare(VALUE self, VALUE other) * signature = pkey.sign("SHA256", data, signopts) * * # Creates a copy of the RSA key pkey, but without the private components - * pub_key = pkey.public_key + * pub_key = OpenSSL::PKey.read(pkey.public_to_der) * puts pub_key.verify("SHA256", signature, data, signopts) # => true */ static VALUE @@ -1350,7 +1350,7 @@ ossl_pkey_verify(int argc, VALUE *argv, VALUE self) * signature = pkey.sign_raw("SHA256", hash, signopts) * * # Creates a copy of the RSA key pkey, but without the private components - * pub_key = pkey.public_key + * pub_key = OpenSSL::PKey.read(pkey.public_to_der) * puts pub_key.verify_raw("SHA256", signature, hash, signopts) # => true */ static VALUE diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index b403871949541b..f0a3a81578b321 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -922,7 +922,7 @@ Init_ossl_x509cert(void) * root_ca.serial = 1 * root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA" * root_ca.issuer = root_ca.subject # root CA's are "self-signed" - * root_ca.public_key = root_key.public_key + * root_ca.public_key = root_key * root_ca.not_before = Time.now * root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity * ef = OpenSSL::X509::ExtensionFactory.new @@ -943,7 +943,7 @@ Init_ossl_x509cert(void) * cert.serial = 2 * cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate" * cert.issuer = root_ca.subject # root CA is the issuer - * cert.public_key = key.public_key + * cert.public_key = key * cert.not_before = Time.now * cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity * ef = OpenSSL::X509::ExtensionFactory.new diff --git a/gc.c b/gc.c index 4db541967aeb18..9e7410a404e264 100644 --- a/gc.c +++ b/gc.c @@ -4722,7 +4722,9 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) // set the shape on it so that the GC finalizer won't try to remove // it again. A "root shape" indicates to the GC that this object // has no fields on it, hence it won't be in the gen fields table. - RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); + if (BUILTIN_TYPE((VALUE)key) != T_NONE) { + RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); + } return ST_DELETE; case ST_REPLACE: { diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 935918a3358531..a7aec6e68338e7 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1840,7 +1840,7 @@ bool rb_gc_impl_shref_marked_p(void *objspace_ptr, VALUE obj) { /* With a single objspace there is no cross-objspace pinning to track. */ - return false; + return true; } size_t diff --git a/include/ruby/internal/has/builtin.h b/include/ruby/internal/has/builtin.h index 8e7fb173d8a3a6..e9ba6598ba1b46 100644 --- a/include/ruby/internal/has/builtin.h +++ b/include/ruby/internal/has/builtin.h @@ -57,6 +57,7 @@ #ifndef __OpenBSD__ # define RBIMPL_HAS_BUILTIN___builtin_bswap32 RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) # define RBIMPL_HAS_BUILTIN___builtin_bswap64 RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) +# define RBIMPL_HAS_BUILTIN___builtin_bswap128 RBIMPL_COMPILER_SINCE(GCC, 11, 1, 0) #endif # define RBIMPL_HAS_BUILTIN___builtin_clz RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) # define RBIMPL_HAS_BUILTIN___builtin_clzl RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) @@ -66,6 +67,7 @@ # define RBIMPL_HAS_BUILTIN___builtin_ctzl RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) # define RBIMPL_HAS_BUILTIN___builtin_ctzll RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) # define RBIMPL_HAS_BUILTIN___builtin_expect RBIMPL_COMPILER_SINCE(GCC, 3, 0, 0) +# define RBIMPL_HAS_BUILTIN___builtin_memcpy RBIMPL_COMPILER_SINCE(GCC, 3, 0, 0) // or older # define RBIMPL_HAS_BUILTIN___builtin_mul_overflow RBIMPL_COMPILER_SINCE(GCC, 5, 1, 0) # define RBIMPL_HAS_BUILTIN___builtin_mul_overflow_p RBIMPL_COMPILER_SINCE(GCC, 7, 0, 0) # define RBIMPL_HAS_BUILTIN___builtin_popcount RBIMPL_COMPILER_SINCE(GCC, 3, 6, 0) @@ -92,6 +94,7 @@ # define RBIMPL_HAS_BUILTIN___builtin_bswap16 HAVE_BUILTIN___BUILTIN_BSWAP16 # define RBIMPL_HAS_BUILTIN___builtin_bswap32 HAVE_BUILTIN___BUILTIN_BSWAP32 # define RBIMPL_HAS_BUILTIN___builtin_bswap64 HAVE_BUILTIN___BUILTIN_BSWAP64 +# define RBIMPL_HAS_BUILTIN___builtin_bswap128 HAVE_BUILTIN___BUILTIN_BSWAP128 # define RBIMPL_HAS_BUILTIN___builtin_clz HAVE_BUILTIN___BUILTIN_CLZ # define RBIMPL_HAS_BUILTIN___builtin_clzl HAVE_BUILTIN___BUILTIN_CLZL # define RBIMPL_HAS_BUILTIN___builtin_clzll HAVE_BUILTIN___BUILTIN_CLZLL diff --git a/internal.h b/internal.h index a08fab10dc2355..fe9ad3518f0bc4 100644 --- a/internal.h +++ b/internal.h @@ -64,6 +64,11 @@ /* internal/symbol.h */ #define rb_sym_intern_ascii_cstr(...) rb_nonexistent_symbol(__VA_ARGS__) +#ifdef _WIN32 +/* win32/win32.c */ +void rb_w32_init_long_paths(void); +#endif + /* MRI debug support */ diff --git a/iseq.c b/iseq.c index 3d1fdcfa22f7b7..5a12633dd78b1c 100644 --- a/iseq.c +++ b/iseq.c @@ -543,7 +543,8 @@ rb_iseq_memsize(const rb_iseq_t *iseq) size += sizeof(struct rb_iseq_constant_body); size += body->iseq_size * sizeof(VALUE); size += body->insns_info.size * (sizeof(struct iseq_insn_info_entry) + sizeof(unsigned int)); - size += body->local_table_size * sizeof(ID); + size += body->local_table_size * sizeof(ID); // body->local_table + if (body->lvar_states) size += body->local_table_size * sizeof(enum lvar_state); size += ISEQ_MBITS_BUFLEN(body->iseq_size) * ISEQ_MBITS_SIZE; if (body->catch_table) { size += iseq_catch_table_bytes(body->catch_table->size); @@ -551,6 +552,8 @@ rb_iseq_memsize(const rb_iseq_t *iseq) size += (body->param.opt_num + 1) * sizeof(VALUE); size += param_keyword_size(body->param.keyword); + if (body->outer_variables) size += rb_id_table_memsize(body->outer_variables); + /* body->is_entries */ size += ISEQ_IS_SIZE(body) * sizeof(union iseq_inline_storage_entry); @@ -2951,9 +2954,9 @@ rb_iseq_disasm_recursive(const rb_iseq_t *iseq, VALUE indent) rb_str_modify_expand(str, header_minlen - l); memset(RSTRING_END(str), '=', header_minlen - l); } - if (iseq->body->builtin_attrs) { + if (ISEQ_BODY(iseq)->builtin_attrs) { #define disasm_builtin_attr(str, iseq, attr) \ - if (iseq->body->builtin_attrs & BUILTIN_ATTR_ ## attr) { \ + if (ISEQ_BODY(iseq)->builtin_attrs & BUILTIN_ATTR_ ## attr) { \ rb_str_cat2(str, " " #attr); \ } disasm_builtin_attr(str, iseq, LEAF); diff --git a/jit.c b/jit.c index fa672f1feef23a..d815fb909ccf2c 100644 --- a/jit.c +++ b/jit.c @@ -70,7 +70,7 @@ const shape_id_t rb_invalid_shape_id = INVALID_SHAPE_ID; unsigned int rb_iseq_encoded_size(const rb_iseq_t *iseq) { - return iseq->body->iseq_size; + return ISEQ_BODY(iseq)->iseq_size; } // Get the PC for a given index in an iseq @@ -78,8 +78,8 @@ VALUE * rb_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - RUBY_ASSERT_ALWAYS(insn_idx < iseq->body->iseq_size); - VALUE *encoded = iseq->body->iseq_encoded; + RUBY_ASSERT_ALWAYS(insn_idx < ISEQ_BODY(iseq)->iseq_size); + VALUE *encoded = ISEQ_BODY(iseq)->iseq_encoded; VALUE *pc = &encoded[insn_idx]; return pc; } @@ -245,21 +245,21 @@ rb_optimized_call(VALUE recv, rb_execution_context_t *ec, int argc, VALUE *argv, unsigned int rb_jit_iseq_builtin_attrs(const rb_iseq_t *iseq) { - return iseq->body->builtin_attrs; + return ISEQ_BODY(iseq)->builtin_attrs; } // Relaxed memory ordering, but called by the JIT with VM lock and barrier. void rb_jit_iseq_mark_ep_escape_recorded(const rb_iseq_t *iseq) { - rbimpl_atomic_store(&iseq->body->jit_ep_escape_recorded, 1, RBIMPL_ATOMIC_RELAXED); + rbimpl_atomic_store(&ISEQ_BODY(iseq)->jit_ep_escape_recorded, 1, RBIMPL_ATOMIC_RELAXED); } // Whether an EP escape of this iseq has been reported to the enabled JIT. bool rb_jit_iseq_ep_escape_recorded_p(const rb_iseq_t *iseq) { - return rbimpl_atomic_load(&iseq->body->jit_ep_escape_recorded, RBIMPL_ATOMIC_RELAXED) != 0; + return rbimpl_atomic_load(&ISEQ_BODY(iseq)->jit_ep_escape_recorded, RBIMPL_ATOMIC_RELAXED) != 0; } int @@ -283,109 +283,109 @@ rb_get_def_iseq_ptr(rb_method_definition_t *def) const rb_iseq_t * rb_get_iseq_body_local_iseq(const rb_iseq_t *iseq) { - return iseq->body->local_iseq; + return ISEQ_BODY(iseq)->local_iseq; } const rb_iseq_t * rb_get_iseq_body_parent_iseq(const rb_iseq_t *iseq) { - return iseq->body->parent_iseq; + return ISEQ_BODY(iseq)->parent_iseq; } unsigned int rb_get_iseq_body_local_table_size(const rb_iseq_t *iseq) { - return iseq->body->local_table_size; + return ISEQ_BODY(iseq)->local_table_size; } VALUE * rb_get_iseq_body_iseq_encoded(const rb_iseq_t *iseq) { - return iseq->body->iseq_encoded; + return ISEQ_BODY(iseq)->iseq_encoded; } unsigned rb_get_iseq_body_stack_max(const rb_iseq_t *iseq) { - return iseq->body->stack_max; + return ISEQ_BODY(iseq)->stack_max; } enum rb_iseq_type rb_get_iseq_body_type(const rb_iseq_t *iseq) { - return iseq->body->type; + return ISEQ_BODY(iseq)->type; } bool rb_get_iseq_flags_has_lead(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_lead; + return ISEQ_BODY(iseq)->param.flags.has_lead; } bool rb_get_iseq_flags_has_opt(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_opt; + return ISEQ_BODY(iseq)->param.flags.has_opt; } bool rb_get_iseq_flags_has_kw(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_kw; + return ISEQ_BODY(iseq)->param.flags.has_kw; } bool rb_get_iseq_flags_has_post(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_post; + return ISEQ_BODY(iseq)->param.flags.has_post; } bool rb_get_iseq_flags_has_kwrest(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_kwrest; + return ISEQ_BODY(iseq)->param.flags.has_kwrest; } bool rb_get_iseq_flags_anon_kwrest(const rb_iseq_t *iseq) { - return iseq->body->param.flags.anon_kwrest; + return ISEQ_BODY(iseq)->param.flags.anon_kwrest; } bool rb_get_iseq_flags_has_rest(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_rest; + return ISEQ_BODY(iseq)->param.flags.has_rest; } bool rb_get_iseq_flags_ruby2_keywords(const rb_iseq_t *iseq) { - return iseq->body->param.flags.ruby2_keywords; + return ISEQ_BODY(iseq)->param.flags.ruby2_keywords; } bool rb_get_iseq_flags_has_block(const rb_iseq_t *iseq) { - return iseq->body->param.flags.has_block; + return ISEQ_BODY(iseq)->param.flags.has_block; } bool rb_get_iseq_flags_ambiguous_param0(const rb_iseq_t *iseq) { - return iseq->body->param.flags.ambiguous_param0; + return ISEQ_BODY(iseq)->param.flags.ambiguous_param0; } bool rb_get_iseq_flags_accepts_no_kwarg(const rb_iseq_t *iseq) { - return iseq->body->param.flags.accepts_no_kwarg; + return ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg; } bool rb_get_iseq_flags_forwardable(const rb_iseq_t *iseq) { - return iseq->body->param.flags.forwardable; + return ISEQ_BODY(iseq)->param.flags.forwardable; } // This is defined only as a named struct inside rb_iseq_constant_body. @@ -396,31 +396,31 @@ typedef struct rb_iseq_param_keyword rb_iseq_param_keyword_struct; const rb_iseq_param_keyword_struct * rb_get_iseq_body_param_keyword(const rb_iseq_t *iseq) { - return iseq->body->param.keyword; + return ISEQ_BODY(iseq)->param.keyword; } unsigned rb_get_iseq_body_param_size(const rb_iseq_t *iseq) { - return iseq->body->param.size; + return ISEQ_BODY(iseq)->param.size; } int rb_get_iseq_body_param_lead_num(const rb_iseq_t *iseq) { - return iseq->body->param.lead_num; + return ISEQ_BODY(iseq)->param.lead_num; } int rb_get_iseq_body_param_opt_num(const rb_iseq_t *iseq) { - return iseq->body->param.opt_num; + return ISEQ_BODY(iseq)->param.opt_num; } const VALUE * rb_get_iseq_body_param_opt_table(const rb_iseq_t *iseq) { - return iseq->body->param.opt_table; + return ISEQ_BODY(iseq)->param.opt_table; } struct rb_control_frame_struct * @@ -615,12 +615,12 @@ void rb_iseq_reset_jit_func(const rb_iseq_t *iseq) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - iseq->body->jit_entry = NULL; - iseq->body->jit_exception = NULL; + ISEQ_BODY(iseq)->jit_entry = NULL; + ISEQ_BODY(iseq)->jit_exception = NULL; // Enable re-compiling this ISEQ. Event when it's invalidated for TracePoint, // we'd like to re-compile ISEQs that haven't been converted to trace_* insns. - iseq->body->jit_entry_calls = 0; - iseq->body->jit_exception_calls = 0; + ISEQ_BODY(iseq)->jit_entry_calls = 0; + ISEQ_BODY(iseq)->jit_exception_calls = 0; } // Callback data for rb_jit_for_each_iseq diff --git a/proc.c b/proc.c index 599b68ca7f2140..586d02139c4215 100644 --- a/proc.c +++ b/proc.c @@ -2230,7 +2230,7 @@ rb_hash_proc(st_index_t hash, VALUE prc) } } else { - hash = rb_st_hash_uint(hash, (st_index_t)proc->block.as.captured.code.iseq->body); + hash = rb_st_hash_uint(hash, (st_index_t)ISEQ_BODY(proc->block.as.captured.code.iseq)); } break; case block_type_ifunc: diff --git a/ractor.c b/ractor.c index 3969e7d071a9f6..c6b6c32b8242b6 100644 --- a/ractor.c +++ b/ractor.c @@ -252,11 +252,7 @@ ractor_mark_unshareable_parts(rb_ractor_t *r) // mark the received messages (the structures the owner mutates guard themselves) ractor_sync_mark(r); - /* Structures the owner mutates while running follow. Only the root scan calls - * this: a local GC for itself, a global GC for the whole set under the barrier. A - * terminated Ractor has left the set; zombie_objspaces covers it instead. */ - VM_ASSERT(r == rb_current_ractor_raw(false) || rb_gc_during_global_gc_p()); - VM_ASSERT(!rb_ractor_status_p(r, ractor_terminated)); + /* Structures the owner mutates while running follow. */ rb_hook_list_mark(&r->pub.hooks); if (r->pub.targeted_hooks.num_entries) { @@ -334,6 +330,11 @@ rb_ractor_mark_local_roots(rb_ractor_t *r) rb_gc_mark(r->loc); rb_gc_mark(r->name); + /* Only the root scan calls this: a local GC for itself, a global GC for the whole + * set under the barrier. A terminated Ractor has left the set; zombie_objspaces + * covers it instead. */ + VM_ASSERT(r == rb_current_ractor_raw(false) || rb_gc_during_global_gc_p()); + VM_ASSERT(!rb_ractor_status_p(r, ractor_terminated)); ractor_mark_unshareable_parts(r); /* This Ractor's rb_gc_register_mark_object pins, treated conservatively: a local GC diff --git a/shape.c b/shape.c index e9037acf6a3ccf..17df38ddd548f2 100644 --- a/shape.c +++ b/shape.c @@ -1343,7 +1343,6 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) if (rb_shape_verify_capacity_consistency_p(obj)) { attr_index_t shape_id_capacity = rb_shape_embedded_capacity(shape_id); - RUBY_ASSERT(shape_id_capacity > 0); size_t shape_id_slot_size = shape_id_capacity * sizeof(VALUE) + sizeof(struct RBasic); size_t actual_slot_size = rb_gc_obj_slot_size(obj); diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb index 04844292897a08..2607a8c0b3a77d 100644 --- a/test/openssl/test_ns_spki.rb +++ b/test/openssl/test_ns_spki.rb @@ -21,17 +21,17 @@ def test_build_data key2 = Fixtures.pkey("rsa-2") spki = OpenSSL::Netscape::SPKI.new spki.challenge = "RandomString" - spki.public_key = key1.public_key + spki.public_key = key1 spki.sign(key1, OpenSSL::Digest.new('SHA256')) - assert(spki.verify(spki.public_key)) - assert(spki.verify(key1.public_key)) - assert(!spki.verify(key2.public_key)) + assert_true(spki.verify(spki.public_key)) + assert_true(spki.verify(OpenSSL::PKey.read(key1.public_to_der))) + assert_false(spki.verify(OpenSSL::PKey.read(key2.public_to_der))) der = spki.to_der spki = OpenSSL::Netscape::SPKI.new(der) assert_equal("RandomString", spki.challenge) - assert_equal(key1.public_key.to_der, spki.public_key.to_der) - assert(spki.verify(spki.public_key)) + assert_equal(key1.public_to_der, spki.public_key.public_to_der) + assert_true(spki.verify(spki.public_key)) assert_not_nil(spki.to_text) end diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 1ec0bf0b4d4bf8..fb692854a1f2d5 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -14,16 +14,15 @@ def test_private assert_equal true, key.private? key2 = OpenSSL::PKey::DSA.new(key.to_der) assert_equal true, key2.private? - key3 = key.public_key + key3 = OpenSSL::PKey::DSA.new(key.public_to_der) assert_equal false, key3.private? - key4 = OpenSSL::PKey::DSA.new(key3.to_der) - assert_equal false, key4.private? end def test_new - key = OpenSSL::PKey::DSA.new(2048) - pem = key.public_key.to_pem - OpenSSL::PKey::DSA.new pem + key = OpenSSL::PKey::DSA.new(1024) + assert_predicate(key, :private?) + assert_equal(1024, key.p.num_bits) + assert_equal(160, key.q.num_bits) end def test_new_break @@ -233,6 +232,19 @@ def test_params assert_nil(pubkey.params["priv_key"]) end + def test_public_key + key = Fixtures.pkey("dsa2048") + pub = key.public_key + assert_not_predicate(pub, :private?) + assert_predicate(pub, :public?) + assert_equal(key.p, pub.p) + assert_equal(key.q, pub.q) + assert_equal(key.g, pub.g) + assert_equal(key.pub_key, pub.pub_key) + assert_nil(pub.priv_key) + assert_equal(key.public_to_der, pub.to_der) + end + def test_dup key = Fixtures.pkey("dsa2048") key2 = key.dup diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index d56d19bd9722ee..31c3c52b344eca 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -20,13 +20,9 @@ def test_private key2 = OpenSSL::PKey::RSA.new(key.to_der) assert_true(key2.private?) - # public key - key3 = key.public_key - assert_false(key3.private?) - # Generated by public key DER - key4 = OpenSSL::PKey::RSA.new(key3.to_der) - assert_false(key4.private?) + key3 = OpenSSL::PKey::RSA.new(key.public_to_der) + assert_false(key3.private?) if !openssl?(3, 0, 0) # Generated by RSA#set_key @@ -290,11 +286,11 @@ def test_export # key has only n, e and d key.set_key(orig.n, orig.e, orig.d) - assert_equal orig.public_key.export, key.export + assert_equal pub.export, key.export # key has only n, e, d, p and q key.set_factors(orig.p, orig.q) - assert_equal orig.public_key.export, key.export + assert_equal pub.export, key.export # key has n, e, d, p, q, dmp1, dmq1 and iqmp key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) @@ -315,11 +311,11 @@ def test_to_der # key has only n, e and d key.set_key(orig.n, orig.e, orig.d) - assert_equal orig.public_key.to_der, key.to_der + assert_equal pub.to_der, key.to_der # key has only n, e, d, p and q key.set_factors(orig.p, orig.q) - assert_equal orig.public_key.to_der, key.to_der + assert_equal pub.to_der, key.to_der # key has n, e, d, p, q, dmp1, dmq1 and iqmp key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) @@ -548,6 +544,17 @@ def test_get_param assert_equal(key.iqmp, key.get_param("rsa-coefficient1")) end + def test_public_key + key = Fixtures.pkey("rsa-1") + pub = key.public_key + assert_not_predicate(pub, :private?) + assert_predicate(pub, :public?) + assert_equal(key.n, pub.n) + assert_equal(key.e, pub.e) + assert_nil(pub.d) + assert_equal(key.public_to_der, pub.to_der) + end + def test_dup key = Fixtures.pkey("rsa-1") key2 = key.dup diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb index edb5210af16313..2ac8b1dc1386f0 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -590,6 +590,22 @@ def test_glob_legacy_short_name assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819) end + def test_glob_long_path + bug18923 = '[Bug #18923]' + # the whole path is longer than MAX_PATH, while each component is + # within the 255 chars limit of NTFS + deep = File.join(@root, "a" * 200, "b" * 200) + begin + FileUtils.mkdir_p(deep) + rescue SystemCallError + omit "long path names are not available" + end + file = File.join(deep, "c.txt") + File.write(file, "") + assert_equal([file], Dir.glob(File.join(@root, "**", "*.txt")), bug18923) + assert_equal(["c.txt"], Dir.children(deep), bug18923) + end + def test_home_windows setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH]) diff --git a/vm.c b/vm.c index 7fece4685228b6..ff1ec24b93138e 100644 --- a/vm.c +++ b/vm.c @@ -276,8 +276,8 @@ vm_ep_in_heap_p_(const rb_execution_context_t *ec, const VALUE *ep) int rb_vm_ep_in_heap_p(const VALUE *ep) { - const rb_execution_context_t *ec = GET_EC(); - if (ec->vm_stack == NULL) return TRUE; + const rb_execution_context_t *ec = rb_current_execution_context(false); + if (ec == NULL || ec->vm_stack == NULL) return TRUE; return vm_ep_in_heap_p_(ec, ep); } #endif @@ -483,7 +483,7 @@ rb_yjit_threshold_hit(const rb_iseq_t *iseq, uint64_t entry_calls) // Record the number of calls at the beginning of the interval if (entry_calls + YJIT_CALL_COUNT_INTERV == rb_yjit_call_threshold) { - iseq->body->yjit_calls_at_interv = yjit_total_entry_hits; + ISEQ_BODY(iseq)->yjit_calls_at_interv = yjit_total_entry_hits; } // Try to estimate the total time taken (total number of calls) to reach 20 calls to this ISEQ @@ -494,7 +494,7 @@ rb_yjit_threshold_hit(const rb_iseq_t *iseq, uint64_t entry_calls) return true; } - uint64_t num_calls = yjit_total_entry_hits - iseq->body->yjit_calls_at_interv; + uint64_t num_calls = yjit_total_entry_hits - ISEQ_BODY(iseq)->yjit_calls_at_interv; // Reject ISEQs that don't get called often enough if (num_calls > rb_yjit_cold_threshold) { diff --git a/vm_callinfo.h b/vm_callinfo.h index 5c7cc6757e9a03..631021d49845b8 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -647,13 +647,24 @@ vm_ccs_p(const struct rb_class_cc_entries *ccs) return ccs->debug_sig == ~(VALUE)ccs; } +static inline bool +vm_cc_check_cme_unlocked(const struct rb_callcache *cc, const rb_callable_method_entry_t *cme) +{ + return vm_cc_cme(cc) == cme || + (cme->def->iseq_overload && vm_cc_cme(cc) == rb_vm_lookup_overloaded_cme(cme)); +} + static inline bool vm_cc_check_cme(const struct rb_callcache *cc, const rb_callable_method_entry_t *cme) { bool valid; - RB_VM_LOCKING_NO_BARRIER() { - valid = vm_cc_cme(cc) == cme || - (cme->def->iseq_overload && vm_cc_cme(cc) == rb_vm_lookup_overloaded_cme(cme)); + if (rb_current_execution_context(false) == NULL) { + valid = vm_cc_check_cme_unlocked(cc, cme); + } + else { + RB_VM_LOCKING_NO_BARRIER() { + valid = vm_cc_check_cme_unlocked(cc, cme); + } } if (valid) { return true; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 0e84feb298c20d..b5297bba22750e 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -3180,9 +3180,9 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, VM_ASSERT(cc == calling->cc); if (vm_call_iseq_optimizable_p(ci, cc)) { - if ((iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) && ruby_vm_c_events_enabled == 0) { - VM_ASSERT(iseq->body->builtin_attrs & BUILTIN_ATTR_LEAF); - vm_cc_bf_set(cc, (void *)iseq->body->iseq_encoded[1]); + if ((ISEQ_BODY(iseq)->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) && ruby_vm_c_events_enabled == 0) { + VM_ASSERT(ISEQ_BODY(iseq)->builtin_attrs & BUILTIN_ATTR_LEAF); + vm_cc_bf_set(cc, (void *)ISEQ_BODY(iseq)->iseq_encoded[1]); CC_SET_FASTPATH(cc, vm_call_single_noarg_leaf_builtin, true); } else { diff --git a/vm_sync.c b/vm_sync.c index d6eb47dfe725cf..6b988c1596f3a3 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -17,10 +17,16 @@ vm_locked(rb_vm_t *vm) } #if RUBY_DEBUG > 0 +static bool +vm_lock_assertable_p(void) +{ + return rb_current_execution_context(false) != NULL; +} + void RUBY_ASSERT_vm_locking(void) { - if (rb_multi_ractor_p()) { + if (vm_lock_assertable_p() && rb_multi_ractor_p()) { rb_vm_t *vm = GET_VM(); VM_ASSERT(vm_locked(vm)); } @@ -29,7 +35,7 @@ RUBY_ASSERT_vm_locking(void) void RUBY_ASSERT_vm_locking_with_barrier(void) { - if (rb_multi_ractor_p()) { + if (vm_lock_assertable_p() && rb_multi_ractor_p()) { rb_vm_t *vm = GET_VM(); VM_ASSERT(vm_locked(vm)); @@ -43,7 +49,7 @@ RUBY_ASSERT_vm_locking_with_barrier(void) void RUBY_ASSERT_vm_unlocking(void) { - if (rb_multi_ractor_p()) { + if (vm_lock_assertable_p() && rb_multi_ractor_p()) { rb_vm_t *vm = GET_VM(); VM_ASSERT(!vm_locked(vm)); } diff --git a/vm_trace.c b/vm_trace.c index 16ff2417e11b97..4fe5f0740d0c40 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -1411,7 +1411,7 @@ rb_tracepoint_enable_for_target(VALUE tpval, VALUE target, VALUE target_line) rb_hash_aset(tp->local_target_set, (VALUE)iseq, Qtrue); if ((tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN)) && - iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) { + ISEQ_BODY(iseq)->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) { rb_clear_bf_ccs(); } diff --git a/win32/win32.c b/win32/win32.c index 4f2504781466b1..d6d4acbb817ad2 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -843,6 +843,47 @@ static int w32_cmdvector(const WCHAR *, char ***, UINT, rb_encoding *); // // Initialization stuff // + +/* License: Ruby's */ +/* Enable long path names for this process, whatever the registry value + * HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled is. The + * loader sets the same PEB bit for longPathAware in the manifest, but only + * when that registry value is set, so the manifest alone is not enough on a + * stock machine. Undocumented, and taken from initLongPathSupport in Go's + * runtime , whose maintainers have + * proposed to drop it . + * + * Called from ruby_setup, not from rb_w32_sysinit, which a program that + * embeds libruby is not expected to call. */ +void +rb_w32_init_long_paths(void) +{ + /* PEB.BitField, and the IsLongPathAwareProcess bit in it */ + enum {peb_bit_field_offset = 3, is_long_path_aware_process = 0x80}; + typedef long (WINAPI version_func)(OSVERSIONINFOW *); + typedef void *(WINAPI peb_func)(void); + version_func *pRtlGetVersion; + peb_func *pRtlGetCurrentPeb; + OSVERSIONINFOW osvi; + BYTE *bit_field; + + pRtlGetVersion = (version_func *)get_proc_address("ntdll.dll", "RtlGetVersion", NULL); + if (!pRtlGetVersion) return; + memset(&osvi, 0, sizeof(osvi)); + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (pRtlGetVersion(&osvi)) return; + + /* the PEB bit is honored since Windows 10 1703 (10.0.15063) */ + if (osvi.dwMajorVersion < 10) return; + if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && + osvi.dwBuildNumber < 15063) return; + + pRtlGetCurrentPeb = (peb_func *)get_proc_address("ntdll.dll", "RtlGetCurrentPeb", NULL); + if (!pRtlGetCurrentPeb) return; + bit_field = (BYTE *)pRtlGetCurrentPeb() + peb_bit_field_offset; + *bit_field |= is_long_path_aware_process; +} + /* License: Ruby's */ void rb_w32_sysinit(int *argc, char ***argv) diff --git a/yjit.c b/yjit.c index e10c10f26d992b..823ae30cfd19e4 100644 --- a/yjit.c +++ b/yjit.c @@ -198,8 +198,8 @@ void * rb_iseq_get_yjit_payload(const rb_iseq_t *iseq) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - if (iseq->body) { - return iseq->body->yjit_payload; + if (ISEQ_BODY(iseq)) { + return ISEQ_BODY(iseq)->yjit_payload; } else { // Body is NULL when constructing the iseq. @@ -211,9 +211,9 @@ void rb_iseq_set_yjit_payload(const rb_iseq_t *iseq, void *payload) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - RUBY_ASSERT_ALWAYS(iseq->body); - RUBY_ASSERT_ALWAYS(NULL == iseq->body->yjit_payload); - iseq->body->yjit_payload = payload; + RUBY_ASSERT_ALWAYS(ISEQ_BODY(iseq)); + RUBY_ASSERT_ALWAYS(NULL == ISEQ_BODY(iseq)->yjit_payload); + ISEQ_BODY(iseq)->yjit_payload = payload; } // This is defined only as a named struct inside rb_iseq_constant_body. @@ -227,11 +227,11 @@ ID rb_get_symbol_id(VALUE namep); static bool invokebuiltin_delegate_leave_p(const rb_iseq_t *iseq) { - int insn1 = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[0]); - if ((int)iseq->body->iseq_size != insn_len(insn1) + insn_len(BIN(leave))) { + int insn1 = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[0]); + if ((int)ISEQ_BODY(iseq)->iseq_size != insn_len(insn1) + insn_len(BIN(leave))) { return false; } - int insn2 = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_len(insn1)]); + int insn2 = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_len(insn1)]); return (insn1 == BIN(opt_invokebuiltin_delegate) || insn1 == BIN(opt_invokebuiltin_delegate_leave)) && insn2 == BIN(leave); } @@ -241,7 +241,7 @@ const struct rb_builtin_function * rb_yjit_builtin_function(const rb_iseq_t *iseq) { if (invokebuiltin_delegate_leave_p(iseq)) { - return (const struct rb_builtin_function *)iseq->body->iseq_encoded[1]; + return (const struct rb_builtin_function *)ISEQ_BODY(iseq)->iseq_encoded[1]; } else { return NULL; @@ -347,9 +347,9 @@ num_digits(int integer) char * rb_yjit_iseq_inspect(const rb_iseq_t *iseq) { - const char *label = RSTRING_PTR(iseq->body->location.label); + const char *label = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); const char *path = RSTRING_PTR(rb_iseq_path(iseq)); - int lineno = iseq->body->location.code_location.beg_pos.lineno; + int lineno = ISEQ_BODY(iseq)->location.code_location.beg_pos.lineno; const size_t size = strlen(label) + strlen(path) + num_digits(lineno) + 3; char *buf = ZALLOC_N(char, size); @@ -392,10 +392,10 @@ rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit uintptr_t code_ptr = (uintptr_t)rb_yjit_iseq_gen_entry_point(iseq, ec, jit_exception); if (jit_exception) { - iseq->body->jit_exception = (rb_jit_func_t)code_ptr; + ISEQ_BODY(iseq)->jit_exception = (rb_jit_func_t)code_ptr; } else { - iseq->body->jit_entry = (rb_jit_func_t)code_ptr; + ISEQ_BODY(iseq)->jit_entry = (rb_jit_func_t)code_ptr; } } } diff --git a/zjit.c b/zjit.c index f915b4a8b5a525..2dcb20b55422c8 100644 --- a/zjit.c +++ b/zjit.c @@ -76,10 +76,10 @@ rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec, jit_exception); if (jit_exception) { - iseq->body->jit_exception = (rb_jit_func_t)code_ptr; + ISEQ_BODY(iseq)->jit_exception = (rb_jit_func_t)code_ptr; } else { - iseq->body->jit_entry = (rb_jit_func_t)code_ptr; + ISEQ_BODY(iseq)->jit_entry = (rb_jit_func_t)code_ptr; } } } @@ -94,11 +94,11 @@ rb_zjit_profile_enable(const rb_iseq_t *iseq) const void *const *insn_table = rb_vm_get_insns_address_table(); unsigned int insn_idx = 0; - while (insn_idx < iseq->body->iseq_size) { - int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]); + while (insn_idx < ISEQ_BODY(iseq)->iseq_size) { + int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]); int zjit_insn = vm_bare_insn_to_zjit_insn(insn); if (insn != zjit_insn) { - iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[zjit_insn]; + ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[zjit_insn]; } insn_idx += insn_len(insn); } @@ -112,11 +112,11 @@ rb_zjit_profile_disable(const rb_iseq_t *iseq) const void *const *insn_table = rb_vm_get_insns_address_table(); unsigned int insn_idx = 0; - while (insn_idx < iseq->body->iseq_size) { - int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]); + while (insn_idx < ISEQ_BODY(iseq)->iseq_size) { + int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]); int bare_insn = vm_zjit_insn_to_bare_insn(insn); if (insn != bare_insn) { - iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn]; + ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn]; } insn_idx += insn_len(insn); } @@ -134,11 +134,11 @@ void rb_zjit_iseq_insn_set(const rb_iseq_t *iseq, unsigned int insn_idx, enum ruby_vminsn_type bare_insn) { #if RUBY_DEBUG - int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]); + int insn = rb_vm_insn_addr2opcode((void *)ISEQ_BODY(iseq)->iseq_encoded[insn_idx]); RUBY_ASSERT(vm_zjit_insn_to_bare_insn(insn) == (int)bare_insn); #endif const void *const *insn_table = rb_vm_get_insns_address_table(); - iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn]; + ISEQ_BODY(iseq)->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn]; } // Get profiling information for ISEQ @@ -146,8 +146,8 @@ void * rb_iseq_get_zjit_payload(const rb_iseq_t *iseq) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - if (iseq->body) { - return iseq->body->zjit_payload; + if (ISEQ_BODY(iseq)) { + return ISEQ_BODY(iseq)->zjit_payload; } else { // Body is NULL when constructing the iseq. @@ -160,9 +160,9 @@ void rb_iseq_set_zjit_payload(const rb_iseq_t *iseq, void *payload) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); - RUBY_ASSERT_ALWAYS(iseq->body); - RUBY_ASSERT_ALWAYS(NULL == iseq->body->zjit_payload); - iseq->body->zjit_payload = payload; + RUBY_ASSERT_ALWAYS(ISEQ_BODY(iseq)); + RUBY_ASSERT_ALWAYS(NULL == ISEQ_BODY(iseq)->zjit_payload); + ISEQ_BODY(iseq)->zjit_payload = payload; } void diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index b53f1cf6733664..6301f5e4a59c63 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -886,12 +886,15 @@ pub fn stlxr(cb: &mut CodeBlock, rs: A64Opnd, rt: A64Opnd, rn: A64Opnd) { } /// STP (signed offset) - store a pair of registers to memory +/// +/// Unlike LDP, STP with the same source register twice is well-defined +/// (e.g. `stp xzr, xzr` is a common way to zero 16 bytes), so the pair is +/// not asserted to be distinct. pub fn stp(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt1, rt2, rn) { (A64Opnd::Reg(rt1), A64Opnd::Reg(rt2), A64Opnd::Mem(rn)) => { assert!(rt1.num_bits == rt2.num_bits, "Expected source registers to be the same size"); assert!(imm_fits_bits(rn.disp.into(), 10), "The displacement must be 10 bits or less."); - assert_ne!(rt1.reg_no, rt2.reg_no, "Behavior is unpredictable with pairs of the same register"); RegisterPair::stp(rt1.reg_no, rt2.reg_no, rn.base_reg_no, rn.disp as i16, rt1.num_bits).into() }, @@ -902,12 +905,13 @@ pub fn stp(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { } /// STP (pre-index) - store a pair of registers to memory, update the base pointer before loading it +/// +/// Unlike LDP, STP with the same source register twice is well-defined; see [stp]. pub fn stp_pre(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt1, rt2, rn) { (A64Opnd::Reg(rt1), A64Opnd::Reg(rt2), A64Opnd::Mem(rn)) => { assert!(rt1.num_bits == rt2.num_bits, "Expected source registers to be the same size"); assert!(imm_fits_bits(rn.disp.into(), 10), "The displacement must be 10 bits or less."); - assert_ne!(rt1.reg_no, rt2.reg_no, "Behavior is unpredictable with pairs of the same register"); RegisterPair::stp_pre(rt1.reg_no, rt2.reg_no, rn.base_reg_no, rn.disp as i16, rt1.num_bits).into() }, @@ -918,12 +922,13 @@ pub fn stp_pre(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { } /// STP (post-index) - store a pair of registers to memory, update the base pointer after loading it +/// +/// Unlike LDP, STP with the same source register twice is well-defined; see [stp]. pub fn stp_post(cb: &mut CodeBlock, rt1: A64Opnd, rt2: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt1, rt2, rn) { (A64Opnd::Reg(rt1), A64Opnd::Reg(rt2), A64Opnd::Mem(rn)) => { assert!(rt1.num_bits == rt2.num_bits, "Expected source registers to be the same size"); assert!(imm_fits_bits(rn.disp.into(), 10), "The displacement must be 10 bits or less."); - assert_ne!(rt1.reg_no, rt2.reg_no, "Behavior is unpredictable with pairs of the same register"); RegisterPair::stp_post(rt1.reg_no, rt2.reg_no, rn.base_reg_no, rn.disp as i16, rt1.num_bits).into() }, @@ -1822,6 +1827,14 @@ mod tests { assert_snapshot!(cb.hexdump(), @"8a2d8da8"); } + #[test] + fn test_stp_pre_same_reg() { + // Unlike LDP, storing the same register twice is well-defined. + let cb = compile(|cb| stp_pre(cb, X31, X31, A64Opnd::new_mem(64, X31, -16))); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stp xzr, xzr, [sp, #-0x10]!"); + assert_snapshot!(cb.hexdump(), @"ff7fbfa9"); + } + #[test] fn test_str_post() { let cb = compile(|cb| str_post(cb, X10, A64Opnd::new_mem(64, X11, -16))); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index b62bdc86bac69d..cf5f1a40d751f7 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -250,12 +250,33 @@ pub use crate::backend::current::{ mem_base_reg, Reg, EC, CFP, SP, - NATIVE_BASE_PTR, + NATIVE_BASE_PTR, NATIVE_STACK_PTR, C_ARG_OPNDS, C_RET_OPND, }; pub static JIT_PRESERVED_REGS: &[Opnd] = &[CFP, SP, EC]; +/// Where the C calling convention passes an argument. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CArgLocation { + /// In one of the argument registers. + Reg(Opnd), + /// In the stack slot that the caller reserves at the bottom of its frame. + /// The slot address depends on the reader: the caller writes it relative + /// to the native SP at the call, and the callee reads it from above its + /// return address and saved frame pointer. + StackSlot(usize), +} + +/// Return where the C calling convention passes argument `idx`. +pub fn c_arg_location(idx: usize) -> CArgLocation { + if idx < C_ARG_OPNDS.len() { + CArgLocation::Reg(C_ARG_OPNDS[idx]) + } else { + CArgLocation::StackSlot(idx - C_ARG_OPNDS.len()) + } +} + // Memory operand base #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub enum MemBase @@ -2575,33 +2596,22 @@ impl Assembler if self.basic_blocks[block_id.0].is_dummy() { continue; } let params = self.basic_blocks[block_id.0].parameters.clone(); - // JIT-to-JIT entries that would need more argument registers should - // be unreachable because can_direct_send() refuses to call them. - // Keep compiling the function body, but make the unsupported entry - // abort if control ever reaches it. TODO: Remove this (Shopify/ruby#916) - if params.len() > C_ARG_OPNDS.len() { - let insert_pos = self.basic_blocks[block_id.0].insns.iter() - .position(|insn| matches!(insn, Insn::FrameSetup { .. })) - .or_else(|| self.basic_blocks[block_id.0].insns.iter().position(|insn| matches!(insn, Insn::Label(_))).map(|idx| idx + 1)) - .unwrap_or(0); - self.basic_blocks[block_id.0].insns.insert(insert_pos, Insn::Abort); - self.basic_blocks[block_id.0].insn_ids.insert(insert_pos, None); - continue; - } - - // Rewrite VRegs to physical registers before sequentialization - // so the parcopy algorithm can detect physical register conflicts. - let reg_copies: Vec> = params.iter().enumerate() + // Rewrite VRegs to physical registers or stack slots before sequentialization + // so the parcopy algorithm can detect conflicts between them. + let copies: Vec> = params.iter().enumerate() .map(|(i, param)| parcopy::RegisterCopy:: { - source: C_ARG_OPNDS[i], + source: match c_arg_location(i) { + CArgLocation::Reg(reg) => reg, + CArgLocation::StackSlot(slot) => Opnd::mem(64, NATIVE_BASE_PTR, Self::frame_size() + slot as i32 * SIZEOF_VALUE_I32), + }, destination: Self::rewritten_opnd(*param, intervals, regs), }) .filter(|copy| copy.source != copy.destination) .collect(); - debug_assert!(reg_copies.iter().all(|c| !c.source.is_vreg() && !c.destination.is_vreg()), - "parcopy must operate on physical registers, not VRegs"); - let sequentialized = parcopy::sequentialize_register(®_copies, Opnd::Reg(SCRATCH_REG)); + debug_assert!(copies.iter().all(|c| !c.source.is_vreg() && !c.destination.is_vreg()), + "parcopy must operate on physical locations, not VRegs"); + let sequentialized = parcopy::sequentialize_register(&copies, Opnd::Reg(SCRATCH_REG)); let moves: Vec = sequentialized .iter() .map(|copy| match copy.source { @@ -2616,9 +2626,13 @@ impl Assembler }) .collect(); - // Find the position after FrameSetup to insert moves + // Find the position after FrameSetup to insert moves. They must come + // after FrameSetup (not before) because spilled destinations and + // stack-passed parameter sources are NATIVE_BASE_PTR-relative, and + // NATIVE_BASE_PTR points at the caller's frame until FrameSetup. let insert_pos = self.basic_blocks[block_id.0].insns.iter() .position(|insn| matches!(insn, Insn::FrameSetup { .. })) + .map(|idx| idx + 1) .or_else(|| self.basic_blocks[block_id.0].insns.iter().position(|insn| matches!(insn, Insn::Label(_))).map(|idx| idx + 1)) .unwrap_or(0); @@ -5143,18 +5157,26 @@ mod tests { } #[test] - fn test_resolve_ssa_entry_params_too_many_abort() { + fn test_resolve_ssa_entry_params_beyond_arg_regs_use_stack() { let mut asm = Assembler::new(); let block = asm.new_block(hir::BlockId(0), true, 0); asm.set_current_block(block); let label = asm.new_label("bb0"); asm.write_label(label); - for _ in 0..=C_ARG_OPNDS.len() { + let params: Vec = (0..=C_ARG_OPNDS.len()).map(|_| { let param = asm.new_vreg(64); asm.basic_blocks[block.0].add_parameter(param); + param + }).collect(); + // Use every parameter so they are all live and get allocations. + let mut acc = params[0]; + for ¶m in ¶ms[1..] { + let out = asm.new_vreg(64); + asm.basic_blocks[block.0].push_insn(Insn::Add { left: acc, right: param, out }); + acc = out; } - asm.basic_blocks[block.0].push_insn(Insn::CRet(Opnd::UImm(0))); + asm.basic_blocks[block.0].push_insn(Insn::CRet(acc)); let live_in = asm.analyze_liveness(); asm.number_instructions(0); @@ -5165,7 +5187,16 @@ mod tests { asm.resolve_ssa(&intervals, ®s); - assert!(matches!(asm.basic_blocks[block.0].insns[1], Insn::Abort)); + // The parameter that doesn't fit in argument registers is loaded from + // the caller's outgoing-argument area, right above this frame's + // return address and saved frame pointer. + let insns = &asm.basic_blocks[block.0].insns; + assert!(!insns.iter().any(|insn| matches!(insn, Insn::Abort)), "no entry should abort"); + let expected_src = Opnd::mem(64, NATIVE_BASE_PTR, Assembler::frame_size()); + assert!( + insns.iter().any(|insn| matches!(insn, Insn::Mov { src, .. } if *src == expected_src)), + "expected a load from {expected_src:?}, got: {insns:?}" + ); } fn build_critical_edge() -> (Assembler, Opnd, Opnd, Opnd, Opnd, Opnd, BlockId, BlockId, BlockId) { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 9f4ea3050ab65b..e39822242b523d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -22,7 +22,7 @@ use crate::state::ZJITState; use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_fallback_counter_for_optimized_method_type}; use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, StackMap, StackMapEntry, Target, asm_ccall, asm_comment}; +use crate::backend::lir::{self, Assembler, CArgLocation, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, NATIVE_STACK_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, StackMap, StackMapEntry, Target, asm_ccall, asm_comment}; use crate::hir::{self, iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason, qualified_method_name}; use crate::hir_type::{types, Type}; @@ -3937,15 +3937,26 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result reg, + CArgLocation::StackSlot(slot) => { + // The stub runs before any frame setup, so stack-passed arguments + // sit right above the return address (x86_64) or right at the + // native SP (arm64, where the return address is in a register). + let ret_addr_bytes = if cfg!(target_arch = "x86_64") { SIZEOF_VALUE_I32 } else { 0 }; + asm.load_into(scratch_reg, Opnd::mem(64, NATIVE_STACK_PTR, ret_addr_bytes + slot as i32 * SIZEOF_VALUE_I32)); + scratch_reg + } + }; asm.store( Opnd::mem(64, SP, -local_size_and_idx_to_bp_offset(local_size, arg_idx) * SIZEOF_VALUE_I32), - C_ARG_OPNDS[arg_idx + 1], + src, ); } diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 0ae3b270c23d02..e51fdfc8093908 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -713,10 +713,10 @@ fn test_yield_inline_invocation_with_args() { } #[test] -fn test_yield_with_too_many_args_for_lir() { +fn test_yield_with_more_args_than_abi_registers() { // `self` + eight yield args don't fit in C argument registers (6 on x86_64, 8 on - // arm64), so the direct block invocation must be rejected instead of emitting an - // uncompilable CCall. + // arm64), so the direct block invocation passes the overflow arguments on the + // native stack. set_call_threshold(2); eval(" def foo = yield(1, 2, 3, 4, 5, 6, 7, 8) @@ -727,6 +727,37 @@ fn test_yield_with_too_many_args_for_lir() { assert_snapshot!(assert_compiles("test"), @"36"); } +#[test] +fn test_send_direct_with_more_args_than_abi_registers() { + // `self` + ten args don't fit in C argument registers (6 on x86_64, 8 on arm64), + // so the JIT-to-JIT call passes the overflow arguments on the native stack, and + // the callee's JIT entry loads them from above its frame. + set_call_threshold(2); + eval(" + def callee(a, b, c, d, e, f, g, h, i, j) = [a, b, c, d, e, f, g, h, i, j] + def test = callee(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + test + test + "); + assert_snapshot!(assert_compiles("test"), @"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"); +} + +#[test] +fn test_send_direct_with_equal_args_beyond_abi_registers() { + // A pair of adjacent stack-passed arguments that are the same zero immediate + // (false) or the same register lowers to an STP with an identical register + // pair on arm64, e.g. `stp xzr, xzr`, which the assembler used to reject. + // On arm64, c_args[8] and c_args[9] (arguments h and i below) form a pair. + set_call_threshold(2); + eval(" + def callee(a, b, c, d, e, f, g, h, i, j) = [a, b, c, d, e, f, g, h, i, j] + def test(x = 9) = [callee(1, 2, 3, 4, 5, 6, 7, false, false, 10), callee(1, 2, 3, 4, 5, 6, 7, x, x, 10)] + test + test + "); + assert_snapshot!(assert_compiles("test"), @"[[1, 2, 3, 4, 5, 6, 7, false, false, 10], [1, 2, 3, 4, 5, 6, 7, 9, 9, 10]]"); +} + #[test] fn test_yield_inline_invocation_live_stack_below_args() { // A live value sits on the stack below the yield args; the no-receiver-slot SP math diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index da312c1e980d2a..86df1e3abf9045 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2690,14 +2690,8 @@ fn can_direct_send(iseq: *const rb_iseq_t, ci: *const rb_callinfo, args: &[InsnI return Err(SendDirectFailure::new(ArgcParamMismatch)); } - // asm.ccall() doesn't support 6+ args. Compute the final argc after keyword setup - // and rest packing: + // Compute the final argc after keyword setup and rest packing: // send_argc = packed positional args + callee's total keywords (all kw slots are filled). - // c_argc = self + send_argc + synthetic block handler arg. - // Right now, the JIT entrypoint accepts the block as an param - // We may remove it, remove the block_arg addition to match - // See: https://github.com/ruby/ruby/pull/15911#discussion_r2710544982 - let block_arg = if 0 != params.flags.has_block() { 1 } else { 0 }; // With *rest, SendDirect receives one rest-array slot instead of each rest // element, so cap positional argc at required/post + filled opts + rest slot. let passed_opt_num = (caller_positional_i32 - min_positional).min(opt_num) as usize; @@ -2705,12 +2699,6 @@ fn can_direct_send(iseq: *const rb_iseq_t, ci: *const rb_callinfo, args: &[InsnI // keyword Hash is included in the SendDirect argument count. let send_positional_argc = if has_rest { min_positional as usize + passed_opt_num + 1 } else { effective_positional }; let send_argc = send_positional_argc + kw_total_num as usize; - let c_argc = 1 + send_argc + block_arg; // +1 for self - - // TODO: Support passing arguments on the stack in C calls - if c_argc > C_ARG_OPNDS.len() { - return Err(SendDirectFailure::new(TooManyArgsForLir)); - } // IseqCall stores the JIT entry index and argc as u16. if u16::try_from(send_argc).is_err() { @@ -2992,12 +2980,6 @@ fn can_direct_invoke_block_iseq(iseq: IseqPtr, argc: usize) -> Result<(), SendFa if argc == 1 && !unsafe { rb_get_iseq_flags_ambiguous_param0(iseq) } { return Err(InvokeBlockNotSpecialized); } - // The JIT-to-JIT call in gen_invoke_block_iseq_direct passes captured self plus - // each argument in C argument registers. - // TODO: Support passing arguments on the stack in C calls - if 1 + argc > C_ARG_OPNDS.len() { - return Err(TooManyArgsForLir); - } if crate::codegen::block_iseq_may_throw(iseq) { return Err(InvokeBlockNotSpecialized); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 82037629c170a9..f05eb4f592bd04 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4217,10 +4217,10 @@ mod hir_opt_tests { } #[test] - fn test_yield_with_too_many_args_for_lir_falls_back() { + fn test_specialize_yield_with_more_args_than_abi_registers() { // Captured self plus eight args don't fit in C argument registers (6 on x86_64, - // 8 on arm64), so the profiled invokeblock specialization must not emit - // InvokeBlockIseqDirect. + // 8 on arm64); the profiled invokeblock specialization still emits + // InvokeBlockIseqDirect, passing the overflow arguments on the stack. let result = eval(" def foo = yield(1, 2, 3, 4, 5, 6, 7, 8) def test = foo { |a, b, c, d, e, f, g, h| a + b + c + d + e + f + g + h } @@ -4247,15 +4247,24 @@ mod hir_opt_tests { v20:Fixnum[6] = Const Value(6) v22:Fixnum[7] = Const Value(7) v24:Fixnum[8] = Const Value(8) - v26:BasicObject = InvokeBlock v10, v12, v14, v16, v18, v20, v22, v24 # SendFallbackReason: Too many arguments for LIR + v26:CPtr = GetEP 0 + v27:CInt64 = LoadField v26, :VM_ENV_DATA_INDEX_SPECVAL@0x1000 + v28:CInt64[3] = Const CInt64(3) + v29:CInt64 = IntAnd v27, v28 + v30:CInt64[1] = GuardBitEquals v29, CInt64(1) recompile + v31:CInt64[-4] = Const CInt64(-4) + v32:CInt64 = IntAnd v27, v31 + v33:CPtr = LoadField v32, :code_iseq@0x1001 + v34:CPtr[CPtr(0x1002)] = GuardBitEquals v33, CPtr(0x1002) recompile + v35:BasicObject = InvokeBlockIseqDirect (0x1002), v32, v10, v12, v14, v16, v18, v20, v22, v24 CheckInterrupts - Return v26 + Return v35 "); } #[test] - fn test_inlined_yield_with_too_many_args_for_lir_falls_back() { - // Same as test_yield_with_too_many_args_for_lir_falls_back, but for the guard-free + fn test_specialize_inlined_yield_with_more_args_than_abi_registers() { + // Same as test_specialize_yield_with_more_args_than_abi_registers, but for the guard-free // yield dispatch inside an inlined callee whose caller passes a literal block. let result = eval(" def foo = yield(1, 2, 3, 4, 5, 6, 7, 8) @@ -4286,10 +4295,14 @@ mod hir_opt_tests { v35:Fixnum[6] = Const Value(6) v37:Fixnum[7] = Const Value(7) v39:Fixnum[8] = Const Value(8) - v41:BasicObject = InvokeBlock v25, v27, v29, v31, v33, v35, v37, v39 # SendFallbackReason: Too many arguments for LIR + v41:CPtr = GetEP 0 + v42:CInt64 = LoadField v41, :VM_ENV_DATA_INDEX_SPECVAL@0x1060 + v43:CInt64[-4] = Const CInt64(-4) + v44:CInt64 = IntAnd v42, v43 + v45:BasicObject = InvokeBlockIseqDirect (0x1068), v44, v25, v27, v29, v31, v33, v35, v37, v39 CheckInterrupts PopInlineFrame - Return v41 + Return v45 "); } @@ -5295,8 +5308,9 @@ mod hir_opt_tests { v37:Fixnum[60] = Const Value(60) v39:Fixnum[70] = Const Value(70) v41:Fixnum[80] = Const Value(80) - v43:BasicObject = Send v52, :target, v27, v29, v31, v33, v35, v37, v39, v41 # SendFallbackReason: Too many arguments for LIR - v45:ArrayExact = NewArray v53, v56, v43 + PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) + v59:BasicObject = SendDirect v52, 0x0, :target (0x1038), jit_entry_idx=7, v27, v29, v31, v33, v35, v37, v39, v41 + v45:ArrayExact = NewArray v53, v56, v59 CheckInterrupts Return v45 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 444e81f0751fa7..ed819e7a34a65f 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -293,11 +293,18 @@ mod snapshot_tests { v23:Fixnum[7] = Const Value(7) v25:Fixnum[8] = Const Value(8) v26:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13, v15, v17, v19, v21, v23, v25], locals: [] } - v27:BasicObject = Send v6, :foo, v11, v13, v15, v17, v19, v21, v23, v25 # SendFallbackReason: Too many arguments for LIR - v28:Any = Snapshot FrameState { pc: 0x1010, stack: [v27], locals: [] } - PatchPoint NoTracePoint + PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) + v34:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile + v35:Any = Snapshot FrameState { pc: 0x1008, stack: [v34, v11, v13, v19, v21, v17, v15, v23, v25], locals: [] } + v64:Fixnum[0] = Const Value(0) + v37:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [] } + PushInlineFrame :foo, v34 (0x1048), num_args=8 + v58:Any = Snapshot FrameState { pc: 0x1070, stack: [v19, v21, v17, v15, v11, v13, v23, v25], locals: [five=v11, six=v13, a=v19, b=v21, c=v17, d=v15, e=v23, f=v25, ID(0)=v64], caller: v37 } + v59:ArrayExact = NewArray v19, v21, v17, v15, v11, v13, v23, v25 + v60:Any = Snapshot FrameState { pc: 0x1078, stack: [v59], locals: [five=v11, six=v13, a=v19, b=v21, c=v17, d=v15, e=v23, f=v25, ID(0)=v64], caller: v37 } CheckInterrupts - Return v27 + PopInlineFrame + Return v59 "); } }