diff options
| -rw-r--r-- | internal/re.h | 1 | ||||
| -rw-r--r-- | re.c | 8 | ||||
| -rw-r--r-- | test/ruby/test_box.rb | 36 | ||||
| -rw-r--r-- | vm_dump.c | 3 | ||||
| -rw-r--r-- | vm_method.c | 46 | ||||
| -rw-r--r-- | zjit/src/asm/x86_64/mod.rs | 3 | ||||
| -rw-r--r-- | zjit/src/backend/arm64/mod.rs | 19 | ||||
| -rw-r--r-- | zjit/src/backend/lir.rs | 45 | ||||
| -rw-r--r-- | zjit/src/backend/x86_64/mod.rs | 28 |
9 files changed, 131 insertions, 58 deletions
diff --git a/internal/re.h b/internal/re.h index 3ad364a1a6..6c0aee6d06 100644 --- a/internal/re.h +++ b/internal/re.h @@ -68,7 +68,6 @@ VALUE rb_reg_equal(VALUE re1, VALUE re2); VALUE rb_backref_set_string(VALUE string, long pos, long len); void rb_match_unbusy(VALUE); int rb_match_count(VALUE match); -VALUE rb_reg_new_ary(VALUE ary, int options); VALUE rb_reg_new_from_values(long cnt, const VALUE *elements, int opt); VALUE rb_reg_last_defined(VALUE match); @@ -3523,16 +3523,10 @@ rb_reg_init_str_enc(VALUE re, VALUE s, rb_encoding *enc, int options) } VALUE -rb_reg_new_ary(VALUE ary, int opt) -{ - return rb_reg_new_str(rb_reg_preprocess_dregexp(ary, opt), opt); -} - -VALUE rb_reg_new_from_values(long cnt, const VALUE *elements, int opt) { const VALUE ary = rb_ary_tmp_new_from_values(0, cnt, elements); - VALUE val = rb_reg_new_ary(ary, (int)opt); + VALUE val = rb_reg_new_str(rb_reg_preprocess_dregexp(ary, opt), opt); rb_ary_clear(ary); return val; } diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index a39979109f..a425c5eb7d 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -1180,4 +1180,40 @@ class TestBox < Test::Unit::TestCase assert_equal :box2, box2.eval("Class.new { include Math }.new.box2_test") end; end + + def test_method_invalidation_between_boxes_1 + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + b = Ruby::Box.new + b.eval(<<~'RUBY') + Module.prepend(Module.new) + class C; end + class D < C; end + def C.===(x) = true + RUBY + + assert String === "x" + assert b # to prevent GCing b + end; + end + + def test_method_invalidation_between_boxes_2 + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + PrepM = Module.new + Module.prepend(PrepM) + Module.new.include?(Module.new) + + b = Ruby::Box.new + b.eval(<<~'RUBY') + Module.class_eval { def _test_method; end } + + class C; end + class D < C; end + def C.include?(x) = true + RUBY + + Module.new.include?(Module.new) + end; + end end @@ -90,6 +90,9 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c break; case VM_FRAME_MAGIC_CFUNC: magic = "CFUNC"; + if (me) { + box = me->def->box; + } break; case VM_FRAME_MAGIC_IFUNC: magic = "IFUNC"; diff --git a/vm_method.c b/vm_method.c index 5a99f684c0..fb34426bf2 100644 --- a/vm_method.c +++ b/vm_method.c @@ -404,6 +404,30 @@ invalidate_callable_method_entry_in_every_m_table_i(rb_classext_t *ext, bool is_ } } +struct collect_per_box_origins_arg { + VALUE owner; + VALUE klass_housing_cme; + VALUE origins; // Array of origins +}; + +static void +collect_per_box_origins_i(rb_classext_t *ext, bool is_prime, VALUE box_value, void *data) +{ + struct collect_per_box_origins_arg *arg = (struct collect_per_box_origins_arg *)data; + VALUE origin = RCLASSEXT_ORIGIN(ext); + + if (origin == arg->owner || origin == arg->klass_housing_cme) { + return; + } + long len = RARRAY_LEN(arg->origins); + for (long i = 0; i < len; i++) { + if (RARRAY_AREF(arg->origins, i) == origin) { + return; + } + } + rb_ary_push(arg->origins, origin); +} + static void invalidate_callable_method_entry_in_every_m_table(VALUE klass, ID mid, const rb_callable_method_entry_t *cme) { @@ -487,6 +511,28 @@ clear_method_cache_by_id_in_class(VALUE klass, ID mid) // replace the cme that will be invalid in the all classexts invalidate_callable_method_entry_in_every_m_table(klass_housing_cme, mid, cme); + // owner may be a boxable class with per-box classext copies of its m_tbl + // (klass_housing_cme may be a non-boxable origin ICLASS that doesn't cover them) + if (klass_housing_cme != owner) { + invalidate_callable_method_entry_in_every_m_table(owner, mid, cme); + } + // Also update per-box origin ICLASSes. When ensure_origin is called in + // one box's context, it creates a per-box origin ICLASS whose m_tbl is + // a copy of owner's m_tbl at that time. The current execution box may + // not see these origins via RCLASS_ORIGIN(owner), so we find them by + // iterating all of owner's classexts and checking their origin_ fields. + { + VALUE origins = rb_ary_new(); + struct collect_per_box_origins_arg origins_arg = { + .owner = owner, + .klass_housing_cme = klass_housing_cme, + .origins = origins, + }; + rb_class_classext_foreach(owner, collect_per_box_origins_i, &origins_arg); + for (long i = 0; i < RARRAY_LEN(origins); i++) { + invalidate_callable_method_entry_in_every_m_table(RARRAY_AREF(origins, i), mid, cme); + } + } } vm_cme_invalidate((rb_callable_method_entry_t *)cme); diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index 628a83b99c..c2733e783f 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -1145,6 +1145,9 @@ pub fn push(cb: &mut CodeBlock, opnd: X86Opnd) { X86Opnd::Mem(_mem) => { write_rm(cb, false, false, X86Opnd::None, opnd, Some(6), &[0xff]); }, + X86Opnd::Imm(X86Imm { value: 0, .. }) | X86Opnd::UImm(X86UImm { value: 0, .. }) => { + cb.write_bytes(&[0x6a, 0x00]); + } _ => unreachable!() } } diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 867b829ecc..abe439cb12 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1407,8 +1407,10 @@ impl Assembler { emit_push(cb, opnd.into()); }, Insn::CPushPair(opnd0, opnd1) => { + let first_push = if let Opnd::UImm(0) | Opnd::Imm(0) = opnd0 { X31 } else { opnd0.into() }; + let second_push = if let Opnd::UImm(0) | Opnd::Imm(0) = opnd1 { X31 } else { opnd1.into() }; // Second operand ends up at the lower stack address - stp_pre(cb, opnd1.into(), opnd0.into(), A64Opnd::new_mem(64, C_SP_REG, -C_SP_STEP)); + stp_pre(cb, second_push, first_push, A64Opnd::new_mem(64, C_SP_REG, -C_SP_STEP)); }, Insn::CPop { out } => { emit_pop(cb, out.into()); @@ -1417,8 +1419,15 @@ impl Assembler { emit_pop(cb, opnd.into()); }, Insn::CPopPairInto(opnd0, opnd1) => { + let mut first_pop = opnd0.into(); + let second_pop = opnd1.into(); + // Avoid illegal load pair into the same register + // by sinking the first pop to the zero register. + if first_pop == second_pop { + first_pop = X31; + } // First operand is popped from the lower stack address - ldp_post(cb, opnd0.into(), opnd1.into(), A64Opnd::new_mem(64, C_SP_REG, C_SP_STEP)); + ldp_post(cb, first_pop, second_pop, A64Opnd::new_mem(64, C_SP_REG, C_SP_STEP)); }, Insn::CCall { fptr, .. } => { match fptr { @@ -2792,17 +2801,17 @@ mod tests { 0x10: mov x4, #5 0x14: stp x1, x0, [sp, #-0x10]! 0x18: stp x3, x2, [sp, #-0x10]! - 0x1c: str x4, [sp, #-0x10]! + 0x1c: stp xzr, x4, [sp, #-0x10]! 0x20: mov x16, #0 0x24: blr x16 - 0x28: ldr x4, [sp], #0x10 + 0x28: ldp xzr, x4, [sp], #0x10 0x2c: ldp x3, x2, [sp], #0x10 0x30: ldp x1, x0, [sp], #0x10 0x34: adds x0, x0, x1 0x38: adds x0, x2, x3 0x3c: adds x0, x2, x4 "); - assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2a40080d2e103bfa9e30bbfa9e40f1ff8100080d200023fd6e40741f8e30bc1a8e103c1a8000001ab400003ab400004ab"); + assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2a40080d2e103bfa9e30bbfa9ff13bfa9100080d200023fd6ff13c1a8e30bc1a8e103c1a8000001ab400003ab400004ab"); } #[test] diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index a8d03ad69a..0ef79b215e 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2384,27 +2384,16 @@ impl Assembler _ => unreachable!(), }) .collect(); - let survivor_push_groups: Vec<Vec<Opnd>> = survivor_regs - .chunks(2) - .map(|group| group.to_vec()) - .collect(); // Push all survivors on the stack, pairing adjacent pushes when possible. - let needs_alignment = cfg!(target_arch = "x86_64") && survivors.len() % 2 == 1; - for group in &survivor_push_groups { - match group.as_slice() { - [left, right] => new_insns.push(Insn::CPushPair(*left, *right)), - [reg] => new_insns.push(Insn::CPush(*reg)), + for group in survivor_regs.chunks(2) { + match group { + &[left, right] => new_insns.push(Insn::CPushPair(left, right)), + &[reg] => new_insns.push(Insn::CPushPair(reg, 0.into())), _ => unreachable!(), } new_ids.push(None); } - // Maintain 16-byte stack alignment for x86_64 - if needs_alignment { - new_insns.push(Insn::CPush(Opnd::Reg(ALLOC_REGS[0]))); - new_ids.push(None); - } - // Extract arguments from CCall, clear opnds assert!(opnds.len() <= regs.len()); @@ -2472,17 +2461,11 @@ impl Assembler new_ids.push(None); } - // Pop alignment padding (if needed) - if needs_alignment { - new_insns.push(Insn::CPopInto(Opnd::Reg(ALLOC_REGS[0]))); - new_ids.push(None); - } - // Restore all survivors in reverse stack order, pairing adjacent pops when possible. - for group in survivor_push_groups.iter().rev() { - match group.as_slice() { - [left, right] => new_insns.push(Insn::CPopPairInto(*right, *left)), - [reg] => new_insns.push(Insn::CPopInto(*reg)), + for group in survivor_regs.chunks(2).rev() { + match group { + &[reg] => new_insns.push(Insn::CPopPairInto(reg, reg)), + &[left, right] => new_insns.push(Insn::CPopPairInto(right, left)), _ => unreachable!(), } new_ids.push(None); @@ -4673,8 +4656,8 @@ mod tests { let insns = &asm.basic_blocks[b1.0].insns; // Find CPush and CPopInto - they should be balanced. - let pushes: Vec<_> = insns.iter().filter(|i| matches!(i, Insn::CPush(_))).collect(); - let pops: Vec<_> = insns.iter().filter(|i| matches!(i, Insn::CPopInto(_))).collect(); + let pushes: Vec<_> = insns.iter().filter(|i| matches!(i, Insn::CPushPair(..))).collect(); + let pops: Vec<_> = insns.iter().filter(|i| matches!(i, Insn::CPopPairInto(..))).collect(); assert_eq!(pushes.len(), pops.len(), "CPush/CPopInto should be balanced"); assert!(!pushes.is_empty(), "Expected at least one saved register across CCall"); @@ -4684,10 +4667,10 @@ mod tests { Allocation::Fixed(reg) => Opnd::Reg(reg), _ => unreachable!(), }; - let pushed_v1 = pushes.iter().any(|insn| matches!(insn, Insn::CPush(opnd) if *opnd == v1_reg)); - let popped_v1 = pops.iter().any(|insn| matches!(insn, Insn::CPopInto(opnd) if *opnd == v1_reg)); - assert!(pushed_v1, "CPush should save v1's register"); - assert!(popped_v1, "CPopInto should restore v1's register"); + let pushed_v1 = pushes.iter().any(|insn| matches!(**insn, Insn::CPushPair(first, second) if first == v1_reg || second == v1_reg)); + let popped_v1 = pops.iter().any(|insn| matches!(**insn, Insn::CPopPairInto(first, second) if first == v1_reg || second == v1_reg)); + assert!(pushed_v1, "CPushPair should save v1's register"); + assert!(popped_v1, "CPopPairInto should restore v1's register"); // The CCall should have empty opnds and out = C_RET_OPND (rewritten to regs[0]) let ccall = insns.iter().find(|i| matches!(i, Insn::CCall { .. })).unwrap(); diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index d8d930dfce..80abd15a6b 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -1985,22 +1985,22 @@ mod tests { 0x1c: push rdx 0x1d: push rcx 0x1e: push r8 - 0x20: push rdi - 0x21: mov eax, 0 - 0x26: call rax - 0x28: pop rdi + 0x20: push 0 + 0x22: mov eax, 0 + 0x27: call rax 0x29: pop r8 - 0x2b: pop rcx - 0x2c: pop rdx - 0x2d: pop rsi - 0x2e: pop rdi - 0x2f: add rdi, rsi - 0x32: mov rdi, rdx - 0x35: add rdi, rcx - 0x38: mov rdi, rdx - 0x3b: add rdi, r8 + 0x2b: pop r8 + 0x2d: pop rcx + 0x2e: pop rdx + 0x2f: pop rsi + 0x30: pop rdi + 0x31: add rdi, rsi + 0x34: mov rdi, rdx + 0x37: add rdi, rcx + 0x3a: mov rdi, rdx + 0x3d: add rdi, r8 "); - assert_snapshot!(cb.hexdump(), @"bf01000000be02000000ba03000000b90400000041b80500000057565251415057b800000000ffd05f4158595a5e5f4801f74889d74801cf4889d74c01c7"); + assert_snapshot!(cb.hexdump(), @"bf01000000be02000000ba03000000b90400000041b8050000005756525141506a00b800000000ffd041584158595a5e5f4801f74889d74801cf4889d74c01c7"); } #[test] |
