diff --git a/array.c b/array.c
index e13239ad3daaa1..b4718238763bab 100644
--- a/array.c
+++ b/array.c
@@ -1789,14 +1789,10 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
/*
* call-seq:
- * self[index] -> object or nil
- * self[start, length] -> object or nil
+ * self[offset] -> object or nil
+ * self[offset, size] -> object or nil
* self[range] -> object or nil
* self[aseq] -> object or nil
- * slice(index) -> object or nil
- * slice(start, length) -> object or nil
- * slice(range) -> object or nil
- * slice(aseq) -> object or nil
*
* Returns elements from +self+; does not modify +self+.
*
@@ -1804,27 +1800,27 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
*
* a = [:foo, 'bar', 2]
*
- * # Single argument index: returns one element.
+ * # Single argument offset: returns one element.
* a[0] # => :foo # Zero-based index.
* a[-1] # => 2 # Negative index counts backwards from end.
*
- * # Arguments start and length: returns an array.
+ * # Arguments offset and size: returns an array.
* a[1, 2] # => ["bar", 2]
- * a[-2, 2] # => ["bar", 2] # Negative start counts backwards from end.
+ * a[-2, 2] # => ["bar", 2] # Negative offset counts backwards from end.
*
* # Single argument range: returns an array.
* a[0..1] # => [:foo, "bar"]
* a[0..-2] # => [:foo, "bar"] # Negative range-begin counts backwards from end.
* a[-2..2] # => ["bar", 2] # Negative range-end counts backwards from end.
*
- * When a single integer argument +index+ is given, returns the element at offset +index+:
+ * When a single integer argument +offset+ is given, returns the element at offset +offset+:
*
* a = [:foo, 'bar', 2]
* a[0] # => :foo
* a[2] # => 2
* a # => [:foo, "bar", 2]
*
- * If +index+ is negative, counts backwards from the end of +self+:
+ * If +offset+ is negative, counts backwards from the end of +self+:
*
* a = [:foo, 'bar', 2]
* a[-1] # => 2
@@ -1832,29 +1828,29 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
*
* If +index+ is out of range, returns +nil+.
*
- * When two Integer arguments +start+ and +length+ are given,
- * returns a new array of size +length+ containing successive elements beginning at offset +start+:
+ * When two Integer arguments +offset+ and +size+ are given,
+ * returns a new array of size +size+ containing successive elements beginning at offset +offset+:
*
* a = [:foo, 'bar', 2]
* a[0, 2] # => [:foo, "bar"]
* a[1, 2] # => ["bar", 2]
*
- * If start + length is greater than self.length,
- * returns all elements from offset +start+ to the end:
+ * If offset + size is greater than self.size,
+ * returns all elements from offset +offset+ to the end:
*
* a = [:foo, 'bar', 2]
* a[0, 4] # => [:foo, "bar", 2]
* a[1, 3] # => ["bar", 2]
* a[2, 2] # => [2]
*
- * If start == self.size and length >= 0,
+ * If offset == self.size and size >= 0,
* returns a new empty array.
*
- * If +length+ is negative, returns +nil+.
+ * If +size+ is negative, returns +nil+.
*
* When a single Range argument +range+ is given,
- * treats range.min as +start+ above
- * and range.size as +length+ above:
+ * treats range.min as +offset+ above
+ * and range.size as +size+ above:
*
* a = [:foo, 'bar', 2]
* a[0..1] # => [:foo, "bar"]
diff --git a/configure.ac b/configure.ac
index a276783b6c2f63..90326e2926fc33 100644
--- a/configure.ac
+++ b/configure.ac
@@ -83,9 +83,17 @@ AC_ARG_WITH(baseruby,
],
[
AC_PATH_PROG([BASERUBY], [ruby], [false])
+ HAVE_BASERUBY=
])
-AS_IF([test "$HAVE_BASERUBY" != no], [
- RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" --verbose || HAVE_BASERUBY=no
+AS_IF([test "$HAVE_BASERUBY" = no], [
+ # --without-baseruby
+], [error=`RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" --verbose 2>&1`], [
+ HAVE_BASERUBY=yes
+], [test "$HAVE_BASERUBY" = ""], [ # no --with-baseruby option
+ AC_MSG_WARN($error) # just warn and continue
+ HAVE_BASERUBY=no
+], [ # the ruby given by --with-baseruby is too old
+ AC_MSG_ERROR($error) # bail out
])
AS_IF([test "${HAVE_BASERUBY:=no}" != no], [
AS_CASE(["$build_os"], [mingw*], [
diff --git a/doc/string/aref.rdoc b/doc/string/aref.rdoc
index 59c6ae97ace01e..a9ab8857bc1fc8 100644
--- a/doc/string/aref.rdoc
+++ b/doc/string/aref.rdoc
@@ -1,30 +1,30 @@
Returns the substring of +self+ specified by the arguments.
-Form self[index]
+Form self[offset]
-With non-negative integer argument +index+ given,
-returns the 1-character substring found in self at character offset index:
+With non-negative integer argument +offset+ given,
+returns the 1-character substring found in self at character offset +offset+:
'hello'[0] # => "h"
'hello'[4] # => "o"
'hello'[5] # => nil
'こんにちは'[4] # => "は"
-With negative integer argument +index+ given,
+With negative integer argument +offset+ given,
counts backward from the end of +self+:
'hello'[-1] # => "o"
'hello'[-5] # => "h"
'hello'[-6] # => nil
-Form self[start, length]
+Form self[offset, size]
-With integer arguments +start+ and +length+ given,
-returns a substring of size +length+ characters (as available)
-beginning at character offset specified by +start+.
+With integer arguments +offset+ and +size+ given,
+returns a substring of size +size+ characters (as available)
+beginning at character offset specified by +offset+.
-If argument +start+ is non-negative,
-the offset is +start+:
+If argument +offset+ is non-negative,
+the offset is +offset+:
'hello'[0, 1] # => "h"
'hello'[0, 5] # => "hello"
@@ -33,7 +33,7 @@ the offset is +start+:
'hello'[2, 0] # => ""
'hello'[2, -1] # => nil
-If argument +start+ is negative,
+If argument +offset+ is negative,
counts backward from the end of +self+:
'hello'[-1, 1] # => "o"
@@ -41,7 +41,7 @@ counts backward from the end of +self+:
'hello'[-1, 0] # => ""
'hello'[-6, 5] # => nil
-Special case: if +start+ equals the length of +self+,
+Special case: if +offset+ equals the size of +self+,
returns a new empty string:
'hello'[5, 3] # => ""
diff --git a/internal/error.h b/internal/error.h
index 4b41aee77b00ec..ae9a13fceced27 100644
--- a/internal/error.h
+++ b/internal/error.h
@@ -75,8 +75,8 @@ PRINTF_ARGS(void rb_warn_deprecated_to_remove(const char *removal, const char *f
PRINTF_ARGS(void rb_warn_reserved_name(const char *removal, const char *fmt, ...), 2, 3);
#if RUBY_DEBUG
# include "ruby/version.h"
-# define RUBY_VERSION_SINCE(major, minor) (RUBY_API_VERSION_CODE >= (major * 10000) + (minor) * 100)
-# define RUBY_VERSION_BEFORE(major, minor) (RUBY_API_VERSION_CODE < (major * 10000) + (minor) * 100)
+# define RUBY_VERSION_SINCE(major, minor) (RUBY_API_VERSION_CODE >= (major) * 10000 + (minor) * 100)
+# define RUBY_VERSION_BEFORE(major, minor) (RUBY_API_VERSION_CODE < (major) * 10000 + (minor) * 100)
# if defined(RBIMPL_WARNING_PRAGMA0)
# define RBIMPL_TODO0(x) RBIMPL_WARNING_PRAGMA0(message(x))
# elif RBIMPL_COMPILER_IS(MSVC)
diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb
index 49fb90249dd6ae..3ac10aa25606d3 100644
--- a/lib/bundled_gems.rb
+++ b/lib/bundled_gems.rb
@@ -124,6 +124,16 @@ def self.warning?(name, specs: nil)
return if specs.include?(name)
+ # Don't warn if a hyphenated gem provides this feature
+ # (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem)
+ if subfeature
+ feature_parts = feature.split("/")
+ if feature_parts.size >= 2
+ hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}"
+ return if specs.include?(hyphenated_gem)
+ end
+ end
+
return if WARNED[name]
WARNED[name] = true
diff --git a/re.c b/re.c
index fe8e93c6a6b96c..b2c1909c153895 100644
--- a/re.c
+++ b/re.c
@@ -2213,12 +2213,12 @@ match_ary_aref(VALUE match, VALUE idx, VALUE result)
/*
* call-seq:
- * matchdata[index] -> string or nil
- * matchdata[start, length] -> array
- * matchdata[range] -> array
- * matchdata[name] -> string or nil
+ * self[offset] -> string or nil
+ * self[offset, size] -> array
+ * self[range] -> array
+ * self[name] -> string or nil
*
- * When arguments +index+, +start and +length+, or +range+ are given,
+ * When arguments +offset+, +offset+ and +size+, or +range+ are given,
* returns match and captures in the style of Array#[]:
*
* m = /(.)(.)(\d+)(\d)/.match("THX1138.")
@@ -3663,12 +3663,11 @@ reg_match_pos(VALUE re, VALUE *strp, long pos, VALUE* set_match)
/*
* call-seq:
- * regexp =~ string -> integer or nil
+ * self =~ other -> integer or nil
*
* Returns the integer index (in characters) of the first match
- * for +self+ and +string+, or +nil+ if none;
- * also sets the
- * {rdoc-ref:Regexp global variables}[rdoc-ref:Regexp@Global+Variables]:
+ * for +self+ and +other+, or +nil+ if none;
+ * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables].
*
* /at/ =~ 'input data' # => 7
* $~ # => #
diff --git a/spec/ruby/library/socket/tcpsocket/shared/new.rb b/spec/ruby/library/socket/tcpsocket/shared/new.rb
index 5280eb790080f8..0e405253c84324 100644
--- a/spec/ruby/library/socket/tcpsocket/shared/new.rb
+++ b/spec/ruby/library/socket/tcpsocket/shared/new.rb
@@ -53,14 +53,23 @@
end
it "connects to a server when passed local_host and local_port arguments" do
- server = TCPServer.new(SocketSpecs.hostname, 0)
+ retries = 0
+ max_retries = 3
+
begin
- available_port = server.addr[1]
- ensure
- server.close
+ retries += 1
+ server = TCPServer.new(SocketSpecs.hostname, 0)
+ begin
+ available_port = server.addr[1]
+ ensure
+ server.close
+ end
+ @socket = TCPSocket.send(@method, @hostname, @server.port,
+ @hostname, available_port)
+ rescue Errno::EADDRINUSE
+ raise if retries >= max_retries
+ retry
end
- @socket = TCPSocket.send(@method, @hostname, @server.port,
- @hostname, available_port)
@socket.should be_an_instance_of(TCPSocket)
end
diff --git a/string.c b/string.c
index c8233be66fd06a..6f4ea03fb37a41 100644
--- a/string.c
+++ b/string.c
@@ -5011,12 +5011,15 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str)
/*
* call-seq:
- * self =~ object -> integer or nil
+ * self =~ other -> integer or nil
*
- * When +object+ is a Regexp, returns the index of the first substring in +self+
- * matched by +object+,
- * or +nil+ if no match is found;
- * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]:
+ * When +other+ is a Regexp:
+ *
+ * - Returns the integer index (in characters) of the first match
+ * for +self+ and +other+, or +nil+ if none;
+ * - Updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables].
+ *
+ * Examples:
*
* 'foo' =~ /f/ # => 0
* $~ # => #
@@ -5034,8 +5037,8 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str)
* /(?\d+)/ =~ 'no. 9' # => 4
* number # => "9" # Assigned.
*
- * If +object+ is not a Regexp, returns the value
- * returned by object =~ self.
+ * When +other+ is not a Regexp, returns the value
+ * returned by other =~ self.
*
* Related: see {Querying}[rdoc-ref:String@Querying].
*/
@@ -5713,8 +5716,8 @@ rb_str_aref(VALUE str, VALUE indx)
/*
* call-seq:
- * self[index] -> new_string or nil
- * self[start, length] -> new_string or nil
+ * self[offset] -> new_string or nil
+ * self[offset, size] -> new_string or nil
* self[range] -> new_string or nil
* self[regexp, capture = 0] -> new_string or nil
* self[substring] -> new_string or nil
@@ -12445,9 +12448,9 @@ sym_casecmp_p(VALUE sym, VALUE other)
/*
* call-seq:
- * symbol =~ object -> integer or nil
+ * self =~ other -> integer or nil
*
- * Equivalent to symbol.to_s =~ object,
+ * Equivalent to self.to_s =~ other,
* including possible updates to global variables;
* see String#=~.
*
@@ -12493,11 +12496,11 @@ sym_match_m_p(int argc, VALUE *argv, VALUE sym)
/*
* call-seq:
- * symbol[index] -> string or nil
- * symbol[start, length] -> string or nil
- * symbol[range] -> string or nil
- * symbol[regexp, capture = 0] -> string or nil
- * symbol[substring] -> string or nil
+ * self[offset] -> string or nil
+ * self[offset, size] -> string or nil
+ * self[range] -> string or nil
+ * self[regexp, capture = 0] -> string or nil
+ * self[substring] -> string or nil
*
* Equivalent to symbol.to_s[]; see String#[].
*
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index 94a2e03940bf5b..585e691765c66a 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -1260,8 +1260,6 @@ def test_fluent_dot
end
def test_fluent_and
- omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION
-
assert_valid_syntax("a\n" "&& foo")
assert_valid_syntax("a\n" "and foo")
@@ -1285,8 +1283,6 @@ def test_fluent_and
end
def test_fluent_or
- omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION
-
assert_valid_syntax("a\n" "|| foo")
assert_valid_syntax("a\n" "or foo")
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 805ecb98b20ff3..43db676d5d1cd3 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -1646,6 +1646,115 @@ def test(x) = [1,2,3][x]
}, call_threshold: 2, insns: [:opt_aref]
end
+ def test_array_fixnum_aset
+ assert_compiles '[1, 2, 7]', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ arr = [1,2,3]
+ test(arr, 2)
+ arr = [1,2,3]
+ test(arr, 2)
+ arr
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_fixnum_aset_returns_value
+ assert_compiles '7', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ test([1,2,3], 2)
+ test([1,2,3], 2)
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_fixnum_aset_out_of_bounds
+ assert_compiles '[1, 2, 3, nil, nil, 7]', %q{
+ def test(arr)
+ arr[5] = 7
+ end
+ arr = [1,2,3]
+ test(arr)
+ arr = [1,2,3]
+ test(arr)
+ arr
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_negative_index
+ assert_compiles '[1, 2, 7]', %q{
+ def test(arr)
+ arr[-1] = 7
+ end
+ arr = [1,2,3]
+ test(arr)
+ arr = [1,2,3]
+ test(arr)
+ arr
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_shared
+ assert_compiles '[10, 999, -1, -2]', %q{
+ def test(arr, idx, val)
+ arr[idx] = val
+ end
+ arr = (0..50).to_a
+ test(arr, 0, -1)
+ test(arr, 1, -2)
+ shared = arr[10, 20]
+ test(shared, 0, 999)
+ [arr[10], shared[0], arr[0], arr[1]]
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_frozen
+ assert_compiles 'FrozenError', %q{
+ def test(arr, idx, val)
+ arr[idx] = val
+ end
+ arr = [1,2,3]
+ test(arr, 1, 9)
+ test(arr, 1, 9)
+ arr.freeze
+ begin
+ test(arr, 1, 9)
+ rescue => e
+ e.class
+ end
+ }, call_threshold: 2
+ end
+
+ def test_array_fixnum_aset_array_subclass
+ assert_compiles '7', %q{
+ class MyArray < Array; end
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ arr = MyArray.new
+ test(arr, 0)
+ arr = MyArray.new
+ test(arr, 0)
+ arr[0]
+ }, call_threshold: 2, insns: [:opt_aset]
+ end
+
+ def test_array_aset_non_fixnum_index
+ assert_compiles 'TypeError', %q{
+ def test(arr, idx)
+ arr[idx] = 7
+ end
+ test([1,2,3], 0)
+ test([1,2,3], 0)
+ begin
+ test([1,2,3], "0")
+ rescue => e
+ e.class
+ end
+ }, call_threshold: 2
+ end
+
def test_empty_array_pop
assert_compiles 'nil', %q{
def test(arr) = arr.pop
diff --git a/test/test_bundled_gems.rb b/test/test_bundled_gems.rb
index 19546dd29606af..6e25df9b01f82d 100644
--- a/test/test_bundled_gems.rb
+++ b/test/test_bundled_gems.rb
@@ -32,4 +32,17 @@ def test_warning_archdir
assert Gem::BUNDLED_GEMS.warning?(path, specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?(path, specs: {})
end
+
+ def test_no_warning_for_hyphenated_gem
+ # When benchmark-ips gem is in specs, requiring "benchmark/ips" should not warn
+ # about the benchmark gem (Bug #21828)
+ assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {"benchmark-ips" => true})
+ end
+
+ def test_warning_without_hyphenated_gem
+ # When benchmark-ips is NOT in specs, requiring "benchmark/ips" should warn
+ warning = Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {})
+ assert warning
+ assert_match(/benchmark/, warning)
+ end
end
diff --git a/win32/configure.bat b/win32/configure.bat
index 8f767ede73256a..181813f4ad1581 100755
--- a/win32/configure.bat
+++ b/win32/configure.bat
@@ -208,7 +208,7 @@ goto :loop ;
shift
goto :loop ;
:baseruby
- echo>> %config_make% HAVE_BASERUBY =
+ echo>> %config_make% HAVE_BASERUBY = yes
echo>> %config_make% BASERUBY = %~2
echo>>%confargs% %1=%2 \
shift
diff --git a/win32/setup.mak b/win32/setup.mak
index b06081cab99d98..de8db870ba69eb 100644
--- a/win32/setup.mak
+++ b/win32/setup.mak
@@ -24,6 +24,9 @@ MAKEFILE = Makefile
CPU = PROCESSOR_LEVEL
CC = $(CC) -nologo -source-charset:utf-8
CPP = $(CC) -EP
+!if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" == ""
+BASERUBY = ruby
+!endif
all: -prologue- -generic- -epilogue-
i386-mswin32: -prologue- -i386- -epilogue-
@@ -46,8 +49,8 @@ prefix = $(prefix:\=/)
<<
@type $(config_make) >>$(MAKEFILE)
@del $(config_make) > nul
-!if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" != ""
- $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" --verbose
+!if "$(HAVE_BASERUBY)" != "no"
+ @$(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" --verbose $(HAVE_BASERUBY:yes=|| exit )|| exit 0
!endif
!if "$(WITH_GMP)" != "no"
@($(CC) $(XINCFLAGS) < nul && (echo USE_GMP = yes) || exit /b 0) >>$(MAKEFILE)
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index b05c0110909e62..c728df7255c8ba 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -375,6 +375,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(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::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)),
+ Insn::ArrayAset { array, index, val } => {
+ no_output!(gen_array_aset(asm, opnd!(array), opnd!(index), opnd!(val)))
+ }
Insn::ArrayPop { array, state } => gen_array_pop(asm, opnd!(array), &function.frame_state(*state)),
Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)),
Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)),
@@ -449,6 +452,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)),
&Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))),
Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)),
+ Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)),
&Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
&Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
@@ -692,6 +696,15 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state:
recv
}
+fn gen_guard_not_shared(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd {
+ let recv = asm.load(recv);
+ // It's a heap object, so check the shared flag
+ let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS));
+ asm.test(flags, (RUBY_ELTS_SHARED as u64).into());
+ asm.jnz(side_exit(jit, state, SideExitReason::GuardNotShared));
+ recv
+}
+
fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd {
asm.cmp(left, right);
asm.jge(side_exit(jit, state, SideExitReason::GuardLess));
@@ -1529,6 +1542,20 @@ fn gen_aref_fixnum(
asm_ccall!(asm, rb_ary_entry, array, unboxed_idx)
}
+fn gen_array_aset(
+ asm: &mut Assembler,
+ array: Opnd,
+ index: Opnd,
+ val: Opnd,
+) {
+ let unboxed_idx = asm.load(index);
+ let array = asm.load(array);
+ let array_ptr = gen_array_ptr(asm, array);
+ let elem_offset = asm.lshift(unboxed_idx, Opnd::UImm(SIZEOF_VALUE.trailing_zeros() as u64));
+ let elem_ptr = asm.add(array_ptr, elem_offset);
+ asm.store(Opnd::mem(VALUE_BITS, elem_ptr, 0), val);
+}
+
fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::Opnd {
gen_prepare_leaf_call_with_gc(asm, state);
asm_ccall!(asm, rb_ary_pop, array)
@@ -1545,6 +1572,14 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd {
asm.csel_nz(embedded_len, heap_len)
}
+fn gen_array_ptr(asm: &mut Assembler, array: Opnd) -> lir::Opnd {
+ let flags = Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RBASIC_FLAGS);
+ asm.test(flags, (RARRAY_EMBED_FLAG as u64).into());
+ let heap_ptr = Opnd::mem(usize::BITS as u8, array, RUBY_OFFSET_RARRAY_AS_HEAP_PTR);
+ let embedded_ptr = asm.lea(Opnd::mem(VALUE_BITS, array, RUBY_OFFSET_RARRAY_AS_ARY));
+ asm.csel_nz(embedded_ptr, heap_ptr)
+}
+
/// Compile opt_newarray_hash - create a hash from array elements
fn gen_opt_newarray_hash(
jit: &JITState,
diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs
index 60060f149c850d..4aa9068cb17c16 100644
--- a/zjit/src/cruby_methods.rs
+++ b/zjit/src/cruby_methods.rs
@@ -225,6 +225,7 @@ pub fn init() -> Annotations {
annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable);
annotate!(rb_cArray, "join", types::StringExact);
annotate!(rb_cArray, "[]", inline_array_aref);
+ annotate!(rb_cArray, "[]=", inline_array_aset);
annotate!(rb_cArray, "<<", inline_array_push);
annotate!(rb_cArray, "push", inline_array_push);
annotate!(rb_cArray, "pop", inline_array_pop);
@@ -332,6 +333,31 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
None
}
+fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option {
+ if let &[index, val] = args {
+ if fun.likely_a(recv, types::ArrayExact, state)
+ && fun.likely_a(index, types::Fixnum, state)
+ {
+ let recv = fun.coerce_to(block, recv, types::ArrayExact, state);
+ let index = fun.coerce_to(block, index, types::Fixnum, state);
+ let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state });
+ let recv = fun.push_insn(block, hir::Insn::GuardNotShared { recv, state });
+
+ // Bounds check: unbox Fixnum index and guard 0 <= idx < length.
+ let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
+ let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv });
+ let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, state });
+ let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) });
+ let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, state });
+
+ let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val });
+ fun.push_insn(block, hir::Insn::WriteBarrier { recv, val });
+ return Some(val);
+ }
+ }
+ None
+}
+
fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option {
// Inline only the case of `<<` or `push` when called with a single argument.
if let &[val] = args {
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 3f1ed83e4bd9b3..2ea6e94c960b6a 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -493,6 +493,7 @@ pub enum SideExitReason {
GuardShape(ShapeId),
GuardBitEquals(Const),
GuardNotFrozen,
+ GuardNotShared,
GuardLess,
GuardGreaterEq,
PatchPoint(Invariant),
@@ -580,6 +581,7 @@ impl std::fmt::Display for SideExitReason {
SideExitReason::GuardType(guard_type) => write!(f, "GuardType({guard_type})"),
SideExitReason::GuardTypeNot(guard_type) => write!(f, "GuardTypeNot({guard_type})"),
SideExitReason::GuardBitEquals(value) => write!(f, "GuardBitEquals({})", value.print(&PtrPrintMap::identity())),
+ SideExitReason::GuardNotShared => write!(f, "GuardNotShared"),
SideExitReason::PatchPoint(invariant) => write!(f, "PatchPoint({invariant})"),
_ => write!(f, "{self:?}"),
}
@@ -728,6 +730,7 @@ pub enum Insn {
/// Push `val` onto `array`, where `array` is already `Array`.
ArrayPush { array: InsnId, val: InsnId, state: InsnId },
ArrayArefFixnum { array: InsnId, index: InsnId },
+ ArrayAset { array: InsnId, index: InsnId, val: InsnId },
ArrayPop { array: InsnId, state: InsnId },
/// Return the length of the array as a C `long` ([`types::CInt64`])
ArrayLength { array: InsnId },
@@ -960,6 +963,9 @@ pub enum Insn {
/// Side-exit if val is frozen. Does *not* check if the val is an immediate; assumes that it is
/// a heap object.
GuardNotFrozen { recv: InsnId, state: InsnId },
+ /// Side-exit if val is shared. Does *not* check if the val is an immediate; assumes
+ /// that it is a heap object.
+ GuardNotShared { recv: InsnId, state: InsnId },
/// Side-exit if left is not greater than or equal to right (both operands are C long).
GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId },
/// Side-exit if left is not less than right (both operands are C long).
@@ -992,8 +998,9 @@ impl Insn {
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
- | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. }
- | Insn::HashAset { .. } => false,
+ | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. }
+ | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. }
+ | Insn::ArrayAset { .. } => false,
_ => true,
}
}
@@ -1121,6 +1128,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::ArrayArefFixnum { array, index, .. } => {
write!(f, "ArrayArefFixnum {array}, {index}")
}
+ Insn::ArrayAset { array, index, val, ..} => {
+ write!(f, "ArrayAset {array}, {index}, {val}")
+ }
Insn::ArrayPop { array, .. } => {
write!(f, "ArrayPop {array}")
}
@@ -1332,6 +1342,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
&Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) },
Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"),
Insn::GuardNotFrozen { recv, .. } => write!(f, "GuardNotFrozen {recv}"),
+ Insn::GuardNotShared { recv, .. } => write!(f, "GuardNotShared {recv}"),
Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"),
Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"),
Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
@@ -1968,6 +1979,7 @@ impl Function {
&GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state },
&GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) },
&GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state },
+ &GuardNotShared { recv, state } => GuardNotShared { recv: find!(recv), state },
&GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state },
&GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state },
&FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state },
@@ -2071,6 +2083,7 @@ impl Function {
&NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) },
&NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) },
&ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) },
+ &ArrayAset { array, index, val } => ArrayAset { array: find!(array), index: find!(index), val: find!(val) },
&ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) },
&ArrayLength { array } => ArrayLength { array: find!(array) },
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
@@ -2143,7 +2156,7 @@ impl Function {
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_)
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. }
- | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } =>
+ | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } | Insn::ArrayAset { .. } =>
panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]),
Insn::Const { val: Const::Value(val) } => Type::from_value(*val),
Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val),
@@ -2197,7 +2210,7 @@ impl Function {
Insn::GuardTypeNot { .. } => types::BasicObject,
Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)),
Insn::GuardShape { val, .. } => self.type_of(*val),
- Insn::GuardNotFrozen { recv, .. } => self.type_of(*recv),
+ Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => self.type_of(*recv),
Insn::GuardLess { left, .. } => self.type_of(*left),
Insn::GuardGreaterEq { left, .. } => self.type_of(*left),
Insn::FixnumAdd { .. } => types::Fixnum,
@@ -3940,6 +3953,7 @@ impl Function {
| &Insn::GuardBitEquals { val, state, .. }
| &Insn::GuardShape { val, state, .. }
| &Insn::GuardNotFrozen { recv: val, state }
+ | &Insn::GuardNotShared { recv: val, state }
| &Insn::ToArray { val, state }
| &Insn::IsMethodCfunc { val, state, .. }
| &Insn::ToNewArray { val, state }
@@ -4004,6 +4018,11 @@ impl Function {
worklist.push_back(array);
worklist.push_back(index);
}
+ &Insn::ArrayAset { array, index, val } => {
+ worklist.push_back(array);
+ worklist.push_back(index);
+ worklist.push_back(val);
+ }
&Insn::ArrayPop { array, state } => {
worklist.push_back(array);
worklist.push_back(state);
@@ -4628,7 +4647,7 @@ impl Function {
| Insn::DefinedIvar { self_val: val, .. } => {
self.assert_subtype(insn_id, val, types::BasicObject)
}
- Insn::GuardNotFrozen { recv, .. } => {
+ Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => {
self.assert_subtype(insn_id, recv, types::HeapBasicObject)
}
// Instructions with 2 Ruby object operands
@@ -4716,6 +4735,10 @@ impl Function {
self.assert_subtype(insn_id, array, types::Array)?;
self.assert_subtype(insn_id, index, types::Fixnum)
}
+ Insn::ArrayAset { array, index, .. } => {
+ self.assert_subtype(insn_id, array, types::ArrayExact)?;
+ self.assert_subtype(insn_id, index, types::CInt64)
+ }
// Instructions with Hash operands
Insn::HashAref { hash, .. }
| Insn::HashAset { hash, .. } => self.assert_subtype(insn_id, hash, types::HashExact),
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 2a70314fbb7f38..afa97e48f1e4dc 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -4772,6 +4772,36 @@ mod hir_opt_tests {
");
}
+ #[test]
+ fn test_dont_optimize_array_aset_if_redefined() {
+ eval(r##"
+ class Array
+ def []=(*args); :redefined; end
+ end
+
+ def test(arr)
+ arr[1] = 10
+ end
+ "##);
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:7:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal :arr, l0, SP@4
+ Jump bb2(v1, v2)
+ bb1(v5:BasicObject, v6:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v5, v6)
+ bb2(v8:BasicObject, v9:BasicObject):
+ v16:Fixnum[1] = Const Value(1)
+ v18:Fixnum[10] = Const Value(10)
+ v22:BasicObject = SendWithoutBlock v9, :[]=, v16, v18 # SendFallbackReason: Uncategorized(opt_aset)
+ CheckInterrupts
+ Return v18
+ ");
+ }
+
#[test]
fn test_dont_optimize_array_max_if_redefined() {
eval(r##"
@@ -6901,7 +6931,7 @@ mod hir_opt_tests {
}
#[test]
- fn test_optimize_array_aset() {
+ fn test_optimize_array_aset_literal() {
eval("
def test(arr)
arr[1] = 10
@@ -6924,12 +6954,93 @@ mod hir_opt_tests {
PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010)
PatchPoint NoSingletonClass(Array@0x1000)
v31:ArrayExact = GuardType v9, ArrayExact
- v32:BasicObject = CCallVariadic v31, :Array#[]=@0x1038, v16, v18
+ v32:ArrayExact = GuardNotFrozen v31
+ v33:ArrayExact = GuardNotShared v32
+ v34:CInt64 = UnboxFixnum v16
+ v35:CInt64 = ArrayLength v33
+ v36:CInt64 = GuardLess v34, v35
+ v37:CInt64[0] = Const CInt64(0)
+ v38:CInt64 = GuardGreaterEq v36, v37
+ ArrayAset v33, v38, v18
+ WriteBarrier v33, v18
+ IncrCounter inline_cfunc_optimized_send_count
CheckInterrupts
Return v18
");
}
+ #[test]
+ fn test_optimize_array_aset_profiled() {
+ eval("
+ def test(arr, index, val)
+ arr[index] = val
+ end
+ test([], 0, 1)
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal :arr, l0, SP@6
+ v3:BasicObject = GetLocal :index, l0, SP@5
+ v4:BasicObject = GetLocal :val, l0, SP@4
+ Jump bb2(v1, v2, v3, v4)
+ bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v7, v8, v9, v10)
+ bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject):
+ PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Array@0x1000)
+ v35:ArrayExact = GuardType v13, ArrayExact
+ v36:Fixnum = GuardType v14, Fixnum
+ v37:ArrayExact = GuardNotFrozen v35
+ v38:ArrayExact = GuardNotShared v37
+ v39:CInt64 = UnboxFixnum v36
+ v40:CInt64 = ArrayLength v38
+ v41:CInt64 = GuardLess v39, v40
+ v42:CInt64[0] = Const CInt64(0)
+ v43:CInt64 = GuardGreaterEq v41, v42
+ ArrayAset v38, v43, v15
+ WriteBarrier v38, v15
+ IncrCounter inline_cfunc_optimized_send_count
+ CheckInterrupts
+ Return v15
+ ");
+ }
+
+ #[test]
+ fn test_optimize_array_aset_array_subclass() {
+ eval("
+ class MyArray < Array; end
+ def test(arr, index, val)
+ arr[index] = val
+ end
+ a = MyArray.new
+ test(a, 0, 1)
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:4:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal :arr, l0, SP@6
+ v3:BasicObject = GetLocal :index, l0, SP@5
+ v4:BasicObject = GetLocal :val, l0, SP@4
+ Jump bb2(v1, v2, v3, v4)
+ bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v7, v8, v9, v10)
+ bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject):
+ PatchPoint MethodRedefined(MyArray@0x1000, []=@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(MyArray@0x1000)
+ v35:ArraySubclass[class_exact:MyArray] = GuardType v13, ArraySubclass[class_exact:MyArray]
+ v36:BasicObject = CCallVariadic v35, :Array#[]=@0x1038, v14, v15
+ CheckInterrupts
+ Return v15
+ ");
+ }
+
#[test]
fn test_optimize_array_ltlt() {
eval("
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 38e8df170d33fd..68eeac456b38a2 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -191,6 +191,7 @@ make_counters! {
exit_guard_int_equals_failure,
exit_guard_shape_failure,
exit_guard_not_frozen_failure,
+ exit_guard_not_shared_failure,
exit_guard_less_failure,
exit_guard_greater_eq_failure,
exit_patchpoint_bop_redefined,
@@ -511,6 +512,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
GuardBitEquals(_) => exit_guard_bit_equals_failure,
GuardShape(_) => exit_guard_shape_failure,
GuardNotFrozen => exit_guard_not_frozen_failure,
+ GuardNotShared => exit_guard_not_shared_failure,
GuardLess => exit_guard_less_failure,
GuardGreaterEq => exit_guard_greater_eq_failure,
CalleeSideExit => exit_callee_side_exit,