summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bootstraptest/test_yjit.rb11
-rw-r--r--yjit_codegen.c40
-rw-r--r--yjit_codegen.h2
-rw-r--r--yjit_core.c2
-rw-r--r--yjit_iface.h1
5 files changed, 53 insertions, 3 deletions
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 725eca5eb5..0ca293f59a 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -1,3 +1,14 @@
+# Make sure that optional param methods return the correct value
+assert_equal '1', %q{
+ def m(ary = [])
+ yield(ary)
+ end
+
+ # Warm the JIT with a 0 param call
+ 2.times { m { } }
+ m(1) { |v| v }
+}
+
# Test for topn
assert_equal 'array', %q{
def threequals(a)
diff --git a/yjit_codegen.c b/yjit_codegen.c
index 0ee795129a..7f12ab7b4f 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -318,12 +318,40 @@ yjit_side_exit(jitstate_t *jit, ctx_t *ctx)
return yjit_gen_exit(jit, ctx, ocb);
}
+// Generate a runtime guard that ensures the PC is at the start of the iseq,
+// otherwise take a side exit. This is to handle the situation of optional
+// parameters. When a function with optional parameters is called, the entry
+// PC for the method isn't necessarily 0, but we always generated code that
+// assumes the entry point is 0.
+static void
+yjit_pc_guard(const rb_iseq_t *iseq)
+{
+ RUBY_ASSERT(cb != NULL);
+
+ mov(cb, REG0, member_opnd(REG_CFP, rb_control_frame_t, pc));
+ mov(cb, REG1, const_ptr_opnd(iseq->body->iseq_encoded));
+ xor(cb, REG0, REG1);
+
+ // xor should impact ZF, so we can jz here
+ uint32_t pc_is_zero = cb_new_label(cb, "pc_is_zero");
+ jz_label(cb, pc_is_zero);
+
+ // We're not starting at the first PC, so we need to exit.
+ GEN_COUNTER_INC(cb, leave_start_pc_non_zero);
+ mov(cb, RAX, imm_opnd(Qundef));
+ ret(cb);
+
+ // PC should be at the beginning
+ cb_write_label(cb, pc_is_zero);
+ cb_link_labels(cb);
+}
+
/*
Compile an interpreter entry block to be inserted into an iseq
Returns `NULL` if compilation fails.
*/
uint8_t *
-yjit_entry_prologue(void)
+yjit_entry_prologue(const rb_iseq_t *iseq)
{
RUBY_ASSERT(cb != NULL);
@@ -352,6 +380,16 @@ yjit_entry_prologue(void)
mov(cb, REG0, const_ptr_opnd(leave_exit_code));
mov(cb, member_opnd(REG_CFP, rb_control_frame_t, jit_return), REG0);
+ // We're compiling iseqs that we *expect* to start at `insn_idx`. But in
+ // the case of optional parameters, the interpreter can set the pc to a
+ // different location depending on the optional parameters. If an iseq
+ // has optional parameters, we'll add a runtime check that the PC we've
+ // compiled for is the same PC that the interpreter wants us to run with.
+ // If they don't match, then we'll take a side exit.
+ if (iseq->body->param.flags.has_opt) {
+ yjit_pc_guard(iseq);
+ }
+
return code_ptr;
}
diff --git a/yjit_codegen.h b/yjit_codegen.h
index 683765319e..7041a5a0a5 100644
--- a/yjit_codegen.h
+++ b/yjit_codegen.h
@@ -41,7 +41,7 @@ typedef enum codegen_status {
// Code generation function signature
typedef codegen_status_t (*codegen_fn)(jitstate_t* jit, ctx_t* ctx);
-uint8_t* yjit_entry_prologue();
+uint8_t* yjit_entry_prologue(const rb_iseq_t* iseq);
void yjit_gen_block(block_t* block, rb_execution_context_t* ec);
diff --git a/yjit_core.c b/yjit_core.c
index 22527e24a6..988f034fc9 100644
--- a/yjit_core.c
+++ b/yjit_core.c
@@ -535,7 +535,7 @@ uint8_t* gen_entry_point(const rb_iseq_t *iseq, uint32_t insn_idx, rb_execution_
blockid_t blockid = { iseq, insn_idx };
// Write the interpreter entry prologue
- uint8_t* code_ptr = yjit_entry_prologue();
+ uint8_t* code_ptr = yjit_entry_prologue(iseq);
// Try to generate code for the entry block
block_t* block = gen_block_version(blockid, &DEFAULT_CTX, ec);
diff --git a/yjit_iface.h b/yjit_iface.h
index 4f63cdb974..d2fad40194 100644
--- a/yjit_iface.h
+++ b/yjit_iface.h
@@ -48,6 +48,7 @@ YJIT_DECLARE_COUNTERS(
leave_se_interrupt,
leave_interp_return,
+ leave_start_pc_non_zero,
getivar_se_self_not_heap,
getivar_idx_out_of_range,