From 1a0b356d4069d4f8f0a0b805c2a15e61e4ab0d08 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Mon, 12 Jan 2026 18:35:12 -0700 Subject: [PATCH 1/4] Add pushtoarray insn to fix segfault with forwarding + splat Example insns diff for `def x = [3]; def a(...) = b(*x, 2, 3, ...)` == disasm: # local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] "..."@0 0000 putself ( 1)[Ca] 0000 putself 0000 opt_send_without_block 0000 splatarray true 0000 putobject 2 0000 putobject 3 +0000 pushtoarray 2 0000 getlocal_WC_0 "..."@0 0000 sendforward , nil 0000 leave [Re] This matches the insns produced by parse.y --- bootstraptest/test_method.rb | 7 +++++++ prism_compile.c | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/bootstraptest/test_method.rb b/bootstraptest/test_method.rb index 997675200e4d73..78aab734854fee 100644 --- a/bootstraptest/test_method.rb +++ b/bootstraptest/test_method.rb @@ -1427,3 +1427,10 @@ def test(*, kw: false) test RUBY + +assert_equal '[1, 2, 3]', %q{ + def target(*args) = args + def x = [1] + def forwarder(...) = target(*x, 2, ...) + forwarder(3).inspect +}, '[Bug #21832] post-splat args before forwarding' diff --git a/prism_compile.c b/prism_compile.c index 36a0b6ce50a77b..788968113090cd 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1833,6 +1833,10 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // foo(*a, b, c: :d) // foo(*a, b, **c) // + // If the next node is a forwarding argument: + // + // foo(*a, b, ...) + // // If the next node is NULL (we have hit the end): // // foo(*a, b) @@ -1855,6 +1859,10 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b PUSH_INSN(ret, location, concatarray); break; } + case PM_FORWARDING_ARGUMENTS_NODE: { + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(post_splat_counter)); + break; + } default: break; } From 6f38d3de7f4f5073f3aed4a0f75f3e918d0a7f0c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 13 Jan 2026 15:47:26 -0500 Subject: [PATCH 2/4] ZJIT: Only print bold text in TTY (#15864) This is really irritating when piping to a file or pager. --- zjit/src/backend/lir.rs | 16 ++++++++++------ zjit/src/disasm.rs | 9 +++++---- zjit/src/lib.rs | 1 + zjit/src/ttycolors.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 zjit/src/ttycolors.rs diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f4ed3d7cb78ce6..d8d82a09cabb00 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1901,18 +1901,19 @@ impl Assembler } } -const BOLD_BEGIN: &str = "\x1b[1m"; -const BOLD_END: &str = "\x1b[22m"; - /// Return a result of fmt::Display for Assembler without escape sequence pub fn lir_string(asm: &Assembler) -> String { - format!("{asm}").replace(BOLD_BEGIN, "").replace(BOLD_END, "") + use crate::ttycolors::TTY_TERMINAL_COLOR; + format!("{asm}").replace(TTY_TERMINAL_COLOR.bold_begin, "").replace(TTY_TERMINAL_COLOR.bold_end, "") } impl fmt::Display for Assembler { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Count the number of duplicated label names to disambiguate them if needed let mut label_counts: HashMap<&String, usize> = HashMap::new(); + let colors = crate::ttycolors::get_colors(); + let bold_begin = colors.bold_begin; + let bold_end = colors.bold_end; for label_name in self.label_names.iter() { let counter = label_counts.entry(label_name).or_insert(0); *counter += 1; @@ -1932,7 +1933,7 @@ impl fmt::Display for Assembler { for insn in self.insns.iter() { match insn { Insn::Comment(comment) => { - writeln!(f, " {BOLD_BEGIN}# {comment}{BOLD_END}")?; + writeln!(f, " {bold_begin}# {comment}{bold_end}")?; } Insn::Label(target) => { let &Target::Label(Label(label_idx)) = target else { @@ -2506,6 +2507,9 @@ impl AssemblerPanicHook { /// Dump Assembler, highlighting the insn_idx line fn dump_asm(asm: &Assembler, insn_idx: usize) { + let colors = crate::ttycolors::get_colors(); + let bold_begin = colors.bold_begin; + let bold_end = colors.bold_end; let lir_string = lir_string(asm); let lines: Vec<&str> = lir_string.split('\n').collect(); @@ -2520,7 +2524,7 @@ impl AssemblerPanicHook { eprintln!("Failed to compile LIR at insn_idx={insn_idx}:"); for (idx, line) in lines.iter().enumerate().filter(|(idx, _)| (min_idx..=max_idx).contains(idx)) { if idx == insn_idx && line.starts_with(" ") { - eprintln!("{BOLD_BEGIN}=>{}{BOLD_END}", &line[" ".len()..]); + eprintln!("{bold_begin}=>{}{bold_end}", &line[" ".len()..]); } else { eprintln!("{line}"); } diff --git a/zjit/src/disasm.rs b/zjit/src/disasm.rs index 70a56d780d4edc..d4f65915946e0e 100644 --- a/zjit/src/disasm.rs +++ b/zjit/src/disasm.rs @@ -1,8 +1,5 @@ use crate::asm::CodeBlock; -pub const BOLD_BEGIN: &str = "\x1b[1m"; -pub const BOLD_END: &str = "\x1b[22m"; - pub fn disasm_addr_range(cb: &CodeBlock, start_addr: usize, end_addr: usize) -> String { use std::fmt::Write; @@ -36,12 +33,16 @@ pub fn disasm_addr_range(cb: &CodeBlock, start_addr: usize, end_addr: usize) -> let start_addr = 0; let insns = cs.disasm_all(code_slice, start_addr as u64).unwrap(); + let colors = crate::ttycolors::get_colors(); + let bold_begin = colors.bold_begin; + let bold_end = colors.bold_end; + // For each instruction in this block for insn in insns.as_ref() { // Comments for this block if let Some(comment_list) = cb.comments_at(insn.address() as usize) { for comment in comment_list { - writeln!(&mut out, " {BOLD_BEGIN}# {comment}{BOLD_END}").unwrap(); + writeln!(&mut out, " {bold_begin}# {comment}{bold_end}").unwrap(); } } writeln!(&mut out, " {}", format!("{insn}").trim()).unwrap(); diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs index 88e7af02b75013..e7420c16dde419 100644 --- a/zjit/src/lib.rs +++ b/zjit/src/lib.rs @@ -30,3 +30,4 @@ mod bitset; mod gc; mod payload; mod json; +mod ttycolors; diff --git a/zjit/src/ttycolors.rs b/zjit/src/ttycolors.rs new file mode 100644 index 00000000000000..f325772431048b --- /dev/null +++ b/zjit/src/ttycolors.rs @@ -0,0 +1,31 @@ +use std::io::IsTerminal; + +pub fn stdout_supports_colors() -> bool { + std::io::stdout().is_terminal() +} + +#[cfg_attr(not(feature = "disasm"), allow(dead_code))] +#[derive(Copy, Clone, Debug)] +pub struct TerminalColor { + pub bold_begin: &'static str, + pub bold_end: &'static str, +} + +pub static TTY_TERMINAL_COLOR: TerminalColor = TerminalColor { + bold_begin: "\x1b[1m", + bold_end: "\x1b[22m", +}; + +pub static NON_TTY_TERMINAL_COLOR: TerminalColor = TerminalColor { + bold_begin: "", + bold_end: "", +}; + +/// Terminal escape codes for colors, font weight, etc. Only enabled if stdout is a TTY. +pub fn get_colors() -> &'static TerminalColor { + if stdout_supports_colors() { + &TTY_TERMINAL_COLOR + } else { + &NON_TTY_TERMINAL_COLOR + } +} From 9f8505f06cd491e5635c40a9baa7d377bb258c5b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 12 Jan 2026 17:30:33 -0500 Subject: [PATCH 3/4] [ruby/mmtk] Implement object movement support for finalizer table https://github.com/ruby/mmtk/commit/e5e2c1c347 --- gc/mmtk/mmtk.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e58d88025bf5e9..e8ba019ae14ae8 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -91,6 +91,8 @@ RB_THREAD_LOCAL_SPECIFIER VALUE marking_parent_object; #include +static inline VALUE rb_mmtk_call_object_closure(VALUE obj, bool pin); + static void rb_mmtk_init_gc_worker_thread(MMTk_VMWorkerThread gc_thread_tls) { @@ -361,7 +363,7 @@ make_final_job(struct objspace *objspace, VALUE obj, VALUE table) } static int -rb_mmtk_update_finalizer_table_i(st_data_t key, st_data_t value, st_data_t data) +rb_mmtk_update_finalizer_table_i(st_data_t key, st_data_t value, st_data_t data, int error) { RUBY_ASSERT(RB_FL_TEST(key, RUBY_FL_FINALIZE)); RUBY_ASSERT(mmtk_is_reachable((MMTk_ObjectReference)value)); @@ -369,7 +371,14 @@ rb_mmtk_update_finalizer_table_i(st_data_t key, st_data_t value, st_data_t data) struct objspace *objspace = (struct objspace *)data; - if (!mmtk_is_reachable((MMTk_ObjectReference)key)) { + if (mmtk_is_reachable((MMTk_ObjectReference)key)) { + VALUE new_key_location = rb_mmtk_call_object_closure((VALUE)key, false); + + if (new_key_location != key) { + return ST_REPLACE; + } + } + else { make_final_job(objspace, (VALUE)key, (VALUE)value); rb_postponed_job_trigger(objspace->finalizer_postponed_job); @@ -380,13 +389,25 @@ rb_mmtk_update_finalizer_table_i(st_data_t key, st_data_t value, st_data_t data) return ST_CONTINUE; } +static int +rb_mmtk_update_finalizer_table_replace_i(st_data_t *key, st_data_t *value, st_data_t data, int existing) +{ + *key = rb_mmtk_call_object_closure((VALUE)*key, false); + + return ST_CONTINUE; +} + static void rb_mmtk_update_finalizer_table(void) { struct objspace *objspace = rb_gc_get_objspace(); - // TODO: replace with st_foreach_with_replace when GC is moving - st_foreach(objspace->finalizer_table, rb_mmtk_update_finalizer_table_i, (st_data_t)objspace); + st_foreach_with_replace( + objspace->finalizer_table, + rb_mmtk_update_finalizer_table_i, + rb_mmtk_update_finalizer_table_replace_i, + (st_data_t)objspace + ); } static int From 4f8478f50cd12e1144addcef0cbff35e9ee1e3db Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 13 Jan 2026 15:27:22 -0800 Subject: [PATCH 4/4] misc/.vscode/settings.json: Fix rust-analyzer warnings --- misc/.vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misc/.vscode/settings.json b/misc/.vscode/settings.json index c11838155efaa9..a2e4e1ec6945af 100644 --- a/misc/.vscode/settings.json +++ b/misc/.vscode/settings.json @@ -3,4 +3,8 @@ "disasm", ], "rust-analyzer.cfg.setTest": false, + // rust-analyzer bundled in the VSCode extension may only support Rust newer than 1.85.0. + // To avoid warnings, install rust-analyzer with `rustup component add rust-analyzer` and + // use `~/.cargo/bin/rust-analyzer` with the following config. + "rust-analyzer.server.path": "rust-analyzer", }