diff options
65 files changed, 1110 insertions, 386 deletions
@@ -51,7 +51,7 @@ define rp printf "%sT_OBJECT%s: ", $color_type, $color_end print ((struct RObject *)($arg0))->basic if ($flags & ROBJECT_EMBED) - print/x *((VALUE*)((struct RObject*)($arg0))->as.ary) @ (rb_shape_get_shape($arg0)->capacity) + print/x *((VALUE*)((struct RObject*)($arg0))->as.ary) @ (RSHAPE_CAPACITY(rb_obj_shape_id($arg0))) else print (((struct RObject *)($arg0))->as.heap) if (((struct RObject*)($arg0))->as.heap.numiv) > 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 426893be2a..2c2982d1d4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,3 +16,7 @@ updates: directory: '/yjit' schedule: interval: 'daily' + - package-ecosystem: 'vcpkg' + directory: '/' + schedule: + interval: 'daily' diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 2d2427a907..3a2ba704ae 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -17,7 +17,7 @@ jobs: id: metadata - name: Wait for status checks - uses: lewagon/wait-on-check-action@31f07a800aa1ba8518509dc8561cdf5a891deb4b # v1.4.0 + uses: lewagon/wait-on-check-action@0dceb95e7c4cad8cc7422aee3885998f5cab9c79 # v1.4.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0b7bf25d95..3e85f7a1c5 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -86,7 +86,7 @@ jobs: - name: Restore vcpkg artifact id: restore-vcpkg - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -98,7 +98,7 @@ jobs: if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - name: Save vcpkg artifact - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -47,6 +47,10 @@ Note: We're only listing outstanding class updates. * `IO.select` accepts +Float::INFINITY+ as a timeout argument. [[Feature #20610]] +* Math + + * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] + * Socket * `Socket.tcp` & `TCPSocket.new` accepts `open_timeout` as a keyword argument to specify @@ -254,3 +258,4 @@ The following bundled gems are updated. [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 +[Feature #21527]: https://bugs.ruby-lang.org/issues/21527 diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index b1e9e3a79d..4a58ece8ac 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1573,6 +1573,33 @@ assert_equal 'true', %q{ rs.map{|r| r.value} == Array.new(RN){n} } +# check method cache invalidation +assert_equal 'true', %q{ + class Foo + def hello = nil + end + + r1 = Ractor.new do + 1000.times do + class Foo + def hello = nil + end + end + end + + r2 = Ractor.new do + 1000.times do + o = Foo.new + o.hello + end + end + + r1.value + r2.value + + true +} + # check experimental warning assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ Warning[:experimental] = $VERBOSE = true @@ -205,13 +205,6 @@ $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/util/.time: $(Q) $(MAKEDIRS) $(@D) @$(NULLCMD) > $@ -$(PRISM_SRCDIR)/srcs.mk: $(HAVE_BASERUBY:yes=$(PRISM_SRCDIR)/templates/template.rb) \ - $(HAVE_BASERUBY:yes=$(PRISM_SRCDIR)/generate-srcs.mk.rb) - $(ECHO) Updating prism/srcs.mk - $(BASERUBY) $(PRISM_SRCDIR)/generate-srcs.mk.rb > $@ - -srcs: $(PRISM_SRCDIR)/srcs.mk - EXPORTOBJS = $(DLNOBJ) \ localeinit.$(OBJEXT) \ loadpath.$(OBJEXT) \ @@ -1221,7 +1214,6 @@ incs: $(INSNS) {$(VPATH)}node_name.inc {$(VPATH)}known_errors.inc \ {$(VPATH)}vm_call_iseq_optimized.inc $(srcdir)/revision.h \ $(REVISION_H) \ $(UNICODE_DATA_HEADERS) $(ENC_HEADERS) \ - $(top_srcdir)/prism/ast.h $(top_srcdir)/prism/diagnostic.h \ {$(VPATH)}id.h {$(VPATH)}probes.dmyh insns: $(INSNS) @@ -1310,6 +1302,11 @@ $(REVISION_H)$(yes_baseruby:yes=~disabled~): # uncommon.mk: $(REVISION_H) # $(MKFILES): $(REVISION_H) +# $(common_mk_includes) is set by config.status or GNUmakefile +common_mk__$(gnumake:yes=artifact)_ = uncommon.mk +common_mk_$(gnumake)_artifact_ = $(MKFILES) +$(common_mk__artifact_): $(srcdir)/common.mk $(common_mk_includes) + ripper_srcs: $(RIPPER_SRCS) $(RIPPER_SRCS): $(srcdir)/parse.y $(srcdir)/defs/id.def @@ -1982,3 +1979,4 @@ $(CROSS_COMPILING:yes=)builtin.$(OBJEXT): {$(VPATH)}mini_builtin.c $(CROSS_COMPILING:yes=)builtin.$(OBJEXT): {$(VPATH)}miniprelude.c !include $(srcdir)/prism/srcs.mk +!include $(srcdir)/depend diff --git a/configure.ac b/configure.ac index 366ffe1e05..82f144a8bb 100644 --- a/configure.ac +++ b/configure.ac @@ -1680,21 +1680,6 @@ AS_IF([test "$rb_cv_func_weak" != x], [ AC_DEFINE(HAVE_FUNC_WEAK) ]) -AC_CACHE_CHECK([for __attribute__((__deprecated__(msg))) in C++], - rb_cv_CentOS6_CXX_workaround, - RUBY_WERROR_FLAG([ - AC_LANG_PUSH([C++]) - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM( - [], - [__attribute__((__deprecated__("message"))) int conftest(...);])], - [rb_cv_CentOS6_CXX_workaround=yes], - [rb_cv_CentOS6_CXX_workaround=no]) - AC_LANG_POP()])) -AS_IF([test "$rb_cv_CentOS6_CXX_workaround" != no],[ - AC_DEFINE([RUBY_CXX_DEPRECATED(msg)], - [__attribute__((__deprecated__(msg)))])]) - AC_CACHE_CHECK([for std::nullptr_t], rb_cv_CXX_nullptr, [ AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE( @@ -4698,9 +4683,12 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ sed '/^MISSING/s/\$U\././g;/^VCS *=/s#@VCS@#'"$VCS"'#;/^VCSUP *=/s#@VCSUP@#'"$VCSUP"'#' Makefile echo; test x"$EXEEXT" = x || echo 'miniruby: miniruby$(EXEEXT)' AS_IF([test "$gnumake" != yes], [ - echo ['$(MKFILES): $(srcdir)/common.mk $(srcdir)/depend $(srcdir)/prism/srcs.mk'] - sed ['s/{\$([^(){}]*)[^{}]*}//g;/^!/d'] ${srcdir}/common.mk ${srcdir}/depend - cat ${srcdir}/prism/srcs.mk + # extract NMake-style include list + set = `sed -n 's/^!include *//p' ${srcdir}/common.mk` + echo common_mk_includes "@S|@*" # generate the macro assignment + shift + common_mk_includes="`echo \"@S|@*\" | sed 's|\$(srcdir)|.|g'`" + (PWD= cd ${srcdir} && sed -f tool/prereq.status common.mk ${common_mk_includes}) AS_IF([test "$YJIT_SUPPORT" = yes], [ cat ${srcdir}/yjit/not_gmake.mk echo ['$(MKFILES): ${srcdir}/yjit/not_gmake.mk'] @@ -4723,6 +4711,7 @@ AC_CONFIG_FILES(Makefile:template/Makefile.in, [ ]) && test -z "`${MAKE-make} -f $tmpgmk info-program | grep '^PROGRAM=ruby$'`" && echo 'ruby: $(PROGRAM);' >> $tmpmk + rm -f uncommon.mk # remove stale uncommon.mk, it should be updated by GNUmakefile test "$tmpmk" = "$tmpgmk" || rm -f "$tmpgmk" ]) && mv -f $tmpmk Makefile], [EXEEXT='$EXEEXT' MAKE='${MAKE-make}' gnumake='$gnumake' GIT='$GIT' YJIT_SUPPORT='$YJIT_SUPPORT']) @@ -6306,6 +6306,7 @@ imemo.$(OBJEXT): $(top_srcdir)/internal/compilers.h imemo.$(OBJEXT): $(top_srcdir)/internal/gc.h imemo.$(OBJEXT): $(top_srcdir)/internal/imemo.h imemo.$(OBJEXT): $(top_srcdir)/internal/namespace.h +imemo.$(OBJEXT): $(top_srcdir)/internal/object.h imemo.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h imemo.$(OBJEXT): $(top_srcdir)/internal/serial.h imemo.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -7159,7 +7160,6 @@ iseq.$(OBJEXT): $(top_srcdir)/prism/pack.h iseq.$(OBJEXT): $(top_srcdir)/prism/parser.h iseq.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h iseq.$(OBJEXT): $(top_srcdir)/prism/prism.h -iseq.$(OBJEXT): $(top_srcdir)/prism/prism.h iseq.$(OBJEXT): $(top_srcdir)/prism/regexp.h iseq.$(OBJEXT): $(top_srcdir)/prism/static_literals.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h @@ -11676,7 +11676,6 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/version.h -prism/prism.$(OBJEXT): $(top_srcdir)/prism/version.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/ast.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/encoding.h diff --git a/doc/string/hash.rdoc b/doc/string/hash.rdoc new file mode 100644 index 0000000000..fe94770ed9 --- /dev/null +++ b/doc/string/hash.rdoc @@ -0,0 +1,19 @@ +Returns the integer hash value for +self+. + +Two \String objects that have identical content and compatible encodings +also have the same hash value; +see Object#hash and {Encodings}[rdoc-ref:encodings.rdoc]: + + s = 'foo' + h = s.hash # => -569050784 + h == 'foo'.hash # => true + h == 'food'.hash # => false + h == 'FOO'.hash # => false + + s0 = "äöü" + s1 = s0.encode(Encoding::ISO_8859_1) + s0.encoding # => #<Encoding:UTF-8> + s1.encoding # => #<Encoding:ISO-8859-1> + s0.hash == s1.hash # => false + +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c index 8291578f27..c18596cbf5 100644 --- a/ext/openssl/ossl_x509store.c +++ b/ext/openssl/ossl_x509store.c @@ -191,8 +191,8 @@ ossl_x509store_set_vfy_cb(VALUE self, VALUE cb) GetX509Store(self, store); rb_iv_set(self, "@verify_callback", cb); - // We don't need to trigger a write barrier because `rb_iv_set` did it. X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb); + RB_OBJ_WRITTEN(self, Qundef, cb); return cb; } @@ -611,6 +611,7 @@ ossl_x509stctx_verify(VALUE self) GetX509StCtx(self, ctx); VALUE cb = rb_iv_get(self, "@verify_callback"); X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, (void *)cb); + RB_OBJ_WRITTEN(self, Qundef, cb); switch (X509_verify_cert(ctx)) { case 1: diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index bc6c303c36..ca00e51ee7 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -578,6 +578,10 @@ start: #endif +#define GETNAMEINFO_WONT_BLOCK(host, serv, flags) \ + ((!(host) || ((flags) & NI_NUMERICHOST)) && \ + (!(serv) || ((flags) & NI_NUMERICSERV))) + #if GETADDRINFO_IMPL == 0 int @@ -615,6 +619,10 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) { + if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { + return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + } + struct getnameinfo_arg arg; int ret; arg.sa = sa; @@ -743,6 +751,10 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, struct getnameinfo_arg *arg; int err = 0, gni_errno = 0; + if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { + return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + } + start: retry = 0; @@ -332,8 +332,6 @@ rb_gc_multi_ractor_p(void) return rb_multi_ractor_p(); } -bool rb_obj_is_main_ractor(VALUE gv); - bool rb_gc_shutdown_call_finalizer_p(VALUE obj) { @@ -321,40 +321,35 @@ objid_hash(VALUE obj) #endif } -/** +/* * call-seq: - * obj.hash -> integer - * - * Generates an Integer hash value for this object. This function must have the - * property that <code>a.eql?(b)</code> implies <code>a.hash == b.hash</code>. - * - * The hash value is used along with #eql? by the Hash class to determine if - * two objects reference the same hash key. Any hash value that exceeds the - * capacity of an Integer will be truncated before being used. + * hash -> integer * - * The hash value for an object may not be identical across invocations or - * implementations of Ruby. If you need a stable identifier across Ruby - * invocations and implementations you will need to generate one with a custom - * method. + * Returns the integer hash value for +self+; + * has the property that if <tt>foo.eql?(bar)</tt> + * then <tt>foo.hash == bar.hash</tt>. * - * Certain core classes such as Integer use built-in hash calculations and - * do not call the #hash method when used as a hash key. + * \Class Hash uses both #hash and #eql? to determine whether two objects + * used as hash keys are to be treated as the same key. + * A hash value that exceeds the capacity of an Integer is truncated before being used. * - * When implementing your own #hash based on multiple values, the best - * practice is to combine the class and any values using the hash code of an - * array: + * Many core classes override method Object#hash; + * other core classes (e.g., Integer) calculate the hash internally, + * and do not call the #hash method when used as a hash key. * - * For example: + * When implementing #hash for a user-defined class, + * best practice is to use Array#hash with the class name and the values + * that are important in the instance; + * this takes advantage of that method's logic for safely and efficiently + * generating a hash value: * * def hash * [self.class, a, b, c].hash * end * - * The reason for this is that the Array#hash method already has logic for - * safely and efficiently combining multiple hash values. - *-- - * \private - *++ + * The hash value may differ among invocations or implementations of Ruby. + * If you need stable hash-like identifiers across Ruby invocations and implementations, + * use a custom method to generate them. */ VALUE rb_obj_hash(VALUE obj) @@ -3,6 +3,7 @@ #include "id_table.h" #include "internal.h" #include "internal/imemo.h" +#include "internal/object.h" #include "internal/st.h" #include "vm_callinfo.h" @@ -208,6 +209,8 @@ rb_imemo_fields_clear(VALUE fields_obj) else { RBASIC_SET_SHAPE_ID(fields_obj, ROOT_SHAPE_ID); } + // Invalidate the ec->gen_fields_cache. + RBASIC_CLEAR_CLASS(fields_obj); } /* ========================================================================= diff --git a/internal/class.h b/internal/class.h index db4ae2ada1..bed69adef7 100644 --- a/internal/class.h +++ b/internal/class.h @@ -630,7 +630,7 @@ RCLASS_WRITE_CALLABLE_M_TBL(VALUE klass, struct rb_id_table *table) static inline void RCLASS_WRITE_CC_TBL(VALUE klass, VALUE table) { - RB_OBJ_WRITE(klass, &RCLASSEXT_CC_TBL(RCLASS_EXT_WRITABLE(klass)), table); + RB_OBJ_ATOMIC_WRITE(klass, &RCLASSEXT_CC_TBL(RCLASS_EXT_WRITABLE(klass)), table); } static inline void diff --git a/internal/re.h b/internal/re.h index 2788f8b42a..593e5c464f 100644 --- a/internal/re.h +++ b/internal/re.h @@ -25,4 +25,9 @@ int rb_match_count(VALUE match); VALUE rb_reg_new_ary(VALUE ary, int options); VALUE rb_reg_last_defined(VALUE match); +#define ARG_REG_OPTION_MASK \ + (ONIG_OPTION_IGNORECASE|ONIG_OPTION_MULTILINE|ONIG_OPTION_EXTEND) +#define ARG_ENCODING_FIXED 16 +#define ARG_ENCODING_NONE 32 + #endif /* INTERNAL_RE_H */ diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb index 46c9c8adac..0f82d8362a 100644 --- a/lib/syntax_suggest/api.rb +++ b/lib/syntax_suggest/api.rb @@ -146,11 +146,7 @@ module SyntaxSuggest def self.valid_without?(without_lines:, code_lines:) lines = code_lines - Array(without_lines).flatten - if lines.empty? - true - else - valid?(lines) - end + lines.empty? || valid?(lines) end # SyntaxSuggest.invalid? [Private] @@ -457,6 +457,34 @@ math_exp(VALUE unused_obj, VALUE x) return DBL2NUM(exp(Get_Double(x))); } +/* + * call-seq: + * Math.expm1(x) -> float + * + * Returns "exp(x) - 1", +e+ raised to the +x+ power, minus 1. + * + * + * - Domain: <tt>[-INFINITY, INFINITY]</tt>. + * - Range: <tt>[-1.0, INFINITY]</tt>. + * + * Examples: + * + * expm1(-INFINITY) # => 0.0 + * expm1(-1.0) # => -0.6321205588285577 # 1.0/E - 1 + * expm1(0.0) # => 0.0 + * expm1(0.5) # => 0.6487212707001282 # sqrt(E) - 1 + * expm1(1.0) # => 1.718281828459045 # E - 1 + * expm1(2.0) # => 6.38905609893065 # E**2 - 1 + * expm1(INFINITY) # => Infinity + * + */ + +static VALUE +math_expm1(VALUE unused_obj, VALUE x) +{ + return DBL2NUM(expm1(Get_Double(x))); +} + #if defined __CYGWIN__ # include <cygwin/version.h> # if CYGWIN_VERSION_DLL_MAJOR < 1005 @@ -646,6 +674,47 @@ math_log10(VALUE unused_obj, VALUE x) return DBL2NUM(log10(d) + numbits * log10(2)); /* log10(d * 2 ** numbits) */ } +/* + * call-seq: + * Math.log1p(x) -> float + * + * Returns "log(x + 1)", the base E {logarithm}[https://en.wikipedia.org/wiki/Logarithm] of (+x+ + 1). + * + * - Domain: <tt>[-1, INFINITY]</tt>. + * - Range: <tt>[-INFINITY, INFINITY]</tt>. + * + * Examples: + * + * log1p(-1.0) # => -Infinity + * log1p(0.0) # => 0.0 + * log1p(E - 1) # => 1.0 + * log1p(INFINITY) # => Infinity + * + */ + +static VALUE +math_log1p(VALUE unused_obj, VALUE x) +{ + size_t numbits; + double d = get_double_rshift(x, &numbits); + + if (numbits != 0) { + x = rb_big_plus(x, INT2FIX(1)); + d = math_log_split(x, &numbits); + /* check for pole error */ + if (d == 0.0) return DBL2NUM(-HUGE_VAL); + d = log(d); + d += numbits * M_LN2; + return DBL2NUM(d); + } + + domain_check_min(d, -1.0, "log1p"); + /* check for pole error */ + if (d == -1.0) return DBL2NUM(-HUGE_VAL); + + return DBL2NUM(log1p(d)); /* log10(d * 2 ** numbits) */ +} + static VALUE rb_math_sqrt(VALUE x); /* @@ -1120,9 +1189,11 @@ InitVM_Math(void) rb_define_module_function(rb_mMath, "atanh", math_atanh, 1); rb_define_module_function(rb_mMath, "exp", math_exp, 1); + rb_define_module_function(rb_mMath, "expm1", math_expm1, 1); rb_define_module_function(rb_mMath, "log", math_log, -1); rb_define_module_function(rb_mMath, "log2", math_log2, 1); rb_define_module_function(rb_mMath, "log10", math_log10, 1); + rb_define_module_function(rb_mMath, "log1p", math_log1p, 1); rb_define_module_function(rb_mMath, "sqrt", math_sqrt, 1); rb_define_module_function(rb_mMath, "cbrt", math_cbrt, 1); diff --git a/prism/srcs.mk b/prism/srcs.mk new file mode 100644 index 0000000000..aa5c0fa2b5 --- /dev/null +++ b/prism/srcs.mk @@ -0,0 +1,142 @@ +PRISM_TEMPLATES_DIR = $(PRISM_SRCDIR)/templates +PRISM_TEMPLATE = $(PRISM_TEMPLATES_DIR)/template.rb +PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml + +srcs uncommon.mk: prism/.srcs.mk.time + +prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ + $(PRISM_SRCDIR)/templates/template.rb \ + $(PRISM_SRCDIR)/srcs.mk.in + $(BASERUBY) $(tooldir)/generic_erb.rb -c -t$@ -o $(PRISM_SRCDIR)/srcs.mk $(PRISM_SRCDIR)/srcs.mk.in + +realclean-prism-srcs:: + $(RM) $(PRISM_SRCDIR)/srcs.mk + +realclean-srcs-local:: realclean-prism-srcs + +main srcs: $(srcdir)/prism/api_node.c +$(srcdir)/prism/api_node.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/ext/prism/api_node.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) ext/prism/api_node.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/api_node.c + +main incs: $(srcdir)/prism/ast.h +$(srcdir)/prism/ast.h: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/include/prism/ast.h.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) include/prism/ast.h $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/ast.h + +main incs: $(srcdir)/prism/diagnostic.h +$(srcdir)/prism/diagnostic.h: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/include/prism/diagnostic.h.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) include/prism/diagnostic.h $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/diagnostic.h + +main srcs: $(srcdir)/lib/prism/compiler.rb +$(srcdir)/lib/prism/compiler.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/compiler.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/compiler.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/compiler.rb + +main srcs: $(srcdir)/lib/prism/dispatcher.rb +$(srcdir)/lib/prism/dispatcher.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/dispatcher.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/dispatcher.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/dispatcher.rb + +main srcs: $(srcdir)/lib/prism/dot_visitor.rb +$(srcdir)/lib/prism/dot_visitor.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/dot_visitor.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/dot_visitor.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/dot_visitor.rb + +main srcs: $(srcdir)/lib/prism/dsl.rb +$(srcdir)/lib/prism/dsl.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/dsl.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/dsl.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/dsl.rb + +main srcs: $(srcdir)/lib/prism/inspect_visitor.rb +$(srcdir)/lib/prism/inspect_visitor.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/inspect_visitor.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/inspect_visitor.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/inspect_visitor.rb + +main srcs: $(srcdir)/lib/prism/mutation_compiler.rb +$(srcdir)/lib/prism/mutation_compiler.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/mutation_compiler.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/mutation_compiler.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/mutation_compiler.rb + +main srcs: $(srcdir)/lib/prism/node.rb +$(srcdir)/lib/prism/node.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/node.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/node.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/node.rb + +main srcs: $(srcdir)/lib/prism/reflection.rb +$(srcdir)/lib/prism/reflection.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/reflection.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/reflection.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/reflection.rb + +main srcs: $(srcdir)/lib/prism/serialize.rb +$(srcdir)/lib/prism/serialize.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/serialize.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/serialize.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/serialize.rb + +main srcs: $(srcdir)/lib/prism/visitor.rb +$(srcdir)/lib/prism/visitor.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/visitor.rb.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/visitor.rb $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/lib/prism/visitor.rb + +main srcs: $(srcdir)/prism/diagnostic.c +$(srcdir)/prism/diagnostic.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/src/diagnostic.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) src/diagnostic.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/diagnostic.c + +main srcs: $(srcdir)/prism/node.c +$(srcdir)/prism/node.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/src/node.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) src/node.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/node.c + +main srcs: $(srcdir)/prism/prettyprint.c +$(srcdir)/prism/prettyprint.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/src/prettyprint.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) src/prettyprint.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/prettyprint.c + +main srcs: $(srcdir)/prism/serialize.c +$(srcdir)/prism/serialize.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/src/serialize.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) src/serialize.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/serialize.c + +main srcs: $(srcdir)/prism/token_type.c +$(srcdir)/prism/token_type.c: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/src/token_type.c.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) src/token_type.c $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/token_type.c diff --git a/prism/srcs.mk.in b/prism/srcs.mk.in new file mode 100644 index 0000000000..655de155d5 --- /dev/null +++ b/prism/srcs.mk.in @@ -0,0 +1,40 @@ +<% # -*- ruby -*- +require_relative 'templates/template' + +script = File.basename(__FILE__) +srcs = output ? File.basename(output) : script.chomp('.in') +mk = 'uncommon.mk' + +# %> +PRISM_TEMPLATES_DIR = $(PRISM_SRCDIR)/templates +PRISM_TEMPLATE = $(PRISM_TEMPLATES_DIR)/template.rb +PRISM_CONFIG = $(PRISM_SRCDIR)/config.yml + +srcs <%=%><%=mk%>: prism/.srcs.mk.time + +prism/.srcs.mk.time: +prism/$(HAVE_BASERUBY:yes=.srcs.mk.time): \ + $(PRISM_SRCDIR)/templates/template.rb \ + $(PRISM_SRCDIR)/<%=%><%=script%> + $(BASERUBY) $(tooldir)/generic_erb.rb -c -t$@ -o $(PRISM_SRCDIR)/<%=%><%=srcs%> $(PRISM_SRCDIR)/<%=%><%=script%> + +realclean-prism-srcs:: + $(RM) $(PRISM_SRCDIR)/<%=%><%=srcs%> + +realclean-srcs-local:: realclean-prism-srcs +<% Prism::Template::TEMPLATES.map do |t| + /\.(?:[ch]|rb)\z/ =~ t or next + s = '$(srcdir)/' + t.sub(%r[\A(?:(src)|ext|include)/]) {$1 && 'prism/'} + s.sub!(%r[\A\$(srcdir)/prism/], '$(PRISM_SRCDIR)/') + target = s.end_with?('.h') ? 'incs' : 'srcs' +# %> + +main <%=%><%=target%>: <%=%><%=s%> +<%=%><%=s%>: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/<%=%><%=t%>.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) <%=%><%=t%> $@ + +realclean-prism-srcs:: + $(RM) <%=%><%=s%> +<% +end +# %> @@ -716,12 +716,15 @@ rb_func_proc_dup(VALUE src_obj) VALUE proc_obj = TypedData_Make_Struct(rb_obj_class(src_obj), cfunc_proc_t, &proc_data_type, proc); memcpy(&proc->basic, src_proc, sizeof(rb_proc_t)); + RB_OBJ_WRITTEN(proc_obj, Qundef, proc->basic.block.as.captured.self); + RB_OBJ_WRITTEN(proc_obj, Qundef, proc->basic.block.as.captured.code.val); + const VALUE *src_ep = src_proc->block.as.captured.ep; VALUE *ep = *(VALUE **)&proc->basic.block.as.captured.ep = proc->env + VM_ENV_DATA_SIZE - 1; - ep[VM_ENV_DATA_INDEX_FLAGS] = src_proc->block.as.captured.ep[VM_ENV_DATA_INDEX_FLAGS]; - ep[VM_ENV_DATA_INDEX_ME_CREF] = src_proc->block.as.captured.ep[VM_ENV_DATA_INDEX_ME_CREF]; - ep[VM_ENV_DATA_INDEX_SPECVAL] = src_proc->block.as.captured.ep[VM_ENV_DATA_INDEX_SPECVAL]; - ep[VM_ENV_DATA_INDEX_ENV] = src_proc->block.as.captured.ep[VM_ENV_DATA_INDEX_ENV]; + ep[VM_ENV_DATA_INDEX_FLAGS] = src_ep[VM_ENV_DATA_INDEX_FLAGS]; + ep[VM_ENV_DATA_INDEX_ME_CREF] = src_ep[VM_ENV_DATA_INDEX_ME_CREF]; + ep[VM_ENV_DATA_INDEX_SPECVAL] = src_ep[VM_ENV_DATA_INDEX_SPECVAL]; + RB_OBJ_WRITE(proc_obj, &ep[VM_ENV_DATA_INDEX_ENV], src_ep[VM_ENV_DATA_INDEX_ENV]); return proc_obj; } @@ -4009,12 +4012,13 @@ proc_ruby2_keywords(VALUE procval) switch (proc->block.type) { case block_type_iseq: if (ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_rest && + !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_post && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kw && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kwrest) { ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.ruby2_keywords = 1; } else { - rb_warn("Skipping set of ruby2_keywords flag for proc (proc accepts keywords or proc does not accept argument splat)"); + rb_warn("Skipping set of ruby2_keywords flag for proc (proc accepts keywords or post arguments or proc does not accept argument splat)"); } break; default: @@ -585,14 +585,6 @@ rb_ractor_main_p_(void) return rb_ec_ractor_ptr(ec) == rb_ec_vm_ptr(ec)->ractor.main_ractor; } -bool -rb_obj_is_main_ractor(VALUE gv) -{ - if (!rb_ractor_p(gv)) return false; - rb_ractor_t *r = DATA_PTR(gv); - return r == GET_VM()->ractor.main_ractor; -} - int rb_ractor_living_thread_num(const rb_ractor_t *r) { @@ -142,18 +142,21 @@ static const rb_random_interface_t random_mt_if = { }; static rb_random_mt_t * -rand_mt_start(rb_random_mt_t *r) +rand_mt_start(rb_random_mt_t *r, VALUE obj) { if (!genrand_initialized(&r->mt)) { r->base.seed = rand_init(&random_mt_if, &r->base, random_seed(Qundef)); + if (obj) { + RB_OBJ_WRITTEN(obj, Qundef, r->base.seed); + } } return r; } static rb_random_t * -rand_start(rb_random_mt_t *r) +rand_start(rb_random_mt_t *r, VALUE obj) { - return &rand_mt_start(r)->base; + return &rand_mt_start(r, obj)->base; } static rb_ractor_local_key_t default_rand_key; @@ -192,7 +195,13 @@ default_rand(void) static rb_random_mt_t * default_mt(void) { - return rand_mt_start(default_rand()); + return rand_mt_start(default_rand(), Qfalse); +} + +static rb_random_t * +default_rand_start(void) +{ + return &default_mt()->base; } unsigned int @@ -293,7 +302,7 @@ get_rnd(VALUE obj) rb_random_t *ptr; TypedData_Get_Struct(obj, rb_random_t, &rb_random_data_type, ptr); if (RTYPEDDATA_TYPE(obj) == &random_mt_type) - return rand_start((rb_random_mt_t *)ptr); + return rand_start((rb_random_mt_t *)ptr, obj); return ptr; } @@ -309,11 +318,11 @@ static rb_random_t * try_get_rnd(VALUE obj) { if (obj == rb_cRandom) { - return rand_start(default_rand()); + return default_rand_start(); } if (!rb_typeddata_is_kind_of(obj, &rb_random_data_type)) return NULL; if (RTYPEDDATA_TYPE(obj) == &random_mt_type) - return rand_start(DATA_PTR(obj)); + return rand_start(DATA_PTR(obj), obj); rb_random_t *rnd = DATA_PTR(obj); if (!rnd) { rb_raise(rb_eArgError, "uninitialized random: %s", @@ -829,6 +838,7 @@ rand_mt_copy(VALUE obj, VALUE orig) mt = &rnd1->mt; *rnd1 = *rnd2; + RB_OBJ_WRITTEN(obj, Qundef, rnd1->base.seed); mt->next = mt->state + numberof(mt->state) - mt->left + 1; return obj; } @@ -916,7 +926,7 @@ rand_mt_load(VALUE obj, VALUE dump) } mt->left = (unsigned int)x; mt->next = mt->state + numberof(mt->state) - x + 1; - rnd->base.seed = rb_to_int(seed); + RB_OBJ_WRITE(obj, &rnd->base.seed, rb_to_int(seed)); return obj; } @@ -975,7 +985,7 @@ static VALUE rb_f_srand(int argc, VALUE *argv, VALUE obj) { VALUE seed, old; - rb_random_mt_t *r = rand_mt_start(default_rand()); + rb_random_mt_t *r = default_mt(); if (rb_check_arity(argc, 0, 1) == 0) { seed = random_seed(obj); @@ -1337,7 +1347,7 @@ rb_random_bytes(VALUE obj, long n) static VALUE random_s_bytes(VALUE obj, VALUE len) { - rb_random_t *rnd = rand_start(default_rand()); + rb_random_t *rnd = default_rand_start(); return rand_bytes(&random_mt_if, rnd, NUM2LONG(rb_to_int(len))); } @@ -1359,7 +1369,7 @@ random_s_bytes(VALUE obj, VALUE len) static VALUE random_s_seed(VALUE obj) { - rb_random_mt_t *rnd = rand_mt_start(default_rand()); + rb_random_mt_t *rnd = default_mt(); return rnd->base.seed; } @@ -1689,7 +1699,7 @@ static VALUE rb_f_rand(int argc, VALUE *argv, VALUE obj) { VALUE vmax; - rb_random_t *rnd = rand_start(default_rand()); + rb_random_t *rnd = default_rand_start(); if (rb_check_arity(argc, 0, 1) && !NIL_P(vmax = argv[0])) { VALUE v = rand_range(obj, rnd, vmax); @@ -1716,7 +1726,7 @@ rb_f_rand(int argc, VALUE *argv, VALUE obj) static VALUE random_s_rand(int argc, VALUE *argv, VALUE obj) { - VALUE v = rand_random(argc, argv, Qnil, rand_start(default_rand())); + VALUE v = rand_random(argc, argv, Qnil, default_rand_start()); check_random_number(v, argv); return v; } @@ -47,6 +47,7 @@ static VALUE r_cover_p(VALUE, VALUE, VALUE, VALUE); static void range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end) { + // Changing this condition has implications for JITs. If you do, please let maintainers know. if ((!FIXNUM_P(beg) || !FIXNUM_P(end)) && !NIL_P(beg) && !NIL_P(end)) { VALUE v; @@ -290,11 +290,6 @@ rb_memsearch(const void *x0, long m, const void *y0, long n, rb_encoding *enc) #define KCODE_FIXED FL_USER4 -#define ARG_REG_OPTION_MASK \ - (ONIG_OPTION_IGNORECASE|ONIG_OPTION_MULTILINE|ONIG_OPTION_EXTEND) -#define ARG_ENCODING_FIXED 16 -#define ARG_ENCODING_NONE 32 - static int char_to_option(int c) { @@ -369,7 +369,7 @@ RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj) { if (RB_SPECIAL_CONST_P(obj)) { - return SPECIAL_CONST_SHAPE_ID; + rb_bug("rb_obj_shape_id: called on a special constant"); } if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) { @@ -1425,6 +1425,9 @@ rb_shape_parent(VALUE self) static VALUE rb_shape_debug_shape(VALUE self, VALUE obj) { + if (RB_SPECIAL_CONST_P(obj)) { + rb_raise(rb_eArgError, "Can't get shape of special constant"); + } return shape_id_t_to_rb_cShape(rb_obj_shape_id(obj)); } @@ -1622,7 +1625,6 @@ Init_shape(void) rb_define_const(rb_cShape, "SHAPE_IVAR", INT2NUM(SHAPE_IVAR)); rb_define_const(rb_cShape, "SHAPE_ID_NUM_BITS", INT2NUM(SHAPE_ID_NUM_BITS)); rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT)); - rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID)); rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS)); rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t))); rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t))); @@ -72,7 +72,6 @@ typedef uint32_t redblack_id_t; #define ROOT_SHAPE_WITH_OBJ_ID 0x1 #define ROOT_TOO_COMPLEX_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_TOO_COMPLEX) #define ROOT_TOO_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_TOO_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID) -#define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_FROZEN) typedef struct redblack_node redblack_node_t; diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb index a9afad4aee..73b4ba62fa 100644 --- a/spec/ruby/core/module/ruby2_keywords_spec.rb +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -213,7 +213,7 @@ describe "Module#ruby2_keywords" do it "prints warning when a method accepts keywords" do obj = Object.new - def obj.foo(a:, b:) end + def obj.foo(*a, b:) end -> { obj.singleton_class.class_exec do @@ -224,7 +224,7 @@ describe "Module#ruby2_keywords" do it "prints warning when a method accepts keyword splat" do obj = Object.new - def obj.foo(**a) end + def obj.foo(*a, **b) end -> { obj.singleton_class.class_exec do @@ -232,4 +232,17 @@ describe "Module#ruby2_keywords" do end }.should complain(/Skipping set of ruby2_keywords flag for/) end + + ruby_version_is "3.5" do + it "prints warning when a method accepts post arguments" do + obj = Object.new + def obj.foo(*a, b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end end diff --git a/spec/ruby/core/proc/ruby2_keywords_spec.rb b/spec/ruby/core/proc/ruby2_keywords_spec.rb index ab67302231..030eeeeb68 100644 --- a/spec/ruby/core/proc/ruby2_keywords_spec.rb +++ b/spec/ruby/core/proc/ruby2_keywords_spec.rb @@ -39,7 +39,7 @@ describe "Proc#ruby2_keywords" do end it "prints warning when a proc accepts keywords" do - f = -> a:, b: { } + f = -> *a, b: { } -> { f.ruby2_keywords @@ -47,10 +47,20 @@ describe "Proc#ruby2_keywords" do end it "prints warning when a proc accepts keyword splat" do - f = -> **a { } + f = -> *a, **b { } -> { f.ruby2_keywords }.should complain(/Skipping set of ruby2_keywords flag for/) end + + ruby_version_is "3.5" do + it "prints warning when a proc accepts post arguments" do + f = -> *a, b { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end end @@ -4133,10 +4133,8 @@ rb_str_hash_cmp(VALUE str1, VALUE str2) * call-seq: * hash -> integer * - * Returns the integer hash value for +self+. - * The value is based on the length, content and encoding of +self+. + * :include: doc/string/hash.rdoc * - * Related: Object#hash. */ static VALUE diff --git a/template/GNUmakefile.in b/template/GNUmakefile.in index 22ff1078dc..452e7cdeef 100644 --- a/template/GNUmakefile.in +++ b/template/GNUmakefile.in @@ -27,5 +27,8 @@ override UNICODE_TABLES_DEPENDENTS = \ $(UNICODE_TABLES_DATA_FILES)))),\ force,none) +# extract NMake-style include list +$(eval common_mk_includes := $(shell sed -n 's/^!include *//p' $(srcdir)/common.mk)) + -include uncommon.mk include $(srcdir)/defs/gmake.mk diff --git a/template/Makefile.in b/template/Makefile.in index daecd1debe..39f702b66d 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -426,8 +426,8 @@ $(MKFILES): config.status $(srcdir)/version.h $(ABI_VERSION_HDR) $(MAKE) -f conftest.mk | grep '^AUTO_REMAKE$$' >/dev/null 2>&1 || \ { echo "$@ updated, restart."; exit 1; } -uncommon.mk: $(srcdir)/common.mk $(srcdir)/depend - sed -f $(srcdir)/tool/prereq.status $(srcdir)/common.mk $(srcdir)/depend > $@ +uncommon.mk: $(srcdir)/tool/prereq.status + sed -f $(srcdir)/tool/prereq.status $(srcdir)/common.mk $(common_mk_includes) > $@ .PHONY: reconfig reconfig-args = $(srcdir)/$(CONFIGURE) $(yes_silence:yes=--silent) $(configure_args) diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index dbf041a732..576a5f6064 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -880,21 +880,20 @@ class TestHash < Test::Unit::TestCase assert_equal(quote1, eval(quote1).inspect) assert_equal(quote2, eval(quote2).inspect) assert_equal(quote3, eval(quote3).inspect) - begin - verbose_bak, $VERBOSE = $VERBOSE, nil - enc = Encoding.default_external - Encoding.default_external = Encoding::ASCII + + EnvUtil.with_default_external(Encoding::ASCII) do utf8_ascii_hash = '{"\\u3042": 1}' assert_equal(eval(utf8_ascii_hash).inspect, utf8_ascii_hash) - Encoding.default_external = Encoding::UTF_8 + end + + EnvUtil.with_default_external(Encoding::UTF_8) do utf8_hash = "{\u3042: 1}" assert_equal(eval(utf8_hash).inspect, utf8_hash) - Encoding.default_external = Encoding::Windows_31J + end + + EnvUtil.with_default_external(Encoding::Windows_31J) do sjis_hash = "{\x87]: 1}".force_encoding('sjis') assert_equal(eval(sjis_hash).inspect, sjis_hash) - ensure - Encoding.default_external = enc - $VERBOSE = verbose_bak end end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 1e3e0e53b1..c836abd0c6 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -2424,6 +2424,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { m.call(42, a: 1, **h2) } end + def test_ruby2_keywords_post_arg + def self.a(*c, **kw) [c, kw] end + def self.b(*a, b) a(*a, b) end + assert_warn(/Skipping set of ruby2_keywords flag for b \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(singleton_class.send(:ruby2_keywords, :b)) + end + assert_equal([[{foo: 1}, {bar: 1}], {}], b({foo: 1}, bar: 1)) + + b = ->(*a, b){a(*a, b)} + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do + b.ruby2_keywords + end + assert_equal([[{foo: 1}, {bar: 1}], {}], b.({foo: 1}, bar: 1)) + end + def test_proc_ruby2_keywords h1 = {:a=>1} foo = ->(*args, &block){block.call(*args)} @@ -2436,8 +2451,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) } assert_equal(h1, foo.call(:a=>1, &->(arg){arg})) - [->(){}, ->(arg){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr| - assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or proc does not accept argument splat\)/) do + [->(){}, ->(arg){}, ->(*args, x){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr| + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do pr.ruby2_keywords end end @@ -2790,10 +2805,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(:opt, o.clear_last_opt(a: 1)) assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) } - assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or post arguments or method does not accept argument splat\)/) do assert_nil(c.send(:ruby2_keywords, :bar)) end + c.class_eval do + def bar_post(*a, x) = nil + define_method(:bar_post_bmethod) { |*a, x| } + end + assert_warn(/Skipping set of ruby2_keywords flag for bar_post \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(c.send(:ruby2_keywords, :bar_post)) + end + assert_warn(/Skipping set of ruby2_keywords flag for bar_post_bmethod \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(c.send(:ruby2_keywords, :bar_post_bmethod)) + end + utf16_sym = "abcdef".encode("UTF-16LE").to_sym c.send(:define_method, utf16_sym, c.instance_method(:itself)) assert_warn(/abcdef/) do diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index b0e2e9f849..9f7a3c7f4b 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -186,33 +186,35 @@ class TestM17N < Test::Unit::TestCase end def test_string_inspect_encoding - EnvUtil.suppress_warning do - begin - orig_int = Encoding.default_internal - orig_ext = Encoding.default_external - Encoding.default_internal = nil - [Encoding::UTF_8, Encoding::EUC_JP, Encoding::Windows_31J, Encoding::GB18030]. - each do |e| - Encoding.default_external = e - str = "\x81\x30\x81\x30".force_encoding('GB18030') - assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect) - str = e("\xa1\x8f\xa1\xa1") - expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP") - assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect) - str = s("\x81@") - assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect) - str = "\u3042\u{10FFFD}" - assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect) - end - Encoding.default_external = Encoding::UTF_8 - [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE, - Encoding::UTF8_SOFTBANK].each do |e| - str = "abc".encode(e) - assert_equal('"abc"', str.inspect) - end - ensure - Encoding.default_internal = orig_int - Encoding.default_external = orig_ext + [ + Encoding::UTF_8, + Encoding::EUC_JP, + Encoding::Windows_31J, + Encoding::GB18030, + ].each do |e| + EnvUtil.with_default_external(e) do + str = "\x81\x30\x81\x30".force_encoding('GB18030') + assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect) + str = e("\xa1\x8f\xa1\xa1") + expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP") + assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect) + str = s("\x81@") + assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect) + str = "\u3042\u{10FFFD}" + assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect) + end + end + + EnvUtil.with_default_external(Encoding::UTF_8) do + [ + Encoding::UTF_16BE, + Encoding::UTF_16LE, + Encoding::UTF_32BE, + Encoding::UTF_32LE, + Encoding::UTF8_SOFTBANK + ].each do |e| + str = "abc".encode(e) + assert_equal('"abc"', str.inspect) end end end @@ -246,59 +248,43 @@ class TestM17N < Test::Unit::TestCase end def test_object_utf16_32_inspect - EnvUtil.suppress_warning do - begin - orig_int = Encoding.default_internal - orig_ext = Encoding.default_external - Encoding.default_internal = nil - Encoding.default_external = Encoding::UTF_8 - o = Object.new - [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e| - o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end" - assert_equal '[abc]', [o].inspect - end - ensure - Encoding.default_internal = orig_int - Encoding.default_external = orig_ext + EnvUtil.with_default_external(Encoding::UTF_8) do + o = Object.new + [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e| + o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end" + assert_equal '[abc]', [o].inspect end end end def test_object_inspect_external - orig_v, $VERBOSE = $VERBOSE, false - orig_int, Encoding.default_internal = Encoding.default_internal, nil - orig_ext = Encoding.default_external - omit "https://bugs.ruby-lang.org/issues/18338" o = Object.new - Encoding.default_external = Encoding::UTF_16BE - def o.inspect - "abc" - end - assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect } + EnvUtil.with_default_external(Encoding::UTF_16BE) do + def o.inspect + "abc" + end + assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect } - def o.inspect - "abc".encode(Encoding.default_external) + def o.inspect + "abc".encode(Encoding.default_external) + end + assert_equal '[abc]', [o].inspect end - assert_equal '[abc]', [o].inspect - - Encoding.default_external = Encoding::US_ASCII - def o.inspect - "\u3042" - end - assert_equal '[\u3042]', [o].inspect + EnvUtil.with_default_external(Encoding::US_ASCII) do + def o.inspect + "\u3042" + end + assert_equal '[\u3042]', [o].inspect - def o.inspect - "\x82\xa0".force_encoding(Encoding::Windows_31J) + def o.inspect + "\x82\xa0".force_encoding(Encoding::Windows_31J) + end + assert_equal '[\x{82A0}]', [o].inspect end - assert_equal '[\x{82A0}]', [o].inspect - ensure - Encoding.default_internal = orig_int - Encoding.default_external = orig_ext - $VERBOSE = orig_v end def test_str_dump diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb index 6e67099c6b..a676bb5cd9 100644 --- a/test/ruby/test_math.rb +++ b/test/ruby/test_math.rb @@ -147,6 +147,13 @@ class TestMath < Test::Unit::TestCase check(Math::E ** 2, Math.exp(2)) end + def test_expm1 + check(0, Math.expm1(0)) + check(Math.sqrt(Math::E) - 1, Math.expm1(0.5)) + check(Math::E - 1, Math.expm1(1)) + check(Math::E ** 2 - 1, Math.expm1(2)) + end + def test_log check(0, Math.log(1)) check(1, Math.log(Math::E)) @@ -201,6 +208,19 @@ class TestMath < Test::Unit::TestCase assert_nothing_raised { assert_infinity(-Math.log10(0)) } end + def test_log1p + check(0, Math.log1p(0)) + check(1, Math.log1p(Math::E - 1)) + check(Math.log(2.0 ** 64 + 1), Math.log1p(1 << 64)) + check(Math.log(2) * 1024.0, Math.log1p(2 ** 1024)) + assert_nothing_raised { assert_infinity(Math.log1p(1.0/0)) } + assert_nothing_raised { assert_infinity(-Math.log1p(-1.0)) } + assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-1.1) } + assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-Float::EPSILON-1) } + assert_nothing_raised { assert_nan(Math.log1p(Float::NAN)) } + assert_nothing_raised { assert_infinity(-Math.log1p(-1)) } + end + def test_sqrt check(0, Math.sqrt(0)) check(1, Math.sqrt(1)) diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 2cfd56d267..c1f33798ba 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -202,6 +202,13 @@ class TestRactor < Test::Unit::TestCase RUBY end + # [Bug #20146] + def test_max_cpu_1 + assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }]) + assert_equal :ok, Ractor.new { :ok }.value + RUBY + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 77bba6421b..08a841d254 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1032,16 +1032,29 @@ class TestShapes < Test::Unit::TestCase assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([])) end - def test_true_has_special_const_shape_id - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id) - end - - def test_nil_has_special_const_shape_id - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id) + def test_raise_on_special_consts + assert_raise ArgumentError do + RubyVM::Shape.of(true) + end + assert_raise ArgumentError do + RubyVM::Shape.of(false) + end + assert_raise ArgumentError do + RubyVM::Shape.of(nil) + end + assert_raise ArgumentError do + RubyVM::Shape.of(0) + end + # 32-bit platforms don't have flonums or static symbols as special + # constants + # TODO(max): Add ArgumentError tests for symbol and flonum, skipping if + # RUBY_PLATFORM =~ /i686/ end - def test_root_shape_transition_to_special_const_on_frozen - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of([].freeze).id) + def test_root_shape_frozen + frozen_root_shape = RubyVM::Shape.of([].freeze) + assert_predicate(frozen_root_shape, :frozen?) + assert_equal(RubyVM::Shape.root_shape.id, frozen_root_shape.raw_id) end def test_basic_shape_transition diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index c7e4b0c1ec..1e0f31ba7c 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3251,18 +3251,12 @@ CODE assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect) assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081) end - begin - verbose, $VERBOSE = $VERBOSE, nil - ext = Encoding.default_external - Encoding.default_external = "us-ascii" - $VERBOSE = verbose + + EnvUtil.with_default_external(Encoding::US_ASCII) do i = S("abc\"\\".force_encoding("utf-8")).inspect - ensure - $VERBOSE = nil - Encoding.default_external = ext - $VERBOSE = verbose + + assert_equal('"abc\\"\\\\"', i, bug4081) end - assert_equal('"abc\\"\\\\"', i, bug4081) end def test_dummy_inspect diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index cc784e7644..3504b9b7dc 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -447,6 +447,19 @@ class TestVariable < Test::Unit::TestCase assert_equal(%i[α b], b.local_variables) end + def test_genivar_cache + bug21547 = '[Bug #21547]' + klass = Class.new(Array) + instance = klass.new + instance.instance_variable_set(:@a1, 1) + instance.instance_variable_set(:@a2, 2) + Fiber.new do + instance.instance_variable_set(:@a3, 3) + instance.instance_variable_set(:@a4, 4) + end.resume + assert_equal 4, instance.instance_variable_get(:@a4) + end + private def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:) local_variables diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 96ac99b6db..879aaf3225 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -220,6 +220,22 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end + def test_send_exit_with_uninitialized_locals + assert_runs 'nil', %q{ + def entry(init) + function_stub_exit(init) + end + + def function_stub_exit(init) + uninitialized_local = 1 if init + uninitialized_local + end + + entry(true) # profile and set 1 to the local slot + entry(false) + }, call_threshold: 2, allowed_iseqs: 'entry@-e:2' + end + def test_invokebuiltin omit 'Test fails at the moment due to not handling optional parameters' assert_compiles '["."]', %q{ @@ -1847,6 +1863,29 @@ class TestZJIT < Test::Unit::TestCase }, insns: [:concatstrings] end + def test_regexp_interpolation + assert_compiles '/123/', %q{ + def test = /#{1}#{2}#{3}/ + + test + }, insns: [:toregexp] + end + + def test_new_range_non_leaf + assert_compiles '(0/1)..1', %q{ + def jit_entry(v) = make_range_then_exit(v) + + def make_range_then_exit(v) + range = (v..1) + super rescue range # TODO(alan): replace super with side-exit intrinsic + end + + jit_entry(0) # profile + jit_entry(0) # compile + jit_entry(0/1r) # run without stub + }, call_threshold: 2 + end + private # Assert that every method call in `test_script` can be compiled by ZJIT @@ -1904,6 +1943,7 @@ class TestZJIT < Test::Unit::TestCase zjit: true, stats: false, debug: true, + allowed_iseqs: nil, timeout: 1000, pipe_fd: ) @@ -1913,6 +1953,12 @@ class TestZJIT < Test::Unit::TestCase args << "--zjit-num-profiles=#{num_profiles}" args << "--zjit-stats" if stats args << "--zjit-debug" if debug + if allowed_iseqs + jitlist = Tempfile.new("jitlist") + jitlist.write(allowed_iseqs) + jitlist.close + args << "--zjit-allowed-iseqs=#{jitlist.path}" + end end args << "-e" << script_shell_encode(script) pipe_r, pipe_w = IO.pipe @@ -1932,6 +1978,7 @@ class TestZJIT < Test::Unit::TestCase pipe_reader&.join(timeout) pipe_r&.close pipe_w&.close + jitlist&.unlink end def script_shell_encode(s) diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 4a671cf3a1..0598b8d295 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -397,11 +397,15 @@ native_thread_check_and_create_shared(rb_vm_t *vm) rb_native_mutex_lock(&vm->ractor.sched.lock); { - unsigned int snt_cnt = vm->ractor.sched.snt_cnt; - if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads) snt_cnt++; // do not need snt for main ractor + unsigned int schedulable_ractor_cnt = vm->ractor.cnt; + RUBY_ASSERT(schedulable_ractor_cnt >= 1); + + if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads) + schedulable_ractor_cnt--; // do not need snt for main ractor + unsigned int snt_cnt = vm->ractor.sched.snt_cnt; if (((int)snt_cnt < MINIMUM_SNT) || - (snt_cnt < vm->ractor.cnt && + (snt_cnt < schedulable_ractor_cnt && snt_cnt < vm->ractor.sched.max_cpu)) { RUBY_DEBUG_LOG("added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u", diff --git a/tool/make-snapshot b/tool/make-snapshot index 7d4fce4f15..2b9a5006e0 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -480,7 +480,14 @@ def package(vcs, rev, destdir, tmp = nil) vars["UNICODE_VERSION"] = $unicode_version if $unicode_version args = vars.dup mk.gsub!(/@([A-Za-z_]\w*)@/) {args.delete($1); vars[$1] || ENV[$1]} - mk << commonmk.gsub(/\{\$([^(){}]*)[^{}]*\}/, "").gsub(/^!/, '-').sub(/^revision\.tmp::$/, '\& Makefile') + commonmk.gsub!(/^!(?:include \$\(srcdir\)\/(.*))?/) do + if inc = $1 and File.exist?(inc) + File.binread(inc).gsub(/^!/, '# !') + else + "#" + end + end + mk << commonmk.gsub(/\{\$([^(){}]*)[^{}]*\}/, "").sub(/^revision\.tmp::$/, '\& Makefile') mk << <<-'APPEND' update-download:: touch-unicode-files diff --git a/tool/prereq.status b/tool/prereq.status index da92460c8d..6aca615e90 100644 --- a/tool/prereq.status +++ b/tool/prereq.status @@ -42,4 +42,4 @@ s,@srcdir@,.,g s/@[A-Za-z][A-Za-z0-9_]*@//g s/{\$([^(){}]*)}//g -s/^!/-/ +s/^!/#!/ diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 029a27c829..cb4e0af50b 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -330,7 +330,7 @@ module SyncDefaultGems rm_rf("test/prism/snapshots") rm("prism/extconf.rb") - `git checkout prism/generate-srcs.mk.rb` + `git checkout prism/srcs.mk*` when "resolv" rm_rf(%w[lib/resolv.* ext/win32/resolv test/resolv ext/win32/lib/win32/resolv.rb]) cp_r("#{upstream}/lib/resolv.rb", "lib") diff --git a/variable.c b/variable.c index 4f0f83d203..1cd1c604c3 100644 --- a/variable.c +++ b/variable.c @@ -1247,7 +1247,7 @@ rb_obj_fields(VALUE obj, ID field_name) generic_fields: { rb_execution_context_t *ec = GET_EC(); - if (ec->gen_fields_cache.obj == obj) { + if (ec->gen_fields_cache.obj == obj && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { fields_obj = ec->gen_fields_cache.fields_obj; } else { diff --git a/vcpkg.json b/vcpkg.json index 16415dece1..efd356e814 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,5 +7,5 @@ "openssl", "zlib" ], - "builtin-baseline": "65be7019941e1401e02daaba0738cab2c8a4a355" -} + "builtin-baseline": "dd3097e305afa53f7b4312371f62058d2e665320" +}
\ No newline at end of file diff --git a/vm_callinfo.h b/vm_callinfo.h index 79ccbfa7ab..e52b2f9b1a 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -613,7 +613,7 @@ static inline bool vm_cc_check_cme(const struct rb_callcache *cc, const rb_callable_method_entry_t *cme) { bool valid; - RB_VM_LOCKING() { + 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)); } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 3aca1bc24f..e186c57745 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2324,14 +2324,15 @@ vm_search_method_fastpath(VALUE cd_owner, struct rb_call_data *cd, VALUE klass) return vm_search_method_slowpath0(cd_owner, cd, klass); } -static const struct rb_callcache * +static const struct rb_callable_method_entry_struct * vm_search_method(VALUE cd_owner, struct rb_call_data *cd, VALUE recv) { VALUE klass = CLASS_OF(recv); VM_ASSERT(klass != Qfalse); VM_ASSERT(RBASIC_CLASS(klass) == 0 || rb_obj_is_kind_of(klass, rb_cClass)); - return vm_search_method_fastpath(cd_owner, cd, klass); + const struct rb_callcache *cc = vm_search_method_fastpath(cd_owner, cd, klass); + return vm_cc_cme(cc); } #if __has_attribute(transparent_union) @@ -2394,8 +2395,8 @@ static inline int vm_method_cfunc_is(const rb_iseq_t *iseq, CALL_DATA cd, VALUE recv, cfunc_type func) { VM_ASSERT(iseq != NULL); - const struct rb_callcache *cc = vm_search_method((VALUE)iseq, cd, recv); - return check_cfunc(vm_cc_cme(cc), func); + const struct rb_callable_method_entry_struct *cme = vm_search_method((VALUE)iseq, cd, recv); + return check_cfunc(cme, func); } #define check_cfunc(me, func) check_cfunc(me, make_cfunc_type(func)) @@ -6161,11 +6162,11 @@ vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd) return recv; } - const struct rb_callcache *cc = vm_search_method((VALUE)iseq, cd, recv); + const struct rb_callable_method_entry_struct *cme = vm_search_method((VALUE)iseq, cd, recv); switch (type) { case T_SYMBOL: - if (check_method_basic_definition(vm_cc_cme(cc))) { + if (check_method_basic_definition(cme)) { // rb_sym_to_s() allocates a mutable string, but since we are only // going to use this string for interpolation, it's fine to use the // frozen string. @@ -6174,7 +6175,7 @@ vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd) break; case T_MODULE: case T_CLASS: - if (check_cfunc(vm_cc_cme(cc), rb_mod_to_s)) { + if (check_cfunc(cme, rb_mod_to_s)) { // rb_mod_to_s() allocates a mutable string, but since we are only // going to use this string for interpolation, it's fine to use the // frozen string. @@ -6186,22 +6187,22 @@ vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd) } break; case T_NIL: - if (check_cfunc(vm_cc_cme(cc), rb_nil_to_s)) { + if (check_cfunc(cme, rb_nil_to_s)) { return rb_nil_to_s(recv); } break; case T_TRUE: - if (check_cfunc(vm_cc_cme(cc), rb_true_to_s)) { + if (check_cfunc(cme, rb_true_to_s)) { return rb_true_to_s(recv); } break; case T_FALSE: - if (check_cfunc(vm_cc_cme(cc), rb_false_to_s)) { + if (check_cfunc(cme, rb_false_to_s)) { return rb_false_to_s(recv); } break; case T_FIXNUM: - if (check_cfunc(vm_cc_cme(cc), rb_int_to_s)) { + if (check_cfunc(cme, rb_int_to_s)) { return rb_fix_to_s(recv); } break; diff --git a/vm_method.c b/vm_method.c index c1793c102c..03fb79cddd 100644 --- a/vm_method.c +++ b/vm_method.c @@ -428,6 +428,8 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) if (rb_objspace_garbage_object_p(klass)) return; RB_VM_LOCKING() { + rb_vm_barrier(); + if (LIKELY(RCLASS_SUBCLASSES_FIRST(klass) == NULL)) { // no subclasses // check only current class @@ -510,7 +512,7 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) } rb_gccct_clear_table(Qnil); -} + } } static void @@ -1752,6 +1754,8 @@ cached_callable_method_entry(VALUE klass, ID mid) return ccs->cme; } else { + rb_vm_barrier(); + rb_managed_id_table_delete(cc_tbl, mid); rb_vm_ccs_invalidate_and_free(ccs); } @@ -1782,7 +1786,14 @@ cache_callable_method_entry(VALUE klass, ID mid, const rb_callable_method_entry_ #endif } else { - vm_ccs_create(klass, cc_tbl, mid, cme); + if (rb_multi_ractor_p()) { + VALUE new_cc_tbl = rb_vm_cc_table_dup(cc_tbl); + vm_ccs_create(klass, new_cc_tbl, mid, cme); + RB_OBJ_ATOMIC_WRITE(klass, &RCLASSEXT_CC_TBL(RCLASS_EXT_WRITABLE(klass)), new_cc_tbl); + } + else { + vm_ccs_create(klass, cc_tbl, mid, cme); + } } } @@ -2948,13 +2959,14 @@ rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module) switch (me->def->type) { case VM_METHOD_TYPE_ISEQ: if (ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_rest && + !ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_post && !ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_kw && !ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_kwrest) { ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.ruby2_keywords = 1; rb_clear_method_cache(module, name); } else { - rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or method does not accept argument splat)", QUOTE_ID(name)); + rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or post arguments or method does not accept argument splat)", QUOTE_ID(name)); } break; case VM_METHOD_TYPE_BMETHOD: { @@ -2967,13 +2979,14 @@ rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module) const struct rb_captured_block *captured = VM_BH_TO_ISEQ_BLOCK(procval); const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq); if (ISEQ_BODY(iseq)->param.flags.has_rest && + !ISEQ_BODY(iseq)->param.flags.has_post && !ISEQ_BODY(iseq)->param.flags.has_kw && !ISEQ_BODY(iseq)->param.flags.has_kwrest) { ISEQ_BODY(iseq)->param.flags.ruby2_keywords = 1; rb_clear_method_cache(module, name); } else { - rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or method does not accept argument splat)", QUOTE_ID(name)); + rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or post arguments or method does not accept argument splat)", QUOTE_ID(name)); } break; } diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 664d54e5ff..1bdef106b3 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -570,7 +570,6 @@ ACTIONS_ENDGROUP = @:: ABI_VERSION_HDR = $(hdrdir)/ruby/internal/abi.h !include $(srcdir)/common.mk -!include $(srcdir)/depend !ifdef SCRIPTPROGRAMS !else if [echo>scriptbin.mk SCRIPTPROGRAMS = \] diff --git a/win32/setup.mak b/win32/setup.mak index 6af70d5830..275ccda3bb 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -238,16 +238,6 @@ MACHINE = x86 @echo # ENCODING>>$(MAKEFILE) @$(MAKE) -l -f $(srcdir)/win32/enc-setup.mak srcdir="$(srcdir)" MAKEFILE=$(MAKEFILE) -!ifdef BASERUBY -ruby = $(BASERUBY) -!else ifndef ruby -ruby = ruby -!endif -$(srcdir)/prism/srcs.mk: - $(ruby:/=\) $(srcdir)/prism/generate-srcs.mk.rb > $@ - --epilogue-: $(srcdir)/prism/srcs.mk - -epilogue-: nul @type << >>$(MAKEFILE) diff --git a/yjit/not_gmake.mk b/yjit/not_gmake.mk index 3a2ca9281f..0d95d8ddf1 100644 --- a/yjit/not_gmake.mk +++ b/yjit/not_gmake.mk @@ -12,21 +12,7 @@ yjit-static-lib: $(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) # Assume GNU flavor LD and OBJCOPY. Works on FreeBSD 13, at least. -$(YJIT_LIBOBJ): $(YJIT_LIBS) +$(RUST_LIBOBJ): $(YJIT_LIBS) $(ECHO) 'partial linking $(YJIT_LIBS) into $@' $(Q) $(LD) -r -o $@ --whole-archive $(YJIT_LIBS) -$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@) - -.PHONY: zjit-static-lib -$(ZJIT_LIBS): zjit-static-lib - $(empty) - -zjit-static-lib: - $(ECHO) 'building Rust ZJIT (release mode)' - $(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) - -# Assume GNU flavor LD and OBJCOPY. Works on FreeBSD 13, at least. -$(ZJIT_LIBOBJ): $(ZJIT_LIBS) - $(ECHO) 'partial linking $(ZJIT_LIBS) into $@' - $(Q) $(LD) -r -o $@ --whole-archive $(ZJIT_LIBS) - -$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 9644b948d7..9c06010527 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -3104,7 +3104,7 @@ fn gen_set_ivar( // Get the iv index let shape_too_complex = comptime_receiver.shape_too_complex(); - let ivar_index = if !shape_too_complex { + let ivar_index = if !comptime_receiver.special_const_p() && !shape_too_complex { let shape_id = comptime_receiver.shape_id_of(); let mut ivar_index: u16 = 0; if unsafe { rb_shape_get_iv_index(shape_id, ivar_name, &mut ivar_index) } { @@ -3369,7 +3369,7 @@ fn gen_definedivar( // Specialize base on compile time values let comptime_receiver = jit.peek_at_self(); - if comptime_receiver.shape_too_complex() || asm.ctx.get_chain_depth() >= GET_IVAR_MAX_DEPTH { + if comptime_receiver.special_const_p() || comptime_receiver.shape_too_complex() || asm.ctx.get_chain_depth() >= GET_IVAR_MAX_DEPTH { // Fall back to calling rb_ivar_defined // Save the PC and SP because the callee may allocate @@ -4315,11 +4315,11 @@ fn gen_opt_ary_freeze( return None; } - let str = jit.get_arg(0); + let ary = jit.get_arg(0); // Push the return value onto the stack let stack_ret = asm.stack_push(Type::CArray); - asm.mov(stack_ret, str.into()); + asm.mov(stack_ret, ary.into()); Some(KeepCompiling) } @@ -4332,11 +4332,11 @@ fn gen_opt_hash_freeze( return None; } - let str = jit.get_arg(0); + let hash = jit.get_arg(0); // Push the return value onto the stack let stack_ret = asm.stack_push(Type::CHash); - asm.mov(stack_ret, str.into()); + asm.mov(stack_ret, hash.into()); Some(KeepCompiling) } @@ -347,7 +347,6 @@ rb_zjit_shape_obj_too_complex_p(VALUE obj) } enum { - RB_SPECIAL_CONST_SHAPE_ID = SPECIAL_CONST_SHAPE_ID, RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID, }; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 77299c2657..ac10341996 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -259,6 +259,13 @@ fn main() { // From internal/re.h .allowlist_function("rb_reg_new_ary") + .allowlist_var("ARG_ENCODING_FIXED") + .allowlist_var("ARG_ENCODING_NONE") + + // From include/ruby/onigmo.h + .allowlist_var("ONIG_OPTION_IGNORECASE") + .allowlist_var("ONIG_OPTION_EXTEND") + .allowlist_var("ONIG_OPTION_MULTILINE") // `ruby_value_type` is a C enum and this stops it from // prefixing all the members with the name of the type @@ -354,7 +361,6 @@ fn main() { .allowlist_function("rb_zjit_singleton_class_p") .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") - .allowlist_var("RB_SPECIAL_CONST_SHAPE_ID") .allowlist_var("RB_INVALID_SHAPE_ID") // From jit.c diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 3b7742f16e..c60ec53285 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -2059,4 +2059,93 @@ mod tests { 0x4: adds x1, x0, #1 "}); } + + #[test] + fn test_reorder_c_args_no_cycle() { + crate::options::rb_zjit_prepare_options(); + let (mut asm, mut cb) = setup_asm(); + + asm.ccall(0 as _, vec![ + C_ARG_OPNDS[0], // mov x0, x0 (optimized away) + C_ARG_OPNDS[1], // mov x1, x1 (optimized away) + ]); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); + + assert_disasm!(cb, "100080d200023fd6", {" + 0x0: mov x16, #0 + 0x4: blr x16 + "}); + } + + #[test] + fn test_reorder_c_args_single_cycle() { + crate::options::rb_zjit_prepare_options(); + let (mut asm, mut cb) = setup_asm(); + + // x0 and x1 form a cycle + asm.ccall(0 as _, vec![ + C_ARG_OPNDS[1], // mov x0, x1 + C_ARG_OPNDS[0], // mov x1, x0 + C_ARG_OPNDS[2], // mov x2, x2 (optimized away) + ]); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); + + assert_disasm!(cb, "f00300aae00301aae10310aa100080d200023fd6", {" + 0x0: mov x16, x0 + 0x4: mov x0, x1 + 0x8: mov x1, x16 + 0xc: mov x16, #0 + 0x10: blr x16 + "}); + } + + #[test] + fn test_reorder_c_args_two_cycles() { + crate::options::rb_zjit_prepare_options(); + let (mut asm, mut cb) = setup_asm(); + + // x0 and x1 form a cycle, and x2 and rcx form another cycle + asm.ccall(0 as _, vec![ + C_ARG_OPNDS[1], // mov x0, x1 + C_ARG_OPNDS[0], // mov x1, x0 + C_ARG_OPNDS[3], // mov x2, rcx + C_ARG_OPNDS[2], // mov rcx, x2 + ]); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); + + assert_disasm!(cb, "f00302aae20303aae30310aaf00300aae00301aae10310aa100080d200023fd6", {" + 0x0: mov x16, x2 + 0x4: mov x2, x3 + 0x8: mov x3, x16 + 0xc: mov x16, x0 + 0x10: mov x0, x1 + 0x14: mov x1, x16 + 0x18: mov x16, #0 + 0x1c: blr x16 + "}); + } + + #[test] + fn test_reorder_c_args_large_cycle() { + crate::options::rb_zjit_prepare_options(); + let (mut asm, mut cb) = setup_asm(); + + // x0, x1, and x2 form a cycle + asm.ccall(0 as _, vec![ + C_ARG_OPNDS[1], // mov x0, x1 + C_ARG_OPNDS[2], // mov x1, x2 + C_ARG_OPNDS[0], // mov x2, x0 + ]); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); + + assert_disasm!(cb, "f00300aae00301aae10302aae20310aa100080d200023fd6", {" + 0x0: mov x16, x0 + 0x4: mov x0, x1 + 0x8: mov x1, x2 + 0xc: mov x2, x16 + 0x10: mov x16, #0 + 0x14: blr x16 + "}); + } + } diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 460e2719dd..1bb4cd024b 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -147,26 +147,15 @@ impl Opnd } } - /// Return Some(Opnd) with a given num_bits if self has num_bits. - /// None if self doesn't have a num_bits field. - pub fn try_num_bits(&self, num_bits: u8) -> Option<Opnd> { - assert!(num_bits == 8 || num_bits == 16 || num_bits == 32 || num_bits == 64); - match *self { - Opnd::Reg(reg) => Some(Opnd::Reg(reg.with_num_bits(num_bits))), - Opnd::Mem(Mem { base, disp, .. }) => Some(Opnd::Mem(Mem { base, disp, num_bits })), - Opnd::VReg { idx, .. } => Some(Opnd::VReg { idx, num_bits }), - _ => None, - } - } - - /// Return Opnd with a given num_bits if self has num_bits. - /// Panic otherwise. This should be used only when you know which Opnd self is. + /// Return Opnd with a given num_bits if self has num_bits. Panic otherwise. #[track_caller] pub fn with_num_bits(&self, num_bits: u8) -> Opnd { - if let Some(opnd) = self.try_num_bits(num_bits) { - opnd - } else { - unreachable!("with_num_bits should not be used on: {self:?}"); + assert!(num_bits == 8 || num_bits == 16 || num_bits == 32 || num_bits == 64); + match *self { + Opnd::Reg(reg) => Opnd::Reg(reg.with_num_bits(num_bits)), + Opnd::Mem(Mem { base, disp, .. }) => Opnd::Mem(Mem { base, disp, num_bits }), + Opnd::VReg { idx, .. } => Opnd::VReg { idx, num_bits }, + _ => unreachable!("with_num_bits should not be used for: {self:?}"), } } @@ -1213,7 +1202,7 @@ impl Assembler /// Append an instruction onto the current list of instructions and update /// the live ranges of any instructions whose outputs are being used as /// operands to this instruction. - pub fn push_insn(&mut self, mut insn: Insn) { + pub fn push_insn(&mut self, insn: Insn) { // Index of this instruction let insn_idx = self.insns.len(); @@ -1225,7 +1214,7 @@ impl Assembler } // If we find any VReg from previous instructions, extend the live range to insn_idx - let mut opnd_iter = insn.opnd_iter_mut(); + let mut opnd_iter = insn.opnd_iter(); while let Some(opnd) = opnd_iter.next() { match *opnd { Opnd::VReg { idx, .. } | @@ -1391,13 +1380,15 @@ impl Assembler } } - // If the output VReg of this instruction is used by another instruction, - // we need to allocate a register to it + // Allocate a register for the output operand if it exists let vreg_idx = match insn.out_opnd() { Some(Opnd::VReg { idx, .. }) => Some(*idx), _ => None, }; - if vreg_idx.is_some() && live_ranges[vreg_idx.unwrap()].end() != index { + if vreg_idx.is_some() { + if live_ranges[vreg_idx.unwrap()].end() == index { + debug!("Allocating a register for VReg({}) at instruction index {} even though it does not live past this index", vreg_idx.unwrap(), index); + } // This is going to be the output operand that we will set on the // instruction. CCall and LiveReg need to use a specific register. let mut out_reg = match insn { @@ -1477,6 +1468,18 @@ impl Assembler } } + // If we have an output that dies at its definition (it is unused), free up the + // register + if let Some(idx) = vreg_idx { + if live_ranges[idx].end() == index { + if let Some(reg) = reg_mapping[idx] { + pool.dealloc_reg(®); + } else { + unreachable!("no register allocated for insn {:?}", insn); + } + } + } + // Push instruction(s) let is_ccall = matches!(insn, Insn::CCall { .. }); match insn { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index f15b32f946..2a02e1b725 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -1247,13 +1247,14 @@ mod tests { #[test] fn test_reorder_c_args_no_cycle() { + crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); asm.ccall(0 as _, vec![ C_ARG_OPNDS[0], // mov rdi, rdi (optimized away) C_ARG_OPNDS[1], // mov rsi, rsi (optimized away) ]); - asm.compile_with_num_regs(&mut cb, 0); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm!(cb, "b800000000ffd0", {" 0x0: mov eax, 0 @@ -1263,6 +1264,7 @@ mod tests { #[test] fn test_reorder_c_args_single_cycle() { + crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // rdi and rsi form a cycle @@ -1271,7 +1273,7 @@ mod tests { C_ARG_OPNDS[0], // mov rsi, rdi C_ARG_OPNDS[2], // mov rdx, rdx (optimized away) ]); - asm.compile_with_num_regs(&mut cb, 0); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm!(cb, "4989f34889fe4c89dfb800000000ffd0", {" 0x0: mov r11, rsi @@ -1284,6 +1286,7 @@ mod tests { #[test] fn test_reorder_c_args_two_cycles() { + crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // rdi and rsi form a cycle, and rdx and rcx form another cycle @@ -1293,7 +1296,7 @@ mod tests { C_ARG_OPNDS[3], // mov rdx, rcx C_ARG_OPNDS[2], // mov rcx, rdx ]); - asm.compile_with_num_regs(&mut cb, 0); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm!(cb, "4989f34889fe4c89df4989cb4889d14c89dab800000000ffd0", {" 0x0: mov r11, rsi @@ -1309,6 +1312,7 @@ mod tests { #[test] fn test_reorder_c_args_large_cycle() { + crate::options::rb_zjit_prepare_options(); let (mut asm, mut cb) = setup_asm(); // rdi, rsi, and rdx form a cycle @@ -1317,7 +1321,7 @@ mod tests { C_ARG_OPNDS[2], // mov rsi, rdx C_ARG_OPNDS[0], // mov rdx, rdi ]); - asm.compile_with_num_regs(&mut cb, 0); + asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); assert_disasm!(cb, "4989f34889d64889fa4c89dfb800000000ffd0", {" 0x0: mov r11, rsi diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d2810cddb7..0549365666 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1,6 +1,7 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; use std::ffi::{c_int, c_long, c_void}; +use std::slice; use crate::asm::Label; use crate::backend::current::{Reg, ALLOC_REGS}; @@ -152,8 +153,9 @@ fn gen_iseq_call(cb: &mut CodeBlock, caller_iseq: IseqPtr, iseq_call: &Rc<RefCel // Update the JIT-to-JIT call to call the stub let stub_addr = stub_ptr.raw_ptr(cb); + let iseq = iseq_call.borrow().iseq; iseq_call.borrow_mut().regenerate(cb, |asm| { - asm_comment!(asm, "call function stub: {}", iseq_get_location(iseq_call.borrow().iseq, 0)); + asm_comment!(asm, "call function stub: {}", iseq_get_location(iseq, 0)); asm.ccall(stub_addr, vec![]); }); Some(()) @@ -337,7 +339,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Const { val: Const::Value(val) } => gen_const(*val), Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => gen_new_hash(jit, asm, elements, &function.frame_state(*state)), - Insn::NewRange { low, high, flag, state } => gen_new_range(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), + Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)), // concatstrings shouldn't have 0 strings @@ -345,6 +347,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::StringConcat { strings, .. } if strings.is_empty() => return None, Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)), Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)), + Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)), Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment Insn::Jump(branch) => no_output!(gen_jump(jit, asm, branch)), @@ -355,7 +358,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SendWithoutBlockDirect { cd, state, self_val, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args)), Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state)), - Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args))?, + // Ensure we have enough room fit ec, self, and arguments + // TODO remove this check when we have stack args (we can use Time.new to test it) + Insn::InvokeBuiltin { bf, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return None, + Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args)), Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)), @@ -370,7 +376,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumOr { left, right } => gen_fixnum_or(asm, opnd!(left), opnd!(right)), Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), - Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state))?, + Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args)), @@ -541,22 +547,18 @@ fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_in asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) } -fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: Vec<Opnd>) -> Option<lir::Opnd> { - // Ensure we have enough room fit ec, self, and arguments - // TODO remove this check when we have stack args (we can use Time.new to test it) - if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) { - return None; - } - +fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: Vec<Opnd>) -> lir::Opnd { + assert!(bf.argc + 2 <= C_ARG_OPNDS.len() as i32, + "gen_invokebuiltin should not be called for builtin function {} with too many arguments: {}", + unsafe { std::ffi::CStr::from_ptr(bf.name).to_str().unwrap() }, + bf.argc); // Anything can happen inside builtin functions gen_prepare_non_leaf_call(jit, asm, state); let mut cargs = vec![EC]; cargs.extend(args); - let val = asm.ccall(bf.func_ptr as *const u8, cargs); - - Some(val) + asm.ccall(bf.func_ptr as *const u8, cargs) } /// Record a patch point that should be invalidated on a given invariant @@ -736,25 +738,26 @@ fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { } /// Set branch params to basic block arguments -fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdge) -> Option<()> { - if !branch.args.is_empty() { - asm_comment!(asm, "set branch params: {}", branch.args.len()); - let mut moves: Vec<(Reg, Opnd)> = vec![]; - for (idx, &arg) in branch.args.iter().enumerate() { - match param_opnd(idx) { - Opnd::Reg(reg) => { - // If a parameter is a register, we need to parallel-move it - moves.push((reg, jit.get_opnd(arg))); - }, - param => { - // If a parameter is memory, we set it beforehand - asm.mov(param, jit.get_opnd(arg)); - } +fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdge) { + if branch.args.is_empty() { + return; + } + + asm_comment!(asm, "set branch params: {}", branch.args.len()); + let mut moves: Vec<(Reg, Opnd)> = vec![]; + for (idx, &arg) in branch.args.iter().enumerate() { + match param_opnd(idx) { + Opnd::Reg(reg) => { + // If a parameter is a register, we need to parallel-move it + moves.push((reg, jit.get_opnd(arg))); + }, + param => { + // If a parameter is memory, we set it beforehand + asm.mov(param, jit.get_opnd(arg)); } } - asm.parallel_mov(moves); } - Some(()) + asm.parallel_mov(moves); } /// Get a method parameter on JIT entry. As of entry, whether EP is escaped or not solely @@ -1003,35 +1006,11 @@ fn gen_new_hash( pairs.push(val); } - let n = pairs.len(); - - // Calculate the compile-time NATIVE_STACK_PTR offset from NATIVE_BASE_PTR - // At this point, frame_setup(&[], jit.c_stack_slots) has been called, - // which allocated aligned_stack_bytes(jit.c_stack_slots) on the stack - let frame_size = aligned_stack_bytes(jit.c_stack_slots); - let allocation_size = aligned_stack_bytes(n); - - asm_comment!(asm, "allocate {} bytes on C stack for {} hash elements", allocation_size, n); - asm.sub_into(NATIVE_STACK_PTR, allocation_size.into()); - - // Calculate the total offset from NATIVE_BASE_PTR to our buffer - let total_offset_from_base = (frame_size + allocation_size) as i32; - - for (idx, &pair_opnd) in pairs.iter().enumerate() { - let slot_offset = -total_offset_from_base + (idx as i32 * SIZEOF_VALUE_I32); - asm.mov( - Opnd::mem(VALUE_BITS, NATIVE_BASE_PTR, slot_offset), - pair_opnd - ); - } - - let argv = asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base)); - + let argv = gen_push_opnds(jit, asm, &pairs); let argc = (elements.len() * 2) as ::std::os::raw::c_long; asm_ccall!(asm, rb_hash_bulk_insert, lir::Opnd::Imm(argc), argv, new_hash); - asm_comment!(asm, "restore C stack pointer"); - asm.add_into(NATIVE_STACK_PTR, allocation_size.into()); + gen_pop_opnds(asm, &pairs); } new_hash @@ -1039,13 +1018,15 @@ fn gen_new_hash( /// Compile a new range instruction fn gen_new_range( + jit: &JITState, asm: &mut Assembler, low: lir::Opnd, high: lir::Opnd, flag: RangeType, state: &FrameState, ) -> lir::Opnd { - gen_prepare_call_with_gc(asm, state); + // Sometimes calls `low.<=>(high)` + gen_prepare_non_leaf_call(jit, asm, state); // Call rb_range_new(low, high, flag) asm_ccall!(asm, rb_range_new, low, high, (flag as i64).into()) @@ -1171,7 +1152,7 @@ fn gen_test(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { } /// Compile a type check with a side exit -fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> Option<lir::Opnd> { +fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> lir::Opnd { if guard_type.is_subtype(types::Fixnum) { asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)); asm.jz(side_exit(jit, state, GuardType(guard_type))); @@ -1182,9 +1163,9 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard asm.jne(side_exit(jit, state, GuardType(guard_type))); } else if guard_type.is_subtype(types::StaticSymbol) { // Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG - // Use 8-bit comparison like YJIT does - debug_assert!(val.try_num_bits(8).is_some(), "GuardType should not be used for a known constant, but val was: {val:?}"); - asm.cmp(val.try_num_bits(8)?, Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); + // Use 8-bit comparison like YJIT does. GuardType should not be used + // for a known VALUE, which with_num_bits() does not support. + asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); asm.jne(side_exit(jit, state, GuardType(guard_type))); } else if guard_type.is_subtype(types::NilClass) { asm.cmp(val, Qnil.into()); @@ -1225,7 +1206,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard } else { unimplemented!("unsupported type: {guard_type}"); } - Some(val) + val } /// Compile an identity check with a side exit @@ -1368,11 +1349,17 @@ fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 { local_size_and_idx_to_ep_offset(local_size as usize, local_idx) } -/// Convert the number of locals and a local index to an offset in the EP +/// Convert the number of locals and a local index to an offset from the EP pub fn local_size_and_idx_to_ep_offset(local_size: usize, local_idx: usize) -> i32 { local_size as i32 - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32 } +/// Convert the number of locals and a local index to an offset from the BP. +/// We don't move the SP register after entry, so we often use SP as BP. +pub fn local_size_and_idx_to_bp_offset(local_size: usize, local_idx: usize) -> i32 { + local_size_and_idx_to_ep_offset(local_size, local_idx) + 1 +} + /// Convert ISEQ into High-level IR fn compile_iseq(iseq: IseqPtr) -> Option<Function> { let mut function = match iseq_to_hir(iseq) { @@ -1468,26 +1455,41 @@ c_callable! { /// This function is expected to be called repeatedly when ZJIT fails to compile the stub. /// We should be able to compile most (if not all) function stubs by side-exiting at unsupported /// instructions, so this should be used primarily for cb.has_dropped_bytes() situations. - fn function_stub_hit(iseq_call_ptr: *const c_void, ec: EcPtr, sp: *mut VALUE) -> *const u8 { + fn function_stub_hit(iseq_call_ptr: *const c_void, cfp: CfpPtr, sp: *mut VALUE) -> *const u8 { with_vm_lock(src_loc!(), || { - // gen_push_frame() doesn't set PC and SP, so we need to set them before exit. + // gen_push_frame() doesn't set PC, so we need to set them before exit. // function_stub_hit_body() may allocate and call gc_validate_pc(), so we always set PC. let iseq_call = unsafe { Rc::from_raw(iseq_call_ptr as *const RefCell<IseqCall>) }; - let cfp = unsafe { get_ec_cfp(ec) }; - let pc = unsafe { rb_iseq_pc_at_idx(iseq_call.borrow().iseq, 0) }; // TODO: handle opt_pc once supported + let iseq = iseq_call.borrow().iseq; + let pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; // TODO: handle opt_pc once supported unsafe { rb_set_cfp_pc(cfp, pc) }; - unsafe { rb_set_cfp_sp(cfp, sp) }; + + // JIT-to-JIT calls don't set SP or fill nils to uninitialized (non-argument) locals. + // We need to set them if we side-exit from function_stub_hit. + fn spill_stack(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE) { + unsafe { + // Set SP which gen_push_frame() doesn't set + rb_set_cfp_sp(cfp, sp); + + // Fill nils to uninitialized (non-argument) locals + let local_size = get_iseq_body_local_table_size(iseq) as usize; + let num_params = get_iseq_body_param_size(iseq) as usize; + let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); + slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); + } + } // If we already know we can't compile the ISEQ, fail early without cb.mark_all_executable(). // TODO: Alan thinks the payload status part of this check can happen without the VM lock, since the whole // code path can be made read-only. But you still need the check as is while holding the VM lock in any case. let cb = ZJITState::get_code_block(); - let payload = get_or_create_iseq_payload(iseq_call.borrow().iseq); + let payload = get_or_create_iseq_payload(iseq); if cb.has_dropped_bytes() || payload.status == IseqStatus::CantCompile { // We'll use this Rc again, so increment the ref count decremented by from_raw. unsafe { Rc::increment_strong_count(iseq_call_ptr as *const RefCell<IseqCall>); } // Exit to the interpreter + spill_stack(iseq, cfp, sp); return ZJITState::get_exit_trampoline().raw_ptr(cb); } @@ -1497,6 +1499,7 @@ c_callable! { code_ptr } else { // Exit to the interpreter + spill_stack(iseq, cfp, sp); ZJITState::get_exit_trampoline() }; cb.mark_all_executable(); @@ -1520,8 +1523,9 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc<RefCell<IseqCall>>) // Update the stub to call the code pointer let code_addr = code_ptr.raw_ptr(cb); + let iseq = iseq_call.borrow().iseq; iseq_call.borrow_mut().regenerate(cb, |asm| { - asm_comment!(asm, "call compiled function: {}", iseq_get_location(iseq_call.borrow().iseq, 0)); + asm_comment!(asm, "call compiled function: {}", iseq_get_location(iseq, 0)); asm.ccall(code_addr, vec![]); }); @@ -1559,7 +1563,7 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Option<CodePtr> { const { assert!(ALLOC_REGS.len() % 2 == 0, "x86_64 would need to push one more if we push an odd number of regs"); } // Compile the stubbed ISEQ - let jump_addr = asm_ccall!(asm, function_stub_hit, SCRATCH_OPND, EC, SP); + let jump_addr = asm_ccall!(asm, function_stub_hit, SCRATCH_OPND, CFP, SP); asm.mov(SCRATCH_OPND, jump_addr); asm_comment!(asm, "restore argument registers"); @@ -1593,36 +1597,58 @@ pub fn gen_exit_trampoline(cb: &mut CodeBlock) -> Option<CodePtr> { }) } -fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec<Opnd>, state: &FrameState) -> Opnd { - gen_prepare_non_leaf_call(jit, asm, state); +fn gen_push_opnds(jit: &mut JITState, asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { + let n = opnds.len(); // Calculate the compile-time NATIVE_STACK_PTR offset from NATIVE_BASE_PTR // At this point, frame_setup(&[], jit.c_stack_slots) has been called, // which allocated aligned_stack_bytes(jit.c_stack_slots) on the stack let frame_size = aligned_stack_bytes(jit.c_stack_slots); - let n = strings.len(); let allocation_size = aligned_stack_bytes(n); - asm_comment!(asm, "allocate {} bytes on C stack for {} strings", allocation_size, n); + asm_comment!(asm, "allocate {} bytes on C stack for {} values", allocation_size, n); asm.sub_into(NATIVE_STACK_PTR, allocation_size.into()); // Calculate the total offset from NATIVE_BASE_PTR to our buffer let total_offset_from_base = (frame_size + allocation_size) as i32; - for (idx, &string_opnd) in strings.iter().enumerate() { + for (idx, &opnd) in opnds.iter().enumerate() { let slot_offset = -total_offset_from_base + (idx as i32 * SIZEOF_VALUE_I32); asm.mov( Opnd::mem(VALUE_BITS, NATIVE_BASE_PTR, slot_offset), - string_opnd + opnd ); } - let first_string_ptr = asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base)); - - let result = asm_ccall!(asm, rb_str_concat_literals, n.into(), first_string_ptr); + asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base)) +} +fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { asm_comment!(asm, "restore C stack pointer"); + let allocation_size = aligned_stack_bytes(opnds.len()); asm.add_into(NATIVE_STACK_PTR, allocation_size.into()); +} + +fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec<Opnd>, state: &FrameState) -> Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + let first_opnd_ptr = gen_push_opnds(jit, asm, &values); + + let tmp_ary = asm_ccall!(asm, rb_ary_tmp_new_from_values, Opnd::Imm(0), values.len().into(), first_opnd_ptr); + let result = asm_ccall!(asm, rb_reg_new_ary, tmp_ary, opt.into()); + asm_ccall!(asm, rb_ary_clear, tmp_ary); + + gen_pop_opnds(asm, &values); + + result +} + +fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec<Opnd>, state: &FrameState) -> Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + let first_string_ptr = gen_push_opnds(jit, asm, &strings); + let result = asm_ccall!(asm, rb_str_concat_literals, strings.len().into(), first_string_ptr); + gen_pop_opnds(asm, &strings); result } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index eff99daf9b..3ec2954c73 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -269,7 +269,6 @@ pub type IseqPtr = *const rb_iseq_t; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct ShapeId(pub u32); -pub const SPECIAL_CONST_SHAPE_ID: ShapeId = ShapeId(RB_SPECIAL_CONST_SHAPE_ID); pub const INVALID_SHAPE_ID: ShapeId = ShapeId(RB_INVALID_SHAPE_ID); // Given an ISEQ pointer, convert PC to insn_idx diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5c939fabe7..ee6d4d5e0e 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -30,6 +30,11 @@ impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> { fmt.write_str("__IncompleteArrayField") } } +pub const ONIG_OPTION_IGNORECASE: u32 = 1; +pub const ONIG_OPTION_EXTEND: u32 = 2; +pub const ONIG_OPTION_MULTILINE: u32 = 4; +pub const ARG_ENCODING_FIXED: u32 = 16; +pub const ARG_ENCODING_NONE: u32 = 32; pub const INTEGER_REDEFINED_OP_FLAG: u32 = 1; pub const FLOAT_REDEFINED_OP_FLAG: u32 = 2; pub const STRING_REDEFINED_OP_FLAG: u32 = 4; @@ -719,7 +724,6 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub const RB_SPECIAL_CONST_SHAPE_ID: _bindgen_ty_38 = 33554432; pub const RB_INVALID_SHAPE_ID: _bindgen_ty_38 = 4294967295; pub type _bindgen_ty_38 = u32; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index afe358ec1d..e7aaf64f28 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -473,6 +473,9 @@ pub enum Insn { StringIntern { val: InsnId, state: InsnId }, StringConcat { strings: Vec<InsnId>, state: InsnId }, + /// Combine count stack values into a regexp + ToRegexp { opt: usize, values: Vec<InsnId>, state: InsnId }, + /// Put special object (VMCORE, CBASE, etc.) based on value_type PutSpecialObject { value_type: SpecialObjectType }, @@ -635,7 +638,6 @@ impl Insn { // NewHash's operands may be hashed and compared for equality, which could have // side-effects. Insn::NewHash { elements, .. } => elements.len() > 0, - Insn::NewRange { .. } => false, Insn::ArrayDup { .. } => false, Insn::HashDup { .. } => false, Insn::Test { .. } => false, @@ -657,6 +659,9 @@ impl Insn { Insn::GetLocal { .. } => false, Insn::IsNil { .. } => false, Insn::CCall { elidable, .. } => !elidable, + // TODO: NewRange is effects free if we can prove the two ends to be Fixnum, + // but we don't have type information here in `impl Insn`. See rb_range_new(). + Insn::NewRange { .. } => true, _ => true, } } @@ -668,6 +673,14 @@ pub struct InsnPrinter<'a> { ptr_map: &'a PtrPrintMap, } +static REGEXP_FLAGS: &[(u32, &str)] = &[ + (ONIG_OPTION_MULTILINE, "MULTILINE"), + (ONIG_OPTION_IGNORECASE, "IGNORECASE"), + (ONIG_OPTION_EXTEND, "EXTENDED"), + (ARG_ENCODING_FIXED, "FIXEDENCODING"), + (ARG_ENCODING_NONE, "NOENCODING"), +]; + impl<'a> std::fmt::Display for InsnPrinter<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self.inner { @@ -716,6 +729,28 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Ok(()) } + Insn::ToRegexp { values, opt, .. } => { + write!(f, "ToRegexp")?; + let mut prefix = " "; + for value in values { + write!(f, "{prefix}{value}")?; + prefix = ", "; + } + + let opt = *opt as u32; + if opt != 0 { + write!(f, ", ")?; + let mut sep = ""; + for (flag, name) in REGEXP_FLAGS { + if opt & flag != 0 { + write!(f, "{sep}{name}")?; + sep = "|"; + } + } + } + + Ok(()) + } Insn::Test { val } => { write!(f, "Test {val}") } Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::Jump(target) => { write!(f, "Jump {target}") } @@ -1179,6 +1214,7 @@ impl Function { &StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state }, &StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) }, &StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) }, + &ToRegexp { opt, ref values, state } => ToRegexp { opt, values: find_vec!(values), state }, &Test { val } => Test { val: find!(val) }, &IsNil { val } => IsNil { val: find!(val) }, &Jump(ref target) => Jump(find_branch_edge!(target)), @@ -1305,6 +1341,7 @@ impl Function { Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, + Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, Insn::NewHash { .. } => types::HashExact, @@ -1939,6 +1976,10 @@ impl Function { worklist.extend(strings); worklist.push_back(state); } + &Insn::ToRegexp { ref values, state, .. } => { + worklist.extend(values); + worklist.push_back(state); + } | &Insn::Return { val } | &Insn::Throw { val, .. } | &Insn::Test { val } @@ -2863,6 +2904,15 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let insn_id = fun.push_insn(block, Insn::StringConcat { strings, state: exit_id }); state.stack_push(insn_id); } + YARVINSN_toregexp => { + // First arg contains the options (multiline, extended, ignorecase) used to create the regexp + let opt = get_arg(pc, 0).as_usize(); + let count = get_arg(pc, 1).as_usize(); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let values = state.stack_pop_n(count)?; + let insn_id = fun.push_insn(block, Insn::ToRegexp { opt, values, state: exit_id }); + state.stack_push(insn_id); + } YARVINSN_newarray => { let count = get_arg(pc, 0).as_usize(); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); @@ -5331,6 +5381,47 @@ mod tests { } #[test] + fn test_toregexp() { + eval(r##" + def test = /#{1}#{2}#{3}/ + "##); + assert_method_hir_with_opcode("test", YARVINSN_toregexp, expect![[r#" + fn test@<compiled>:2: + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + v4:BasicObject = ObjToString v2 + v6:String = AnyToString v2, str: v4 + v7:Fixnum[2] = Const Value(2) + v9:BasicObject = ObjToString v7 + v11:String = AnyToString v7, str: v9 + v12:Fixnum[3] = Const Value(3) + v14:BasicObject = ObjToString v12 + v16:String = AnyToString v12, str: v14 + v18:RegexpExact = ToRegexp v6, v11, v16 + Return v18 + "#]]); + } + + #[test] + fn test_toregexp_with_options() { + eval(r##" + def test = /#{1}#{2}/mixn + "##); + assert_method_hir_with_opcode("test", YARVINSN_toregexp, expect![[r#" + fn test@<compiled>:2: + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + v4:BasicObject = ObjToString v2 + v6:String = AnyToString v2, str: v4 + v7:Fixnum[2] = Const Value(2) + v9:BasicObject = ObjToString v7 + v11:String = AnyToString v7, str: v9 + v13:RegexpExact = ToRegexp v6, v11, MULTILINE|IGNORECASE|EXTENDED|NOENCODING + Return v13 + "#]]); + } + + #[test] fn throw() { eval(" define_method(:throw_return) { return 1 } @@ -6110,6 +6201,29 @@ mod opt_tests { } #[test] + fn test_do_not_eliminate_new_range_non_fixnum() { + eval(" + def test() + _ = (-'a'..'b') + 0 + end + test; test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@<compiled>:3: + bb0(v0:BasicObject): + v1:NilClass = Const Value(nil) + v4:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v6:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v8:StringExact = StringCopy v6 + v10:RangeExact = NewRange v4 NewRangeInclusive v8 + v11:Fixnum[0] = Const Value(0) + Return v11 + "#]]); + } + + #[test] fn test_eliminate_new_array_with_elements() { eval(" def test(a) diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 7ffaea29dc..771d90cb0e 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -98,19 +98,32 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize let obj = profiler.peek_at_stack((n - i - 1) as isize); // TODO(max): Handle GC-hidden classes like Array, Hash, etc and make them look normal or // drop them or something - let ty = ProfiledType::new(obj.class_of(), obj.shape_id_of()); + let ty = ProfiledType::new(obj); unsafe { rb_gc_writebarrier(profiler.iseq.into(), ty.class()) }; types[i].observe(ty); } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Flags(u32); + +impl Flags { + const NONE: u32 = 0; + const IS_IMMEDIATE: u32 = 1 << 0; + + pub fn none() -> Self { Self(Self::NONE) } + + pub fn immediate() -> Self { Self(Self::IS_IMMEDIATE) } + pub fn is_immediate(self) -> bool { (self.0 & Self::IS_IMMEDIATE) != 0 } +} + /// opt_send_without_block/opt_plus/... should store: /// * the class of the receiver, so we can do method lookup /// * the shape of the receiver, so we can optimize ivar lookup /// with those two, pieces of information, we can also determine when an object is an immediate: -/// * Integer + SPECIAL_CONST_SHAPE_ID == Fixnum -/// * Float + SPECIAL_CONST_SHAPE_ID == Flonum -/// * Symbol + SPECIAL_CONST_SHAPE_ID == StaticSymbol +/// * Integer + IS_IMMEDIATE == Fixnum +/// * Float + IS_IMMEDIATE == Flonum +/// * Symbol + IS_IMMEDIATE == StaticSymbol /// * NilClass == Nil /// * TrueClass == True /// * FalseClass == False @@ -118,6 +131,7 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize pub struct ProfiledType { class: VALUE, shape: ShapeId, + flags: Flags, } impl Default for ProfiledType { @@ -127,12 +141,42 @@ impl Default for ProfiledType { } impl ProfiledType { - fn new(class: VALUE, shape: ShapeId) -> Self { - Self { class, shape } + fn new(obj: VALUE) -> Self { + if obj == Qfalse { + return Self { class: unsafe { rb_cFalseClass }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + if obj == Qtrue { + return Self { class: unsafe { rb_cTrueClass }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + if obj == Qnil { + return Self { class: unsafe { rb_cNilClass }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + if obj.fixnum_p() { + return Self { class: unsafe { rb_cInteger }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + if obj.flonum_p() { + return Self { class: unsafe { rb_cFloat }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + if obj.static_sym_p() { + return Self { class: unsafe { rb_cSymbol }, + shape: INVALID_SHAPE_ID, + flags: Flags::immediate() }; + } + Self { class: obj.class_of(), shape: obj.shape_id_of(), flags: Flags::none() } } pub fn empty() -> Self { - Self { class: VALUE(0), shape: INVALID_SHAPE_ID } + Self { class: VALUE(0), shape: INVALID_SHAPE_ID, flags: Flags::none() } } pub fn is_empty(&self) -> bool { @@ -148,27 +192,27 @@ impl ProfiledType { } pub fn is_fixnum(&self) -> bool { - self.class == unsafe { rb_cInteger } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cInteger } && self.flags.is_immediate() } pub fn is_flonum(&self) -> bool { - self.class == unsafe { rb_cFloat } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cFloat } && self.flags.is_immediate() } pub fn is_static_symbol(&self) -> bool { - self.class == unsafe { rb_cSymbol } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cSymbol } && self.flags.is_immediate() } pub fn is_nil(&self) -> bool { - self.class == unsafe { rb_cNilClass } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cNilClass } && self.flags.is_immediate() } pub fn is_true(&self) -> bool { - self.class == unsafe { rb_cTrueClass } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cTrueClass } && self.flags.is_immediate() } pub fn is_false(&self) -> bool { - self.class == unsafe { rb_cFalseClass } && self.shape == SPECIAL_CONST_SHAPE_ID + self.class == unsafe { rb_cFalseClass } && self.flags.is_immediate() } } |