summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md16
-rw-r--r--doc/string/insert.rdoc16
-rw-r--r--gc.c2
-rw-r--r--proc.c3
-rw-r--r--ractor.c8
-rw-r--r--shape.c1
-rw-r--r--shape.h1
-rw-r--r--spec/ruby/core/module/ruby2_keywords_spec.rb17
-rw-r--r--spec/ruby/core/proc/ruby2_keywords_spec.rb14
-rw-r--r--string.c14
-rw-r--r--test/ruby/test_keyword.rb32
-rw-r--r--test/ruby/test_ractor.rb7
-rw-r--r--test/ruby/test_shapes.rb6
-rw-r--r--test/ruby/test_zjit.rb24
-rw-r--r--thread_pthread_mn.c10
-rw-r--r--vm_insnhelper.c23
-rw-r--r--vm_method.c25
-rw-r--r--yjit/not_gmake.mk16
-rw-r--r--zjit.c1
-rw-r--r--zjit/bindgen/src/main.rs1
-rw-r--r--zjit/src/backend/arm64/mod.rs89
-rw-r--r--zjit/src/backend/lir.rs24
-rw-r--r--zjit/src/backend/x86_64/mod.rs12
-rw-r--r--zjit/src/codegen.rs67
-rw-r--r--zjit/src/cruby.rs1
-rw-r--r--zjit/src/cruby_bindings.inc.rs1
26 files changed, 319 insertions, 112 deletions
diff --git a/NEWS.md b/NEWS.md
index 964bafacd6..b3d04feacf 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -243,6 +243,22 @@ The following bundled gems are updated.
## JIT
+* YJIT
+ * YJIT stats
+ * `ratio_in_yjit` no longer works in the default build.
+ Use `--enable-yjit=stats` on `configure` to enable it on `--yjit-stats`.
+ * Add `invalidate_everything` to default stats, which is
+ incremented when every code is invalidated by TracePoint.
+ * Add `mem_size:` and `call_threshold:` options to `RubyVM::YJIT.enable`.
+* ZJIT
+ * Add an experimental method-based JIT compiler.
+ Use `--enable-zjit` on `configure` to enable the `--zjit` support.
+ * As of Ruby 3.5.0-preview2, ZJIT is not yet ready for speeding up most benchmarks.
+ Please refrain from evaluating ZJIT just yet. Stay tuned for the Ruby 3.5 release.
+* RJIT
+ * `--rjit` is removed. We will move the implementation of the third-party JIT API
+ to the [ruby/rjit](https://github.com/ruby/rjit) repository.
+
[Feature #17473]: https://bugs.ruby-lang.org/issues/17473
[Feature #18455]: https://bugs.ruby-lang.org/issues/18455
[Feature #19908]: https://bugs.ruby-lang.org/issues/19908
diff --git a/doc/string/insert.rdoc b/doc/string/insert.rdoc
new file mode 100644
index 0000000000..d8252d5ec5
--- /dev/null
+++ b/doc/string/insert.rdoc
@@ -0,0 +1,16 @@
+Inserts the given +other_string+ into +self+; returns +self+.
+
+If the given +index+ is non-negative, inserts +other_string+ at offset +index+:
+
+ 'foo'.insert(0, 'bar') # => "barfoo"
+ 'foo'.insert(1, 'bar') # => "fbaroo"
+ 'foo'.insert(3, 'bar') # => "foobar"
+ 'тест'.insert(2, 'bar') # => "теbarст" # Characters, not bytes.
+ 'こんにちは'.insert(2, 'bar') # => "こんbarにちは"
+
+If the +index+ is negative, counts backward from the end of +self+
+and inserts +other_string+ _after_ the offset:
+
+ 'foo'.insert(-2, 'bar') # => "fobaro"
+
+Related: see {Modifying}[rdoc-ref:String@Modifying].
diff --git a/gc.c b/gc.c
index c2fc681253..686e727521 100644
--- a/gc.c
+++ b/gc.c
@@ -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)
{
diff --git a/proc.c b/proc.c
index 554ec5e237..ae1068e24f 100644
--- a/proc.c
+++ b/proc.c
@@ -4012,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:
diff --git a/ractor.c b/ractor.c
index 096bda5df6..c4d748e69c 100644
--- a/ractor.c
+++ b/ractor.c
@@ -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)
{
diff --git a/shape.c b/shape.c
index d58f9f1d0c..4345654f84 100644
--- a/shape.c
+++ b/shape.c
@@ -1625,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)));
diff --git a/shape.h b/shape.h
index 2d13c9b762..8eb1d2c2d6 100644
--- a/shape.h
+++ b/shape.h
@@ -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
diff --git a/string.c b/string.c
index 0329d2845a..d873d93d8f 100644
--- a/string.c
+++ b/string.c
@@ -6056,19 +6056,9 @@ rb_str_aset_m(int argc, VALUE *argv, VALUE str)
/*
* call-seq:
- * insert(index, other_string) -> self
+ * insert(offset, other_string) -> self
*
- * Inserts the given +other_string+ into +self+; returns +self+.
- *
- * If the Integer +index+ is positive, inserts +other_string+ at offset +index+:
- *
- * 'foo'.insert(1, 'bar') # => "fbaroo"
- *
- * If the Integer +index+ is negative, counts backward from the end of +self+
- * and inserts +other_string+ at offset <tt>index+1</tt>
- * (that is, _after_ <tt>self[index]</tt>):
- *
- * 'foo'.insert(-2, 'bar') # => "fobaro"
+ * :include: doc/string/insert.rdoc
*
*/
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_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 aa488e0421..08a841d254 100644
--- a/test/ruby/test_shapes.rb
+++ b/test/ruby/test_shapes.rb
@@ -1051,8 +1051,10 @@ class TestShapes < Test::Unit::TestCase
# 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_zjit.rb b/test/ruby/test_zjit.rb
index b5b5a69847..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{
@@ -1927,6 +1943,7 @@ class TestZJIT < Test::Unit::TestCase
zjit: true,
stats: false,
debug: true,
+ allowed_iseqs: nil,
timeout: 1000,
pipe_fd:
)
@@ -1936,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
@@ -1955,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/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 241e4cacef..fb217ef43d 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -1822,6 +1822,25 @@ callable_method_entry_or_negative(VALUE klass, ID mid, VALUE *defined_class_ptr)
const rb_callable_method_entry_t *cme;
VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS);
+
+ /* Fast path: lock-free read from cache */
+ VALUE cc_tbl = RUBY_ATOMIC_VALUE_LOAD(RCLASS_WRITABLE_CC_TBL(klass));
+ if (cc_tbl) {
+ VALUE ccs_data;
+ if (rb_managed_id_table_lookup(cc_tbl, mid, &ccs_data)) {
+ struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_data;
+ VM_ASSERT(vm_ccs_p(ccs));
+
+ if (LIKELY(!METHOD_ENTRY_INVALIDATED(ccs->cme))) {
+ VM_ASSERT(ccs->cme->called_id == mid);
+ if (defined_class_ptr != NULL) *defined_class_ptr = ccs->cme->defined_class;
+ RB_DEBUG_COUNTER_INC(ccs_found);
+ return ccs->cme;
+ }
+ }
+ }
+
+ /* Slow path: need to lock and potentially populate cache */
RB_VM_LOCKING() {
cme = cached_callable_method_entry(klass, mid);
@@ -2959,13 +2978,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: {
@@ -2978,13 +2998,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/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/zjit.c b/zjit.c
index 09ab128ae3..54d9f7ed86 100644
--- a/zjit.c
+++ b/zjit.c
@@ -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 59b7f9737e..ac10341996 100644
--- a/zjit/bindgen/src/main.rs
+++ b/zjit/bindgen/src/main.rs
@@ -361,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 be5bda052d..1bb4cd024b 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -1202,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();
@@ -1214,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, .. } |
@@ -1380,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 {
@@ -1466,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(&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 db56db0927..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};
@@ -1005,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
@@ -1372,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) {
@@ -1472,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);
}
@@ -1501,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();
@@ -1564,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");
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 524b06b580..ee6d4d5e0e 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -724,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;