diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index d6194825a445a7..ed559d64e10d05 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -158,7 +158,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: macos-14 diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 64bb0903f8ae7c..37a9000d704c5a 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -215,7 +215,7 @@ jobs: include: # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' - bench_opts: '--warmup=1 --bench=1 --excludes=fluentd,psych-load,railsbench' + bench_opts: '--warmup=1 --bench=1' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index fc592520e80b06..cf1605cf407ac3 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -5435,3 +5435,18 @@ def jit_caller = test_body("session_id") alias some_method binding # induce environment escape test_body(:symbol) } + +# regression test for missing check in identity method inlining +assert_normal_exit %q{ + # Use dead code (if false) to create a local + # without initialization instructions. + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test +} diff --git a/string.c b/string.c index 5b7169ab12ffaa..060a1467a06f55 100644 --- a/string.c +++ b/string.c @@ -10263,8 +10263,6 @@ rb_str_lstrip_bang(int argc, VALUE *argv, VALUE str) char *start, *s; long olen, loffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10324,8 +10322,6 @@ rb_str_lstrip(int argc, VALUE *argv, VALUE str) char *start; long len, loffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - RSTRING_GETMEM(str, start, len); if (argc > 0) { char table[TR_TABLE_SIZE]; @@ -10413,8 +10409,6 @@ rb_str_rstrip_bang(int argc, VALUE *argv, VALUE str) char *start; long olen, roffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10472,8 +10466,6 @@ rb_str_rstrip(int argc, VALUE *argv, VALUE str) char *start; long olen, roffset; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); if (argc > 0) { @@ -10510,8 +10502,6 @@ rb_str_strip_bang(int argc, VALUE *argv, VALUE str) long olen, loffset, roffset; rb_encoding *enc; - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); @@ -10577,8 +10567,6 @@ rb_str_strip(int argc, VALUE *argv, VALUE str) long olen, loffset, roffset; rb_encoding *enc = STR_ENC_GET(str); - rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); - RSTRING_GETMEM(str, start, olen); if (argc > 0) { diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index df7d2cf2234631..25804ad5e92a01 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -355,6 +355,21 @@ def test = foo(1) }, call_threshold: 2 end + def test_nonparam_local_nil_in_jit_call + # Non-parameter locals must be initialized to nil in JIT-to-JIT calls. + # Use dead code (if false) to create locals without initialization instructions. + # Then eval a string that accesses the uninitialized locals. + assert_compiles '["x", "x", "x", "x"]', %q{ + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + }, call_threshold: 2 + end + def test_setlocal_on_eval assert_compiles '1', %q{ @b = binding diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 4a53e8834c5fb7..35d1713ed83ae9 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7466,6 +7466,12 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, block: Opti let ep_offset = unsafe { *rb_iseq_pc_at_idx(iseq, 1) }.as_u32(); let local_idx = ep_offset_to_local_idx(iseq, ep_offset); + // Only inline getlocal on a parameter. DCE in the IESQ builder can + // make a two-instruction ISEQ that does not return a parameter. + if local_idx >= unsafe { get_iseq_body_param_size(iseq) } { + return None; + } + if unsafe { rb_simple_iseq_p(iseq) } { return Some(IseqReturn::LocalVariable(local_idx)); } else if unsafe { rb_iseq_only_kwparam_p(iseq) } { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 07f519db1c0e7d..46952dc12215a8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1391,6 +1391,16 @@ fn gen_send_without_block_direct( 0 }; + // Fill non-parameter locals with nil (they may be read by eval before being written) + let num_params = params.size.to_usize(); + if local_size > num_params { + asm_comment!(asm, "initialize non-parameter locals to nil"); + for local_idx in num_params..local_size { + let offset = local_size_and_idx_to_bp_offset(local_size, local_idx); + asm.store(Opnd::mem(64, SP, -offset * SIZEOF_VALUE_I32), Qnil.into()); + } + } + // Make a method call. The target address will be rewritten once compiled. let iseq_call = IseqCall::new(iseq, num_optionals_passed); let dummy_ptr = cb.get_write_ptr().raw_ptr(cb); @@ -2369,8 +2379,8 @@ c_callable! { let pc = unsafe { rb_iseq_pc_at_idx(iseq, entry_insn_idxs[iseq_call.jit_entry_idx.to_usize()]) }; unsafe { rb_set_cfp_pc(cfp, pc) }; - // 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. + // Successful JIT-to-JIT calls fill nils to non-parameter locals in generated code. + // If we side-exit from function_stub_hit (before JIT code runs), we need to set them here. fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, compile_error: &CompileError) { unsafe { // Set SP which gen_push_frame() doesn't set