summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <[email protected]>2024-01-23 12:09:57 -0800
committerGitHub <[email protected]>2024-01-23 20:09:57 +0000
commitc84237f9531aed3b204d3fdacc2dd9d2bd4c7d81 (patch)
tree924ec7260333bb75d8780e76325d5dd0303e0729
parent27c1dd8634d34bfe3592151d66b410f28ca749ce (diff)
Rewrite Array#each in Ruby using Primitive (#9533)
-rw-r--r--array.c59
-rw-r--r--array.rb55
-rw-r--r--benchmark/loop_each.yml4
-rw-r--r--builtin.h2
-rw-r--r--gems/bundled_gems2
-rw-r--r--test/ruby/test_process.rb1
-rw-r--r--test/ruby/test_settracefunc.rb6
-rw-r--r--tool/mk_builtin_loader.rb3
8 files changed, 82 insertions, 50 deletions
diff --git a/array.c b/array.c
index 93e0cc9be4..951de5297f 100644
--- a/array.c
+++ b/array.c
@@ -28,6 +28,7 @@
#include "ruby/encoding.h"
#include "ruby/st.h"
#include "ruby/util.h"
+#include "vm_core.h"
#include "builtin.h"
#if !ARRAY_DEBUG
@@ -2484,50 +2485,19 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj)
return rb_ary_length(ary);
}
-/*
- * call-seq:
- * array.each {|element| ... } -> self
- * array.each -> Enumerator
- *
- * Iterates over array elements.
- *
- * When a block given, passes each successive array element to the block;
- * returns +self+:
- *
- * a = [:foo, 'bar', 2]
- * a.each {|element| puts "#{element.class} #{element}" }
- *
- * Output:
- *
- * Symbol foo
- * String bar
- * Integer 2
- *
- * Allows the array to be modified during iteration:
- *
- * a = [:foo, 'bar', 2]
- * a.each {|element| puts element; a.clear if element.to_s.start_with?('b') }
- *
- * Output:
- *
- * foo
- * bar
- *
- * When no block given, returns a new Enumerator:
- * a = [:foo, 'bar', 2]
- *
- * e = a.each
- * e # => #<Enumerator: [:foo, "bar", 2]:each>
- * a1 = e.each {|element| puts "#{element.class} #{element}" }
- *
- * Output:
- *
- * Symbol foo
- * String bar
- * Integer 2
- *
- * Related: #each_index, #reverse_each.
- */
+// Primitive to avoid a race condition in Array#each.
+// Return `true` and write `value` and `index` if the element exists.
+static VALUE
+ary_fetch_next(VALUE self, VALUE *index, VALUE *value)
+{
+ long i = NUM2LONG(*index);
+ if (i >= RARRAY_LEN(self)) {
+ return Qfalse;
+ }
+ *value = RARRAY_AREF(self, i);
+ *index = LONG2NUM(i + 1);
+ return Qtrue;
+}
VALUE
rb_ary_each(VALUE ary)
@@ -8644,7 +8614,6 @@ Init_Array(void)
rb_define_method(rb_cArray, "unshift", rb_ary_unshift_m, -1);
rb_define_alias(rb_cArray, "prepend", "unshift");
rb_define_method(rb_cArray, "insert", rb_ary_insert, -1);
- rb_define_method(rb_cArray, "each", rb_ary_each, 0);
rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0);
rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0);
rb_define_method(rb_cArray, "length", rb_ary_length, 0);
diff --git a/array.rb b/array.rb
index d17f374235..e1ce6f49f9 100644
--- a/array.rb
+++ b/array.rb
@@ -1,5 +1,60 @@
class Array
# call-seq:
+ # array.each {|element| ... } -> self
+ # array.each -> Enumerator
+ #
+ # Iterates over array elements.
+ #
+ # When a block given, passes each successive array element to the block;
+ # returns +self+:
+ #
+ # a = [:foo, 'bar', 2]
+ # a.each {|element| puts "#{element.class} #{element}" }
+ #
+ # Output:
+ #
+ # Symbol foo
+ # String bar
+ # Integer 2
+ #
+ # Allows the array to be modified during iteration:
+ #
+ # a = [:foo, 'bar', 2]
+ # a.each {|element| puts element; a.clear if element.to_s.start_with?('b') }
+ #
+ # Output:
+ #
+ # foo
+ # bar
+ #
+ # When no block given, returns a new Enumerator:
+ # a = [:foo, 'bar', 2]
+ #
+ # e = a.each
+ # e # => #<Enumerator: [:foo, "bar", 2]:each>
+ # a1 = e.each {|element| puts "#{element.class} #{element}" }
+ #
+ # Output:
+ #
+ # Symbol foo
+ # String bar
+ # Integer 2
+ #
+ # Related: #each_index, #reverse_each.
+ def each
+ Primitive.attr! :inline_block
+ unless defined?(yield)
+ return to_enum(:each) { self.length }
+ end
+ _i = 0
+ value = nil
+ while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
+ yield value
+ end
+ self
+ end
+
+ # call-seq:
# array.shuffle!(random: Random) -> array
#
# Shuffles the elements of +self+ in place.
diff --git a/benchmark/loop_each.yml b/benchmark/loop_each.yml
new file mode 100644
index 0000000000..1c757185a8
--- /dev/null
+++ b/benchmark/loop_each.yml
@@ -0,0 +1,4 @@
+prelude: |
+ arr = [nil] * 30_000_000
+benchmark:
+ loop_each: arr.each{|e|}
diff --git a/builtin.h b/builtin.h
index 85fd1a009a..24aa7c2fdb 100644
--- a/builtin.h
+++ b/builtin.h
@@ -106,6 +106,8 @@ rb_vm_lvar(rb_execution_context_t *ec, int index)
#endif
}
+#define LOCAL_PTR(local) local ## __ptr
+
// dump/load
struct builtin_binary {
diff --git a/gems/bundled_gems b/gems/bundled_gems
index 4d9e6cbf94..8f2ff3f624 100644
--- a/gems/bundled_gems
+++ b/gems/bundled_gems
@@ -19,7 +19,7 @@ matrix 0.4.2 https://github.com/ruby/matrix
prime 0.1.2 https://github.com/ruby/prime
rbs 3.4.2 https://github.com/ruby/rbs
typeprof 0.21.9 https://github.com/ruby/typeprof
-debug 1.9.1 https://github.com/ruby/debug
+debug 1.9.1 https://github.com/ruby/debug 19b91b14ce814a0eb615abb8d2bef0594c61c5c8
racc 1.7.3 https://github.com/ruby/racc
mutex_m 0.2.0 https://github.com/ruby/mutex_m
getoptlong 0.2.1 https://github.com/ruby/getoptlong
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 3d20d6eff5..8fb3a9df0c 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -2841,6 +2841,7 @@ EOS
def test_low_memory_startup
omit "JIT enabled" if %w[YJIT RJIT].any? {|n| RubyVM.const_defined?(n) and RubyVM.const_get(n).enabled?}
+ omit "flaky on Travis arm32" if /armv8l-linux-eabihf/ =~ RUBY_PLATFORM
as = 1<<25
_, _, status = EnvUtil.invoke_ruby(%W'-W0', "", true, :merge_to_stdout, rlimit_as: as)
omit sprintf("Crashed with AS: %#x: %s", as, status) if status.signaled?
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index dbaf0aaf09..6c17f279d4 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -504,7 +504,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
3: }
- 4: [1].each{|;_local_var| _local_var = :inner
+ 4: [1].reverse_each{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
@@ -531,10 +531,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
answer_events = [
#
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 4, 'xyzzy', Array, :each, [1], nil, :nothing],
+ [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
- [:c_return, 4, "xyzzy", Array, :each, [1], nil, [1]],
+ [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
[:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
[:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
diff --git a/tool/mk_builtin_loader.rb b/tool/mk_builtin_loader.rb
index 989c8c5957..07c8291249 100644
--- a/tool/mk_builtin_loader.rb
+++ b/tool/mk_builtin_loader.rb
@@ -274,7 +274,8 @@ def generate_cexpr(ofile, lineno, line_file, body_lineno, text, locals, func_nam
locals&.reverse_each&.with_index{|param, i|
next unless Symbol === param
next unless local_candidates.include?(param.to_s)
- f.puts "MAYBE_UNUSED(const VALUE) #{param} = rb_vm_lvar(ec, #{-3 - i});"
+ f.puts "VALUE *const #{param}__ptr = (VALUE *)&ec->cfp->ep[#{-3 - i}];"
+ f.puts "MAYBE_UNUSED(const VALUE) #{param} = *#{param}__ptr;"
lineno += 1
}
f.puts "#line #{body_lineno} \"#{line_file}\""