[前][次][番号順一覧][スレッド一覧]

ruby-changes:70784

From: Alan <ko1@a...>
Date: Sat, 8 Jan 2022 09:29:23 +0900 (JST)
Subject: [ruby-changes:70784] 54c91042ed (master): YJIT: Discard local var type info on routine call

https://git.ruby-lang.org/ruby.git/commit/?id=54c91042ed

From 54c91042ed61a869d4a66fc089b21f56d165265f Mon Sep 17 00:00:00 2001
From: Alan Wu <XrXr@u...>
Date: Thu, 6 Jan 2022 21:57:43 -0500
Subject: YJIT: Discard local var type info on routine call

Routines that are called from YJIT's output code can call methods, and
calling methods mean they can capture and change the environment of the
calling frame.

Discard type info whenever we perform routine calls. This is more
conservative than strictly necessary as some routines need to perform GC
allocation but can never call methods and so should never be able to
change local variables. However, manually analyzing C functions for
whether they have code paths that call methods is error prone and can go
out of date as changes land in the codebase.

Closes: shopify/yjit#300
---
 bootstraptest/test_yjit.rb | 31 +++++++++++++++++++++++++++++++
 yjit_codegen.c             |  4 ++++
 2 files changed, 35 insertions(+)

diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 30298a820d6..47744efb735 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -1,3 +1,34 @@ https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_yjit.rb#L1
+assert_equal '2022', %q{
+ def contrivance(hash, key)
+    # Expect this to compile to an `opt_aref`.
+    hash[key]
+
+    # The [] call above tracks that the `hash` local has a VALUE that
+    # is a heap pointer and the guard for the Kernel#itself call below
+    # doesn't check that it's a heap pointer VALUE.
+    #
+    # As you can see from the crash, the call to rb_hash_aref() can set the
+    # `hash` local, making eliding the heap object guard unsound.
+    hash.itself
+  end
+
+  # This is similar to ->(recv, mid) { send(recv, mid).local_variable_set(...) }.
+  # By composing we avoid creating new Ruby frames and so sending :binding
+  # captures the environment of the frame that does the missing key lookup.
+  # We use it to capture the environment inside of `contrivance`.
+  cap_then_set =
+    Kernel.instance_method(:send).method(:bind_call).to_proc >>
+      ->(binding) { binding.local_variable_set(:hash, 2022) }
+  special_missing = Hash.new(&cap_then_set)
+
+  # Make YJIT speculate that it's a hash and generate code
+  # that calls rb_hash_aref().
+  contrivance({}, :warmup)
+  contrivance({}, :warmup)
+
+  contrivance(special_missing, :binding)
+}
+
 assert_equal '18374962167983112447', %q{
   # regression test for incorrectly discarding 32 bits of a pointer when it
   # comes to default values.
diff --git a/yjit_codegen.c b/yjit_codegen.c
index aa352dac700..25fcfca0830 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -184,6 +184,10 @@ jit_prepare_routine_call(jitstate_t *jit, ctx_t *ctx, x86opnd_t scratch_reg) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L184
     jit->record_boundary_patch_point = true;
     jit_save_pc(jit, scratch_reg);
     jit_save_sp(jit, ctx);
+
+    // In case the routine calls Ruby methods, it can set local variables
+    // through Kernel#binding and other means.
+    ctx_clear_local_types(ctx);
 }
 
 // Record the current codeblock write position for rewriting into a jump into
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]