From 89d89fa49d387a09e0d3ea7092a598b88d6d86eb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 17 Jul 2025 15:20:20 -0700 Subject: [PATCH 1/4] When reading from stdin, put a wrapper around the IO object The purpose of this commit is to fix Bug #21188. We need to detect when stdin has run in to an EOF case. Unfortunately we can't _call_ the eof function on IO because it will block. Here is a short script to demonstrate the issue: ```ruby x = STDIN.gets puts x puts x.eof? ``` If you run the script, then type some characters (but _NOT_ a newline), then hit Ctrl-D twice, it will print the input string. Unfortunately, calling `eof?` will try to read from STDIN again causing us to need a 3rd Ctrl-D to exit the program. Before introducing the EOF callback to Prism, the input loop looked kind of like this: ```ruby loop do str = STDIN.gets process(str) if str.nil? p :DONE end end ``` Which required 3 Ctrl-D to exit. If we naively changed it to something like this: ```ruby loop do str = STDIN.gets process(str) if STDIN.eof? p :DONE end end ``` It would still require 3 Ctrl-D because `eof?` would block. In this patch, we're wrapping the IO object, checking the buffer for a newline and length, and then using that to simulate a non-blocking eof? method. This commit wraps STDIN and emulates a non-blocking `eof` function. [Bug #21188] --- lib/prism/ffi.rb | 7 +++++-- prism/extension.c | 10 +++++++++- prism/prism.c | 19 +++++++++++++------ prism/prism.h | 13 +++++++++++-- prism_compile.c | 30 ++++++++++++++++++++++++++++-- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 1e1bf8b1c877d4..5ae177055f0108 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -86,6 +86,7 @@ def self.load_exported_functions_from(header, *functions, callbacks) end callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer + callback :pm_parse_stream_feof_t, [:pointer], :int enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY] enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE] @@ -101,7 +102,7 @@ def self.load_exported_functions_from(header, *functions, callbacks) "pm_string_query_local", "pm_string_query_constant", "pm_string_query_method_name", - [:pm_parse_stream_fgets_t] + [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t] ) load_exported_functions_from( @@ -281,12 +282,14 @@ def parse_stream(stream, **options) end } + eof_callback = -> (_) { stream.eof? } + # In the pm_serialize_parse_stream function it accepts a pointer to the # IO object as a void* and then passes it through to the callback as the # third argument, but it never touches it itself. As such, since we have # access to the IO object already through the closure of the lambda, we # can pass a null pointer here and not worry. - LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options)) + LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options)) Prism.load(source, buffer.read, options.fetch(:freeze, false)) end end diff --git a/prism/extension.c b/prism/extension.c index 1533ca7bb379c1..83415d0c29923e 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -994,6 +994,14 @@ profile_file(int argc, VALUE *argv, VALUE self) { return Qnil; } +static int +parse_stream_eof(void *stream) { + if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) { + return 1; + } + return 0; +} + /** * An implementation of fgets that is suitable for use with Ruby IO objects. */ @@ -1034,7 +1042,7 @@ parse_stream(int argc, VALUE *argv, VALUE self) { pm_parser_t parser; pm_buffer_t buffer; - pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, &options); + pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, parse_stream_eof, &options); rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE source = pm_source_new(&parser, encoding, options.freeze); diff --git a/prism/prism.c b/prism/prism.c index ec8f84fb6bc8da..d01c2a0766619c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22848,7 +22848,7 @@ pm_parse(pm_parser_t *parser) { * otherwise return true. */ static bool -pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets) { +pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof) { #define LINE_SIZE 4096 char line[LINE_SIZE]; @@ -22884,6 +22884,12 @@ pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t if (strncmp(line, "__END__\r\n", 9) == 0) return false; break; } + + // All data should be read via gets. If the string returned by gets + // _doesn't_ end with a newline, then we assume we hit EOF condition. + if (stream_feof(stream)) { + break; + } } return true; @@ -22919,16 +22925,17 @@ pm_parse_stream_unterminated_heredoc_p(pm_parser_t *parser) { * can stream stdin in to Ruby so we need to support a streaming API. */ PRISM_EXPORTED_FUNCTION pm_node_t * -pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const pm_options_t *options) { +pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options) { pm_buffer_init(buffer); - bool eof = pm_parse_stream_read(buffer, stream, stream_fgets); + bool eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof); + pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); pm_node_t *node = pm_parse(parser); while (!eof && parser->error_list.size > 0 && (parser->lex_modes.index > 0 || pm_parse_stream_unterminated_heredoc_p(parser))) { pm_node_destroy(parser, node); - eof = pm_parse_stream_read(buffer, stream, stream_fgets); + eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof); pm_parser_free(parser); pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); @@ -23020,13 +23027,13 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons * given stream into to the given buffer. */ PRISM_EXPORTED_FUNCTION void -pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data) { +pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const char *data) { pm_parser_t parser; pm_options_t options = { 0 }; pm_options_read(&options, data); pm_buffer_t parser_buffer; - pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, &options); + pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, stream_feof, &options); pm_serialize_header(buffer); pm_serialize_content(&parser, node, buffer); pm_buffer_append_byte(buffer, '\0'); diff --git a/prism/prism.h b/prism/prism.h index 317568aa0cbd51..a6f22f1a5ad39f 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -87,6 +87,13 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser); */ typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream); +/** + * This function is used in pm_parse_stream to check whether a stream is EOF. + * It closely mirrors that of feof so that feof can be used as the + * default implementation. + */ +typedef int (pm_parse_stream_feof_t)(void *stream); + /** * Parse a stream of Ruby source and return the tree. * @@ -94,10 +101,11 @@ typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream); * @param buffer The buffer to use. * @param stream The stream to parse. * @param stream_fgets The function to use to read from the stream. + * @param stream_feof The function to use to determine if the stream has hit eof. * @param options The optional options to use when parsing. * @return The AST representing the source. */ -PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const pm_options_t *options); +PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options); // We optionally support serializing to a binary string. For systems that don't // want or need this functionality, it can be turned off with the @@ -111,9 +119,10 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buff * @param buffer The buffer to serialize to. * @param stream The stream to parse. * @param stream_fgets The function to use to read from the stream. + * @param stream_feof The function to use to tell if the stream has hit eof. * @param data The optional data to pass to the parser. */ -PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data); +PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const char *data); /** * Serialize the given list of comments to the given buffer. diff --git a/prism_compile.c b/prism_compile.c index 17db5e58c881f9..2f5bb4ebe3bbfb 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -11492,6 +11492,18 @@ pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE * return pm_parse_process(result, node, script_lines); } +struct rb_stdin_wrapper { + VALUE rb_stdin; + int eof_seen; +}; + +static int +pm_parse_stdin_eof(void *stream) +{ + struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream; + return wrapped_stdin->eof_seen; +} + /** * An implementation of fgets that is suitable for use with Ruby IO objects. */ @@ -11500,7 +11512,9 @@ pm_parse_stdin_fgets(char *string, int size, void *stream) { RUBY_ASSERT(size > 0); - VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1)); + struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream; + + VALUE line = rb_funcall(wrapped_stdin->rb_stdin, rb_intern("gets"), 1, INT2FIX(size - 1)); if (NIL_P(line)) { return NULL; } @@ -11511,6 +11525,13 @@ pm_parse_stdin_fgets(char *string, int size, void *stream) memcpy(string, cstr, length); string[length] = '\0'; + // We're reading strings from stdin via gets. We'll assume that if the + // string is smaller than the requested length, and doesn't end with a + // newline, that we hit EOF. + if (length < (size - 1) && string[length - 1] != '\n') { + wrapped_stdin->eof_seen = 1; + } + return string; } @@ -11527,8 +11548,13 @@ pm_parse_stdin(pm_parse_result_t *result) { pm_options_frozen_string_literal_init(&result->options); + struct rb_stdin_wrapper wrapped_stdin = { + rb_stdin, + 0 + }; + pm_buffer_t buffer; - pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) rb_stdin, pm_parse_stdin_fgets, &result->options); + pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) &wrapped_stdin, pm_parse_stdin_fgets, pm_parse_stdin_eof, &result->options); // Copy the allocated buffer contents into the input string so that it gets // freed. At this point we've handed over ownership, so we don't need to From 53b046284196c783d02eadb1a3ba691eb5efd509 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 4 Aug 2025 14:44:51 -0700 Subject: [PATCH 2/4] ZJIT: Add helpers to prepare for C calls (#14100) --- zjit/src/codegen.rs | 100 ++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 433b22e15d2c79..0db4d6b781686a 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -340,7 +340,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SendWithoutBlockDirect { cd, state, self_val, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), opnd!(self_val), opnds!(args))?, Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state))?, - Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(asm, &function.frame_state(*state), bf, opnds!(args))?, + Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args))?, Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, @@ -364,7 +364,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), &Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level)?, Insn::SetLocal { val, ep_offset, level } => return gen_setlocal_with_ep(asm, opnd!(val), *ep_offset, *level), - Insn::GetConstantPath { ic, state } => gen_get_constant_path(asm, *ic, &function.frame_state(*state)), + Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state))?, Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), Insn::SideExit { state, reason } => return gen_side_exit(jit, asm, reason, &function.frame_state(*state)), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), @@ -490,25 +490,26 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, local_ep_offset: u32, le Some(()) } -fn gen_get_constant_path(asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { +fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Option { unsafe extern "C" { fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; } - // Save PC since the call can allocate an IC - gen_save_pc(asm, state); + // Anything could be called on const_missing + gen_prepare_non_leaf_call(jit, asm, state)?; - asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic)) + Some(asm_ccall!(asm, rb_vm_opt_getconstant_path, EC, CFP, Opnd::const_ptr(ic))) } -fn gen_invokebuiltin(asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: Vec) -> Option { +fn gen_invokebuiltin(jit: &JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: Vec) -> Option { // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) { return None; } - gen_save_pc(asm, state); + // Anything can happen inside builtin functions + gen_prepare_non_leaf_call(jit, asm, state)?; let mut cargs = vec![EC]; cargs.extend(args); @@ -772,19 +773,17 @@ fn gen_send_without_block( self_val: Opnd, args: Vec, ) -> Option { - // Spill locals onto the stack. - // TODO: Don't spill locals eagerly; lazily reify frames - asm_comment!(asm, "spill locals"); - for (idx, &insn_id) in state.locals().enumerate() { - asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?); - } + gen_spill_locals(jit, asm, state)?; // Spill the receiver and the arguments onto the stack. // They need to be on the interpreter stack to let the interpreter access them. // TODO: Avoid spilling operands that have been spilled before. + // TODO: Despite https://github.com/ruby/ruby/pull/13468, Kokubun thinks this should + // spill the whole stack in case it raises an exception. The HIR might need to change + // for opt_aref_with, which pushes to the stack in the middle of the instruction. asm_comment!(asm, "spill receiver and arguments"); for (idx, &val) in [self_val].iter().chain(args.iter()).enumerate() { // Currently, we don't move the SP register. So it's equal to the base pointer. - let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32); + let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32); asm.mov(stack_opnd, val); } @@ -821,15 +820,8 @@ fn gen_send_without_block_direct( gen_save_pc(asm, state); gen_save_sp(asm, state.stack().len() - args.len() - 1); // -1 for receiver - // Spill the virtual stack and the locals of the caller onto the stack - // TODO: Lazily materialize caller frames on side exits or when needed - asm_comment!(asm, "spill locals and stack"); - for (idx, &insn_id) in state.locals().enumerate() { - asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?); - } - for (idx, &insn_id) in state.stack().enumerate() { - asm.mov(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?); - } + gen_spill_locals(jit, asm, state)?; + gen_spill_stack(jit, asm, state)?; // Set up the new frame // TODO: Lazily materialize caller frames on side exits or when needed @@ -891,8 +883,7 @@ fn gen_array_dup( val: lir::Opnd, state: &FrameState, ) -> lir::Opnd { - // Save PC - gen_save_pc(asm, state); + gen_prepare_call_with_gc(asm, state); asm_ccall!(asm, rb_ary_resurrect, val) } @@ -903,8 +894,7 @@ fn gen_new_array( elements: Vec, state: &FrameState, ) -> lir::Opnd { - // Save PC - gen_save_pc(asm, state); + gen_prepare_call_with_gc(asm, state); let length: ::std::os::raw::c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long"); @@ -925,8 +915,7 @@ fn gen_new_range( flag: RangeType, state: &FrameState, ) -> lir::Opnd { - // Save PC - gen_save_pc(asm, state); + gen_prepare_call_with_gc(asm, state); // Call rb_range_new(low, high, flag) asm_ccall!(asm, rb_range_new, low, high, (flag as i64).into()) @@ -1040,8 +1029,7 @@ fn gen_isnil(asm: &mut Assembler, val: lir::Opnd) -> Option { } fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> Option { - // Save PC - gen_save_pc(asm, state); + gen_prepare_call_with_gc(asm, state); Some(asm_ccall!(asm, rb_obj_as_string_result, str, val)) } @@ -1135,6 +1123,54 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) { asm.mov(cfp_sp, sp_addr); } +/// Spill locals onto the stack. +fn gen_spill_locals(jit: &JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> { + // TODO: Avoid spilling locals that have been spilled before and not changed. + asm_comment!(asm, "spill locals"); + for (idx, &insn_id) in state.locals().enumerate() { + asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?); + } + Some(()) +} + +/// Spill the virtual stack onto the stack. +fn gen_spill_stack(jit: &JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> { + // This function does not call gen_save_sp() at the moment because + // gen_send_without_block_direct() spills stack slots above SP for arguments. + asm_comment!(asm, "spill stack"); + for (idx, &insn_id) in state.stack().enumerate() { + asm.mov(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?); + } + Some(()) +} + +/// Prepare for calling a C function that may call an arbitrary method. +/// Use gen_prepare_call_with_gc() if the method is leaf but allocates objects. +#[must_use] +fn gen_prepare_non_leaf_call(jit: &JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> { + // TODO: Lazily materialize caller frames when needed + // Save PC for backtraces and allocation tracing + gen_save_pc(asm, state); + + // Save SP and spill the virtual stack in case it raises an exception + // and the interpreter uses the stack for handling the exception + gen_save_sp(asm, state.stack().len()); + gen_spill_stack(jit, asm, state)?; + + // Spill locals in case the method looks at caller Bindings + gen_spill_locals(jit, asm, state)?; + Some(()) +} + +/// Prepare for calling a C function that may allocate objects and trigger GC. +/// Use gen_prepare_non_leaf_call() if it may also call an arbitrary method. +fn gen_prepare_call_with_gc(asm: &mut Assembler, state: &FrameState) { + // Save PC for allocation tracing + gen_save_pc(asm, state); + // Unlike YJIT, we don't need to save the stack to protect them from GC + // because the backend spills all live registers onto the C stack on asm.ccall(). +} + /// Frame metadata written by gen_push_frame() struct ControlFrame { recv: Opnd, From 8091f3bce588f9062fdda08977c67ba09c9095dd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 4 Aug 2025 23:20:07 +0100 Subject: [PATCH 3/4] ZJIT: Remove passing tests from exclusions (#14097) --- test/.excludes-zjit/TestFixnum.rb | 2 +- test/.excludes-zjit/TestProc.rb | 2 -- test/.excludes-zjit/TestResolvDNS.rb | 8 -------- test/.excludes-zjit/TestThread.rb | 2 -- 4 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 test/.excludes-zjit/TestResolvDNS.rb delete mode 100644 test/.excludes-zjit/TestThread.rb diff --git a/test/.excludes-zjit/TestFixnum.rb b/test/.excludes-zjit/TestFixnum.rb index 6aa0a1f95b5575..aaf8760f2f9ded 100644 --- a/test/.excludes-zjit/TestFixnum.rb +++ b/test/.excludes-zjit/TestFixnum.rb @@ -1,2 +1,2 @@ # Issue: https://github.com/Shopify/ruby/issues/646 -exclude(/test_/, 'Tests make ZJIT panic') +exclude(/test_/, 'Tests make ZJIT panic on Ubuntu') diff --git a/test/.excludes-zjit/TestProc.rb b/test/.excludes-zjit/TestProc.rb index aa6abbecbe3de6..2961b686b6e3e7 100644 --- a/test/.excludes-zjit/TestProc.rb +++ b/test/.excludes-zjit/TestProc.rb @@ -1,3 +1 @@ -exclude(:test_proc_args_pos_rest_block, 'Test crashes with ZJIT') -exclude(:test_proc_args_rest_post_block, 'Test crashes with ZJIT') exclude(:test_binding_receiver, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestResolvDNS.rb b/test/.excludes-zjit/TestResolvDNS.rb deleted file mode 100644 index 37b6d791039e42..00000000000000 --- a/test/.excludes-zjit/TestResolvDNS.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Only happens when running with other tests -# Panics with: -# -# thread '' panicked at zjit/src/asm/arm64/mod.rs:939:13: -# Expected displacement -264 to be 9 bits or less -# -# May be related to https://github.com/Shopify/ruby/issues/646 -exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestThread.rb b/test/.excludes-zjit/TestThread.rb deleted file mode 100644 index 1eb9c50dbab9ae..00000000000000 --- a/test/.excludes-zjit/TestThread.rb +++ /dev/null @@ -1,2 +0,0 @@ -exclude(:test_switch_while_busy_loop, 'Test hangs with ZJIT') -exclude(:test_handle_interrupted?, 'Test fails with ZJIT') From 591849a1a565e2177bee82bee8d0118a7c48eaa9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 4 Aug 2025 17:31:08 -0700 Subject: [PATCH 4/4] Resurrect a ZJIT skip on test_switch_while_busy_loop This can still hang: https://github.com/ruby/ruby/actions/runs/16735509694/job/47373381258 partially reverting https://github.com/ruby/ruby/pull/14097 --- test/.excludes-zjit/TestThread.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/.excludes-zjit/TestThread.rb diff --git a/test/.excludes-zjit/TestThread.rb b/test/.excludes-zjit/TestThread.rb new file mode 100644 index 00000000000000..f6a4b7009b4704 --- /dev/null +++ b/test/.excludes-zjit/TestThread.rb @@ -0,0 +1 @@ +exclude(:test_switch_while_busy_loop, 'Test sometimes hangs with ZJIT')