From 4ec235e0b227d38426aa477e537ac397963c0ee8 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 14 May 2026 16:06:15 -0400 Subject: [PATCH 01/10] ZJIT: Replace non-ASCII chars in comments with ASCII equivalents (#16975) --- zjit/src/backend/arm64/mod.rs | 2 +- zjit/src/backend/lir.rs | 44 +++++++++++++++++----------------- zjit/src/backend/x86_64/mod.rs | 6 ++--- zjit/src/codegen.rs | 22 ++++++++--------- zjit/src/hir.rs | 6 ++--- zjit/src/hir/opt_tests.rs | 2 +- zjit/src/virtualmem.rs | 6 ++--- 7 files changed, 44 insertions(+), 44 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 68799aa6ce17d8..867b829eccdd87 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -685,7 +685,7 @@ impl Assembler { // Convert MemBase::Stack to MemBase::Reg(NATIVE_BASE_PTR) with the // correct stack displacement. The stack slot value lives directly at // [NATIVE_BASE_PTR + stack_disp], so we just adjust the base and - // combine displacements — no indirection needed. Large + // combine displacements -- no indirection needed. Large // displacements are handled by split_stack_membase(). let Mem { base, disp: stack_disp, .. } = stack_state.stack_membase_to_mem(stack_membase); Opnd::Mem(Mem { base, disp: stack_disp + opnd_disp, num_bits: opnd_num_bits }) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 976e93c8ae4126..467cd5b4de5624 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -2458,7 +2458,7 @@ impl Assembler if survivors.is_empty() { if call_result_live { - // No survivors to restore — move result directly to output. + // No survivors to restore -- move result directly to output. let out = Self::rewritten_opnd(out, assignments); new_insns.push(Insn::Mov { dest: out, src: C_RET_OPND }); new_ids.push(None); @@ -3257,7 +3257,7 @@ impl fmt::Display for Assembler { } // Use sorted_blocks() instead of block_order() because block_order() - // calls rpo() → edges() which requires all blocks end with terminators. + // calls rpo() -> edges() which requires all blocks end with terminators. // After arm64_scratch_split, blocks may not have terminators. for bb in self.sorted_blocks() { let params = &bb.parameters; @@ -4323,7 +4323,7 @@ mod tests { asm.number_instructions(16); let intervals = asm.build_intervals(live_in); - // 3 registers — only r10 needs to spill + // 3 registers -- only r10 needs to spill let preferred_registers = asm.preferred_register_assignments(&intervals); let (assignments, num_stack_slots) = asm.linear_scan(intervals, 3, &preferred_registers); @@ -4351,7 +4351,7 @@ mod tests { asm.number_instructions(16); let intervals = asm.build_intervals(live_in); - // Only 1 register available — forces spills + // Only 1 register available -- forces spills let preferred_registers = asm.preferred_register_assignments(&intervals); let (assignments, num_stack_slots) = asm.linear_scan(intervals, 1, &preferred_registers); @@ -4431,9 +4431,9 @@ mod tests { use crate::backend::current::ALLOC_REGS; let regs = &ALLOC_REGS[..5]; - // Edge b1→b2 (single succ): args=[UImm(1), v1], params=[v2, v3] - // v1→Reg(1), v2→Reg(1), v3→Reg(2) - // Reg copy: Reg(1)→Reg(2) → Mov(regs[2], regs[1]) + // Edge b1->b2 (single succ): args=[UImm(1), v1], params=[v2, v3] + // v1->Reg(1), v2->Reg(1), v3->Reg(2) + // Reg copy: Reg(1)->Reg(2) -> Mov(regs[2], regs[1]) // Imm move: Mov(regs[1], UImm(1)) // Inserted in b1 before Jmp: [Label, Mov, Mov, Jmp] let b1_insns = &asm.basic_blocks[b1.0].insns; @@ -4443,10 +4443,10 @@ mod tests { assert!(matches!(&b1_insns[2], Insn::Mov { dest, src } if *dest == Opnd::Reg(regs[1]) && *src == Opnd::UImm(1))); - // Edge b3→b2 (single succ): args=[v4, v5], params=[v2, v3] - // v4→Reg(3), v5→Reg(2), v2→Reg(1), v3→Reg(2) - // Reg copy: Reg(3)→Reg(1) → Mov(regs[1], regs[3]) - // Reg(2)→Reg(2) is self-move, filtered + // Edge b3->b2 (single succ): args=[v4, v5], params=[v2, v3] + // v4->Reg(3), v5->Reg(2), v2->Reg(1), v3->Reg(2) + // Reg copy: Reg(3)->Reg(1) -> Mov(regs[1], regs[3]) + // Reg(2)->Reg(2) is self-move, filtered // Inserted in b3 before Jmp: [Label, Mul, Sub, Mov, Jmp] let b3_insns = &asm.basic_blocks[b3.0].insns; assert_eq!(b3_insns.len(), 5); @@ -4455,7 +4455,7 @@ mod tests { // Verify original instructions in b3 are rewritten to physical registers. // b3: Mul { left: r12, right: r13, out: r14 }, Sub { left: r13, right: UImm(1), out: r15 } - // r12→Reg(1), r13→Reg(2), r14→Reg(3), r15→Reg(2) + // r12->Reg(1), r13->Reg(2), r14->Reg(3), r15->Reg(2) assert!(matches!(&b3_insns[1], Insn::Mul { left, right, out } if *left == Opnd::Reg(regs[1]) && *right == Opnd::Reg(regs[2]) && *out == Opnd::Reg(regs[3]))); assert!(matches!(&b3_insns[2], Insn::Sub { left, right, out } @@ -4473,8 +4473,8 @@ mod tests { let (assignments, _) = asm.linear_scan(intervals.clone(), 5, &preferred_registers); // Entry block b1 has parameters [v0, v1]. - // With 5 registers: v0 → Reg(0) = regs[0], arrival = param_opnd(0) = regs[0] → self-move, filtered - // v1 → Reg(1) = regs[1], arrival = param_opnd(1) = regs[1] → self-move, filtered + // With 5 registers: v0 -> Reg(0) = regs[0], arrival = param_opnd(0) = regs[0] -> self-move, filtered + // v1 -> Reg(1) = regs[1], arrival = param_opnd(1) = regs[1] -> self-move, filtered // Before resolve_ssa, b1 has: [Label, Jmp] = 2 insns assert_eq!(asm.basic_blocks[b1.0].insns.len(), 2); @@ -4482,7 +4482,7 @@ mod tests { // After resolve_ssa, b1 should still have the same number of insns // (plus any edge moves, but no entry param moves since they're all self-moves). - // Edge b1→b2 inserts 2 moves before Jmp: [Label, Mov, Mov, Jmp] = 4 insns + // Edge b1->b2 inserts 2 moves before Jmp: [Label, Mov, Mov, Jmp] = 4 insns // No additional entry param moves. let b1_insns = &asm.basic_blocks[b1.0].insns; assert_eq!(b1_insns.len(), 4); @@ -4508,8 +4508,8 @@ mod tests { let b3 = asm.new_block(hir::BlockId(2), false, 2); // b1: v0 = Add(123, 0), v1 = Add(v0, 456), Cmp(v1, 0), Jl(b2, [v0]), Jmp(b3, [v1]) - // v0 is live across b1→b2 edge AND v1 is live across b1→b3 edge - // This forces v0 and v1 to have overlapping live ranges → different registers + // v0 is live across b1->b2 edge AND v1 is live across b1->b3 edge + // This forces v0 and v1 to have overlapping live ranges -> different registers asm.set_current_block(b1); let label_b1 = asm.new_label("bb0"); asm.write_label(label_b1); @@ -4562,8 +4562,8 @@ mod tests { asm.resolve_ssa(&intervals, &assignments); - // A new interstitial block should have been created for the critical edge b1→b3 - // b1→b3 is critical because b1 has 2 successors and b3 has 2 predecessors + // A new interstitial block should have been created for the critical edge b1->b3 + // b1->b3 is critical because b1 has 2 successors and b3 has 2 predecessors assert_eq!(asm.basic_blocks.len(), 4); let split_block_id = BlockId(3); @@ -4587,11 +4587,11 @@ mod tests { panic!("Expected Jmp(b3) at end of split block"); } - // The split block should have a Mov for v1→v4 + // The split block should have a Mov for v1->v4 let has_mov = split_insns.iter().any(|insn| matches!(insn, Insn::Mov { .. })); - assert!(has_mov, "Expected Mov in split block for v1→v4"); + assert!(has_mov, "Expected Mov in split block for v1->v4"); - // b2→b3 is not a critical edge (b2 has single succ), so moves go before Jmp in b2 + // b2->b3 is not a critical edge (b2 has single succ), so moves go before Jmp in b2 let v3_alloc = assignments[v3.vreg_idx().0].unwrap(); let b2_insns = &asm.basic_blocks[b2.0].insns; if v3_alloc != v4_alloc { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 3957e3c490534d..d8d930dfceac54 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -112,7 +112,7 @@ const SCRATCH1_OPND: Opnd = Opnd::Reg(R10_REG); pub const SCRATCH_REG: Reg = R11_REG; impl Assembler { - // This keeps frame growth below the ±4096-byte displacement range we rely + // This keeps frame growth below the +/-4096-byte displacement range we rely // on for common stack-slot accesses on x86_64. const MAX_FRAME_STACK_SLOTS: usize = 2048; @@ -142,7 +142,7 @@ impl Assembler { } // These are the callee-saved registers in the x86-64 SysV ABI - // RBX, RSP, RBP, and R12–R15 + // RBX, RSP, RBP, and R12-R15 /// Split IR instructions for the x86 platform fn x86_split(mut self) -> Assembler @@ -357,7 +357,7 @@ impl Assembler { // Convert MemBase::Stack to MemBase::Reg(NATIVE_BASE_PTR) with the // correct stack displacement. The stack slot value lives directly at // [NATIVE_BASE_PTR + stack_disp], so we just adjust the base and - // combine displacements — no indirection needed. + // combine displacements -- no indirection needed. let Mem { base, disp: stack_disp, .. } = stack_state.stack_membase_to_mem(stack_membase); Opnd::Mem(Mem { base, disp: stack_disp + opnd_disp, num_bits: opnd_num_bits }) } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f11e112bfe0000..eebd72c8727c5e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -314,7 +314,7 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> R } // Compile the ISEQ. When function is None, this is a lazy compile - // from a stub hit — wrap in a trace event covering the full compile. + // from a stub hit -- wrap in a trace event covering the full compile. let mut version = IseqVersion::new(iseq); let code_ptrs = if function.is_none() { trace_compile_phase(&iseq_get_location(iseq, 0), || gen_iseq_body(cb, iseq, version, function)) @@ -387,7 +387,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func // Create all LIR basic blocks corresponding to HIR basic blocks for (rpo_idx, &block_id) in reverse_post_order.iter().enumerate() { - // Skip the entries superblock — it's an internal CFG artifact + // Skip the entries superblock -- it's an internal CFG artifact if block_id == function.entries_block { continue; } let lir_block_id = asm.new_block(block_id, function.is_entry_block(block_id), rpo_idx); hir_to_lir[block_id.0] = Some(lir_block_id); @@ -395,7 +395,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func // Compile each basic block for &block_id in reverse_post_order.iter() { - // Skip the entries superblock — it's an internal CFG artifact + // Skip the entries superblock -- it's an internal CFG artifact if block_id == function.entries_block { continue; } // Set the current block to the LIR block that corresponds to this // HIR block. @@ -2069,22 +2069,22 @@ fn gen_is_a(jit: &mut JITState, asm: &mut Assembler, obj: Opnd, class: Opnd) -> let val = asm.load_mem(obj); - // Immediate → definitely not String/Array/Hash + // Immediate -> definitely not String/Array/Hash asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64)); asm.jnz(jit, result_edge(Qfalse.into())); - // Qfalse → definitely not String/Array/Hash + // Qfalse -> definitely not String/Array/Hash asm.cmp(val, Qfalse.into()); asm.je(jit, result_edge(Qfalse.into())); - // Heap object → check builtin type + // Heap object -> check builtin type let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS)); let obj_builtin_type = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64)); asm.cmp(obj_builtin_type, Opnd::UImm(builtin_type as u64)); let result = asm.csel_e(Qtrue.into(), Qfalse.into()); asm.jmp(result_edge(result)); - // Result block — receives the value via block parameter (phi node) + // Result block -- receives the value via block parameter (phi node) asm.set_current_block(result_block); let label = jit.get_label(asm, result_block, hir_block_id); asm.write_label(label); @@ -2458,21 +2458,21 @@ fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, ty: Typ // TODO: Max thinks codegen should not care about the shapes of the operands except to create them. (Shopify/ruby#685) let val = asm.load_mem(val); - // Immediate → definitely not the class + // Immediate -> definitely not the class asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into()); asm.jnz(jit, result_edge(Opnd::Imm(0))); - // Qfalse → definitely not the class + // Qfalse -> definitely not the class asm.cmp(val, Qfalse.into()); asm.je(jit, result_edge(Opnd::Imm(0))); - // Heap object → check klass field + // Heap object -> check klass field let klass = asm.load(Opnd::mem(64, val, RUBY_OFFSET_RBASIC_KLASS)); asm.cmp(klass, Opnd::Value(expected_class)); let result = asm.csel_e(Opnd::UImm(1), Opnd::Imm(0)); asm.jmp(result_edge(result)); - // Result block — receives the value via block parameter (phi node) + // Result block -- receives the value via block parameter (phi node) asm.set_current_block(result_block); let label = jit.get_label(asm, result_block, hir_block_id); asm.write_label(label); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9e38f41c29d892..86bee5568a643f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6233,7 +6233,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> { writeln!(f, "fn {iseq_name}:")?; for block_id in fun.rpo() { if !self.display_snapshot_and_tp_patchpoints && block_id == fun.entries_block { - // Unless we're doing --zjit-dump-hir=all, skip the entries superblock — it's an + // Unless we're doing --zjit-dump-hir=all, skip the entries superblock -- it's an // internal CFG artifact continue; } @@ -7041,7 +7041,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // gen_is_block_given) to check for a block handler. Precompute the lexical // distance from this iseq up to local_iseq so codegen does not have to // walk the parent chain. Any DEFINED_YIELD reaching this branch has a - // method local_iseq by construction — the above branch has already + // method local_iseq by construction -- the above branch has already // diverted the non-method case to Qnil. let lep_level = if op_type == DEFINED_YIELD as usize { get_lvar_level(iseq) @@ -8606,7 +8606,7 @@ impl Dominators { let rpo = f.rpo(); let num_blocks = f.blocks.len(); - // Map BlockId → RPO index for O(1) lookup in intersect. + // Map BlockId -> RPO index for O(1) lookup in intersect. let mut rpo_order = vec![usize::MAX; num_blocks]; for (idx, &block) in rpo.iter().enumerate() { rpo_order[block.0] = idx; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 467d9cb6f75abd..e8a0488023da88 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -15683,7 +15683,7 @@ mod hir_opt_tests { #[test] fn test_recompile_no_profile_send() { - // Test the SideExit → recompile flow: a no-profile send becomes a SideExit, + // Test the SideExit -> recompile flow: a no-profile send becomes a SideExit, // the exit profiles the send, triggers recompilation, and the new version // optimizes it to SendDirect. eval(" diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index c2a1e13a5dee83..0088ef1a66fecb 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -113,11 +113,11 @@ impl VirtualMem { // Memory protection syscalls need page-aligned addresses, so check it here. Assuming // `virt_block` is page-aligned, `second_half` should be page-aligned as long as the - // page size in bytes is a power of two 2¹⁹ or smaller. This is because the user - // requested size is half of mem_option × 2²⁰ as it's in MiB. + // page size in bytes is a power of two 2^19 or smaller. This is because the user + // requested size is half of mem_option * 2^20 as it's in MiB. // // Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB - // (2¹⁶ bytes) pages, which should be fine. 4KiB pages seem to be the most popular though. + // (2^16 bytes) pages, which should be fine. 4KiB pages seem to be the most popular though. let page_size = unsafe { rb_jit_get_page_size() }; assert_eq!( virt_block as usize % page_size as usize, 0, From 0dc4b2ee8175841ea7b48599612ac8e7e751d0f1 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 14 May 2026 15:56:57 -0500 Subject: [PATCH 02/10] [DOC] Tweaks for Pathname#<=> --- pathname.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pathname.c b/pathname.c index a294a0d7301ead..ec026c133344e0 100644 --- a/pathname.c +++ b/pathname.c @@ -53,16 +53,16 @@ get_strpath(VALUE obj) * * Examples: * - * Pathname.new('a') <=> Pathname.new('b') # => -1 - * Pathname.new('a') <=> Pathname.new('ab') # => -1 - * Pathname.new('a') <=> Pathname.new('a') # => 0 - * Pathname.new('b') <=> Pathname.new('a') # => 1 - * Pathname.new('ab') <=> Pathname.new('a') # => 1 - * Pathname.new('ab') <=> 'a' # => nil + * Pathname('a') <=> Pathname('b') # => -1 + * Pathname('a') <=> Pathname('ab') # => -1 + * Pathname('a') <=> Pathname('a') # => 0 + * Pathname('b') <=> Pathname('a') # => 1 + * Pathname('ab') <=> Pathname('a') # => 1 + * Pathname('ab') <=> 'a' # => nil * * Two pathnames that are different may refer to the same entry in the filesystem: * - * Pathname.new('lib') <=> Pathname.new('./lib') # => 1 + * Pathname('lib') <=> Pathname('./lib') # => 1 * */ static VALUE From c1be6a379104bbc48e171659509d8714bb492e33 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 14 May 2026 14:47:00 +0100 Subject: [PATCH 03/10] [DOC] Doc for Pathname#atime --- pathname_builtin.rb | 50 ++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index fd1e1a7929243e..00f575ef552c60 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1001,23 +1001,41 @@ def binwrite(...) File.binwrite(@path, ...) end # Returns a new Time object containing the time of the most recent # access (read or write) to the entry represented by +self+: # - # filepath = 't.tmp' - # pn = Pathname.new(filepath) - # File.exist?(filepath) # => false - # pn.atime # Raises Errno::ENOENT: No such file or directory - # File.write(filepath, 'foo') - # pn.atime # => 2026-03-22 13:49:44.5165608 -0500 - # File.read(filepath) - # pn.atime # => 2026-03-22 13:49:57.5359349 -0500 - # File.delete(filepath) + # # Work in a temporary directory. + # require 'tmpdir' + # Dir.mktmpdir do |tmpdirpath| + # # A subdirectory therein, and its Pathname. + # dirpath = File.join(tmpdirpath, 'subdir') + # Dir.mkdir(dirpath) + # dir_pn = Pathname(dirpath) + # puts "Create directory; establishes atime for directory." + # puts " Directory atime: #{dir_pn.atime}" + # sleep(1) + # + # # A file in the subdirectory, and its Pathname. + # filepath = File.join(dirpath, 't.txt') + # puts "Create file; establishes atime for file, updates atime for directory." + # File.write(filepath, 'foo') + # file_pn = Pathname(filepath) + # puts " File atime: #{file_pn.atime}" + # puts " Directory atime: #{dir_pn.atime}" + # sleep(1) + # puts "Write file; updates atimes for file and directory." + # File.write(filepath, 'bar') + # puts " File atime: #{file_pn.atime}" + # puts " Directory atime: #{dir_pn.atime}" + # end + # + # Output: # - # dirpath = 'tmp' - # Dir.mkdir(dirpath) - # pn = Pathname.new(dirpath) - # pn.atime # => 2026-03-31 11:46:35.4813492 -0500 - # Dir.empty?(dirname) # => true - # pn.atime # => 2026-03-31 11:51:10.1210092 -0500 - # Dir.delete(dirpath) + # Create directory; establishes atime for directory. + # Directory atime: 2026-05-14 14:36:43 +0100 + # Create file; establishes atime for file, updates atime for directory. + # File atime: 2026-05-14 14:36:44 +0100 + # Directory atime: 2026-05-14 14:36:44 +0100 + # Write file; updates atimes for file and directory. + # File atime: 2026-05-14 14:36:45 +0100 + # Directory atime: 2026-05-14 14:36:45 +0100 # # See {File System Timestamps}[rdoc-ref:file/timestamps.md]. def atime() File.atime(@path) end From 8f1ae02c6d3bac103f1fd3f7b811f05ebca7000f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 14 May 2026 13:53:17 +0100 Subject: [PATCH 04/10] [DOC] Tweak for Pathname#ascend --- pathname_builtin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 00f575ef552c60..b0558cf87c8900 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -590,7 +590,7 @@ def descend # yields +self+, then a new pathname for each successive dirname in the stored path; # see File.dirname: # - # Pathname.new('/path/to/some/file.rb').ascend {|dirname| p dirname} + # Pathname('/path/to/some/file.rb').ascend {|dirname| p dirname} # # # # # # From ec106b10a9998734eadd9a6ad14ab8209b9da5cc Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 13 May 2026 21:54:26 -0400 Subject: [PATCH 05/10] Move rb_ractor_setup_belonging to rb_newobj --- gc.c | 6 ++++++ gc/default/default.c | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gc.c b/gc.c index 191f86ff513d15..639761dcd28ec6 100644 --- a/gc.c +++ b/gc.c @@ -1022,6 +1022,12 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape GC_ASSERT((flags & FL_WB_PROTECTED) == 0); rb_ractor_t *cr = rb_ec_ractor_ptr(ec); VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size); + +#if RACTOR_CHECK_MODE + void rb_ractor_setup_belonging(VALUE obj); + rb_ractor_setup_belonging(obj); +#endif + RBASIC_SET_SHAPE_ID_NO_CHECKS(obj, shape_id); gc_validate_pc(obj); diff --git a/gc/default/default.c b/gc/default/default.c index d186eeba366d7f..fa39ecc83217a1 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2222,12 +2222,6 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, RBASIC(obj)->shape_id = 0; #endif - -#if RACTOR_CHECK_MODE - void rb_ractor_setup_belonging(VALUE obj); - rb_ractor_setup_belonging(obj); -#endif - #if RGENGC_CHECK_MODE int lev = RB_GC_VM_LOCK_NO_BARRIER(); { From 9a55fc506dfe26c7511c3bcd75fbf3dad295fa4a Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 12 May 2026 11:43:52 -0700 Subject: [PATCH 06/10] [ruby/rubygems] Close stdin immediately when using popen2e It's good hygiene to close the stdin pipe as soon as you are done writing to it. This can happen for example if "ruby extconf.rb" spawns another process (for example "cargo build", which may spawn arbitrary commands to fetch credentials) and any of those subprocesses attempt to read STDIN until it is closed. We can close it as soon as it's created since we aren't writing to it at all. https://github.com/ruby/rubygems/commit/ab09bfdf10 --- lib/rubygems/ext/builder.rb | 3 ++- test/rubygems/test_gem_ext_builder.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 62d36bcf48d304..e00cf159da3e7c 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -102,7 +102,8 @@ def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {}) # Set $SOURCE_DATE_EPOCH for the subprocess. build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env) output, status = begin - Open3.popen2e(build_env, *command, chdir: dir) do |_stdin, stdouterr, wait_thread| + Open3.popen2e(build_env, *command, chdir: dir) do |stdin, stdouterr, wait_thread| + stdin.close output = String.new while line = stdouterr.gets output << line diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb index 5fcbc3e2acfdec..37204f3c472a7f 100644 --- a/test/rubygems/test_gem_ext_builder.rb +++ b/test/rubygems/test_gem_ext_builder.rb @@ -106,6 +106,22 @@ def test_custom_make_with_options assert_match(/install: OK/, results) end + def test_class_run_closes_stdin + results = [] + check_stdin_script = <<~'RUBY' + if IO.select([STDIN], nil, nil, 1) + puts "STDIN: #{STDIN.read.inspect}" + else + puts "NOT_READY" + end + RUBY + + Gem::Ext::Builder.run([Gem.ruby, "-e", check_stdin_script], results) + + command_output = results.last + assert_equal "STDIN: \"\"\n", command_output + end + def test_build_extensions pend "terminates on mswin" if vc_windows? && ruby_repo? From 2df9dfaa06073d47a788ef59933567a7ab46695e Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 14 May 2026 16:42:34 -0500 Subject: [PATCH 07/10] [DOC] Doc for Pathname#chown --- pathname_builtin.rb | 56 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index b0558cf87c8900..446c52f0d958e7 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1116,7 +1116,61 @@ def chmod(mode) File.chmod(mode, @path) end # See File.lchmod. def lchmod(mode) File.lchmod(mode, @path) end - # See File.chown. Change owner and group of file. + # call-seq: + # chown(owner_id, group_id) -> 0 + # + # Changes the owner and group of an entry (directory or file): + # + # # Work in a temporary directory. + # require 'tmpdir' + # Dir.mktmpdir do |tmpdirpath| + # # A subdirectory therein, and its Pathname. + # dirpath = File.join(tmpdirpath, 'subdir') + # Dir.mkdir(dirpath) + # dir_stat = File.stat(dirpath) + # puts "Original directory owner: #{dir_stat.uid}" + # puts "Original directory group: #{dir_stat.gid}" + # dir_pn = Pathname(dirpath) + # dir_pn.chown(1000, 1000) + # dir_stat = File.stat(dirpath) + # puts "New directory owner: #{dir_stat.uid}" + # puts "New directory group: #{dir_stat.gid}" + # + # # A file in the subdirectory, and its Pathname. + # filepath = File.join(dirpath, 't.txt') + # file_pn = Pathname(filepath) + # # Create the file. + # File.write(filepath, 'foo') + # file_stat = File.stat(filepath) + # puts "Original file owner: #{file_stat.uid}" + # puts "Original file group: #{file_stat.gid}" + # file_pn = Pathname(dirpath) + # file_pn.chown(1000, 1000) + # file_stat = File.stat(dirpath) + # puts "New file owner: #{file_stat.uid}" + # puts "New file group: #{file_stat.gid}" + # end + # + # Output: + # + # Original directory owner: 0 + # Original directory group: 0 + # New directory owner: 1000 + # New directory group: 1000 + # Original file owner: 0 + # Original file group: 0 + # New file owner: 1000 + # New file group: 1000 + # + # Notes: + # + # - On Windows, the owner and group are not changed. + # - Only a process with superuser privileges can change the owner of an entry. + # - The owner of an entry can change its group to any group + # to which the owner belongs. + # - A +nil+ or +-1+ owner or group id is ignored. + # - The method follows symbolic links to the target entry. + # def chown(owner, group) File.chown(owner, group, @path) end # See File.lchown. From 63397319b4a3a66f1464289aa4a68e8fa19ba4cf Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 14 May 2026 16:59:34 -0400 Subject: [PATCH 08/10] [ruby/mmtk] Remove call to rb_ractor_setup_belonging Don't need to call it after https://github.com/ruby/ruby/pull/16961. https://github.com/ruby/mmtk/commit/ae5638ac00 --- gc/mmtk/mmtk.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 6547a6cdea4986..113e37857c8c11 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -27,7 +27,6 @@ #if RACTOR_CHECK_MODE # define RVALUE_SUFFIX_SIZE sizeof(VALUE) -void rb_ractor_setup_belonging(VALUE obj); #else # define RVALUE_SUFFIX_SIZE 0 #endif @@ -930,10 +929,6 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags objspace->total_allocated_objects++; -#if RACTOR_CHECK_MODE - rb_ractor_setup_belonging((VALUE)alloc_obj); -#endif - return (VALUE)alloc_obj; } From fff4b3ef2e3e309e7a84288de53c189aa3d45fed Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 14 May 2026 16:47:07 -0400 Subject: [PATCH 09/10] Make T_MATCH objects able to go through the GC cleanup_p fastpath I think it's likely they will go through this path as well. Bitflag for external offsets allocation is jhawthorn's idea, since size always equals num_regs if it's allocated. --- gc.c | 6 +++++- internal/re.h | 1 + re.c | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gc.c b/gc.c index 639761dcd28ec6..f3ba5840f9df2d 100644 --- a/gc.c +++ b/gc.c @@ -1398,6 +1398,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case T_FLOAT: case T_RATIONAL: case T_COMPLEX: + case T_MATCH: break; case T_FILE: @@ -1406,7 +1407,6 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case T_ICLASS: case T_MODULE: case T_REGEXP: - case T_MATCH: return true; } @@ -1442,6 +1442,10 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) if (flags & RHASH_ST_TABLE_FLAG) return true; return rb_shape_has_fields(shape_id); + case T_MATCH: + if ((flags & (RMATCH_ONIG | RMATCH_OFFSETS_EXTERNAL)) || USE_DEBUG_COUNTER) return true; + return rb_shape_has_fields(shape_id); + case T_BIGNUM: if (!(flags & BIGNUM_EMBED_FLAG)) return true; return rb_shape_has_fields(shape_id); diff --git a/internal/re.h b/internal/re.h index b0acac6033ec89..da165e4756969d 100644 --- a/internal/re.h +++ b/internal/re.h @@ -13,6 +13,7 @@ #include "ruby/re.h" /* for struct RMatch and struct re_registers */ #define RMATCH_ONIG FL_USER1 +#define RMATCH_OFFSETS_EXTERNAL FL_USER2 static inline OnigPosition * RMATCH_BEG_PTR(VALUE match) diff --git a/re.c b/re.c index b8b5963d725b7a..b778fa08f331e6 100644 --- a/re.c +++ b/re.c @@ -1083,6 +1083,7 @@ update_char_offset(VALUE match) if (rm->char_offset_num_allocated < num_regs) { SIZED_REALLOC_N(rm->char_offset, struct rmatch_offset, num_regs, rm->char_offset_num_allocated); rm->char_offset_num_allocated = num_regs; + FL_SET_RAW(match, RMATCH_OFFSETS_EXTERNAL); } enc = rb_enc_get(RMATCH(match)->str); @@ -1159,6 +1160,7 @@ match_init_copy(VALUE obj, VALUE orig) if (rm->char_offset_num_allocated < rm->num_regs) { SIZED_REALLOC_N(rm->char_offset, struct rmatch_offset, rm->num_regs, rm->char_offset_num_allocated); rm->char_offset_num_allocated = rm->num_regs; + FL_SET_RAW(obj, RMATCH_OFFSETS_EXTERNAL); } MEMCPY(rm->char_offset, RMATCH(orig)->char_offset, struct rmatch_offset, rm->num_regs); From 356c0cd0e7953baafd240914476213566f526c85 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 13 May 2026 21:34:12 -0400 Subject: [PATCH 10/10] [ruby/mmtk] Remove dead ractor_check_mode field https://github.com/ruby/mmtk/commit/a46b68fe5b --- gc/mmtk/mmtk.c | 1 - gc/mmtk/mmtk.h | 1 - gc/mmtk/src/abi.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 113e37857c8c11..aa6ac39d7495ea 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -575,7 +575,6 @@ rb_gc_impl_objspace_alloc(void) { MMTk_Builder *builder = rb_mmtk_builder_init(); MMTk_RubyBindingOptions binding_options = { - .ractor_check_mode = RACTOR_CHECK_MODE != 0, .suffix_size = RVALUE_SUFFIX_SIZE, }; mmtk_init_binding(builder, &binding_options, &ruby_upcalls); diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index e8f95920ddcaf5..b11e2873e328a6 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -33,7 +33,6 @@ typedef struct MMTk_BumpPointer { #define MMTk_GC_THREAD_KIND_WORKER 1 typedef struct MMTk_RubyBindingOptions { - bool ractor_check_mode; size_t suffix_size; } MMTk_RubyBindingOptions; diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index c880302080307b..30890e0853801b 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -291,7 +291,6 @@ impl From> for RawVecOfObjRef { #[repr(C)] #[derive(Clone)] pub struct RubyBindingOptions { - pub ractor_check_mode: bool, pub suffix_size: usize, }