diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index c1c87db2c725c8..8aecd32f80fab7 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -106,7 +106,11 @@ fn parse_float_env_var(key: &str, default: f64, min: f64, max: f64) -> f64 { .unwrap_or(default) } -fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCTriggerSelector { +fn mmtk_builder_default_parse_heap_mode( + heap_min: usize, + heap_max: usize, + plan: PlanSelector, +) -> GCTriggerSelector { let make_fixed = || GCTriggerSelector::FixedHeapSize(heap_max); let make_dynamic = || GCTriggerSelector::DynamicHeapSize(heap_min, heap_max); @@ -114,6 +118,13 @@ fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCT "fixed" => Some(make_fixed()), "dynamic" => Some(make_dynamic()), "ruby" => { + if plan == PlanSelector::NoGC { + eprintln!( + "[WARN] Cannot use ruby heap mode with NoGC. Using fixed heap mode instead." + ); + return Some(make_fixed()); + } + let min_ratio = parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", 0.2, 0.0, 1.0); let goal_ratio = parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", 0.4, min_ratio, 1.0); @@ -133,6 +144,13 @@ fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCT Some(GCTriggerSelector::Delegated) } "cpu" => { + if plan == PlanSelector::NoGC { + eprintln!( + "[WARN] Cannot use cpu heap mode with NoGC. Using fixed heap mode instead." + ); + return Some(make_fixed()); + } + // CPU-overhead-driven heap sizing based on Tavakolisomeh et al., // "Heap Size Adjustment with CPU Control", MPLR '23. // @@ -206,12 +224,16 @@ pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { std::process::exit(1); } + let plan = mmtk_builder_default_parse_plan(); + + builder.options.plan.set(plan); + builder .options .gc_trigger - .set(mmtk_builder_default_parse_heap_mode(heap_min, heap_max)); - - builder.options.plan.set(mmtk_builder_default_parse_plan()); + .set(mmtk_builder_default_parse_heap_mode( + heap_min, heap_max, plan, + )); Box::into_raw(Box::new(builder)) } diff --git a/include/ruby/internal/intern/object.h b/include/ruby/internal/intern/object.h index 3897639a0ae6ce..c008b058b63150 100644 --- a/include/ruby/internal/intern/object.h +++ b/include/ruby/internal/intern/object.h @@ -281,7 +281,7 @@ RBIMPL_ATTR_PURE() */ VALUE rb_class_real(VALUE klass); -RBIMPL_ATTR_PURE() +RBIMPL_ATTR_NOALIAS() /** * Determines if the given two modules are relatives. * @@ -294,7 +294,7 @@ RBIMPL_ATTR_PURE() */ VALUE rb_class_inherited_p(VALUE scion, VALUE ascendant); -RBIMPL_ATTR_PURE() +RBIMPL_ATTR_NOALIAS() /** * Queries the parent of the given class. * diff --git a/include/ruby/internal/symbol.h b/include/ruby/internal/symbol.h index 8bfd686fbe6487..8ab9009852f04c 100644 --- a/include/ruby/internal/symbol.h +++ b/include/ruby/internal/symbol.h @@ -261,7 +261,7 @@ RBIMPL_ATTR_NONNULL(()) VALUE rb_check_symbol(volatile VALUE *namep); RBIMPL_SYMBOL_EXPORT_END() -RBIMPL_ATTR_PURE() +RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_NONNULL(()) /** * This is a "tiny optimisation" over rb_intern(). If you pass a string diff --git a/io.c b/io.c index 07ac3d7b33a2dc..4ce0aaf2026c81 100644 --- a/io.c +++ b/io.c @@ -5639,7 +5639,15 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl) error = finish_writeconv(fptr, noraise); } } - if (fptr->wbuf.len) { + /* Do not flush the write buffer on close when the stream is in sync + * mode. In sync mode Ruby's write buffer is not authoritative (writes go + * straight to the OS), so any bytes left in the buffer are the result of + * writes made while sync was disabled. Setting sync = true is therefore a + * way to abandon that pending output rather than replaying it on close, + * which matters after an interrupted write where the amount actually + * written is indeterminate. Call flush before enabling sync if the + * buffered data should still be sent. */ + if (fptr->wbuf.len && !(fptr->mode & FMODE_SYNC)) { if (noraise) { io_flush_buffer_sync(fptr); } diff --git a/string.c b/string.c index 11b2d5757ebdaa..d372f923b65add 100644 --- a/string.c +++ b/string.c @@ -9606,7 +9606,7 @@ struct tr_trans_pairs_search { #ifdef HAVE_SIMD unsigned char needles[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES]; - int needles_count; + unsigned int needles_count; #ifdef HAVE_SIMD_NEON uint64_t matches_bitmap; #endif @@ -9651,18 +9651,18 @@ tr_trans_pairs_next_match_sse2(struct tr_trans_pairs_search *search) static inline VALUE tr_trans_pairs_search_sse2(struct tr_trans_pairs_search *search) { - if (search->needles_count) { - RBIMPL_ASSERT_OR_ASSUME(search->needles_count > 0); - RBIMPL_ASSERT_OR_ASSUME(search->needles_count <= TR_TRANS_PAIRS_SIMD_MAX_NEEDLES); + const unsigned int needles_count = search->needles_count; + if (needles_count) { + RBIMPL_ASSERT_OR_ASSUME(needles_count <= TR_TRANS_PAIRS_SIMD_MAX_NEEDLES); if (search->matches_bitmap) { return tr_trans_pairs_next_match_sse2(search); } if ((size_t)(search->send - search->s) >= sizeof(__m128i)) { - int i; + unsigned int i; __m128i masks[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES]; - for (i = 0; i < search->needles_count; i++) { + for (i = 0; i < needles_count; i++) { masks[i] = _mm_set1_epi8(search->needles[i]); } @@ -9670,12 +9670,11 @@ tr_trans_pairs_search_sse2(struct tr_trans_pairs_search *search) const __m128i bytes = _mm_loadu_si128((__m128i const *)search->s); __m128i matches[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES]; - matches[0] = _mm_setzero_si128(); - for (i = 0; i < search->needles_count; i++) { + for (i = 0; i < needles_count; i++) { matches[i] = _mm_cmpeq_epi8(bytes, masks[i]); } - for (i = 1; i < search->needles_count; i++) { + for (i = 1; i < needles_count; i++) { matches[0] = _mm_or_si128(matches[0], matches[i]); } @@ -9714,18 +9713,18 @@ tr_trans_pairs_next_match_neon(struct tr_trans_pairs_search *search) static inline VALUE tr_trans_pairs_search_neon(struct tr_trans_pairs_search *search) { - if (search->needles_count) { - RBIMPL_ASSERT_OR_ASSUME(search->needles_count > 0); - RBIMPL_ASSERT_OR_ASSUME(search->needles_count <= TR_TRANS_PAIRS_SIMD_MAX_NEEDLES); + const unsigned int needles_count = search->needles_count; + if (needles_count) { + RBIMPL_ASSERT_OR_ASSUME(needles_count <= TR_TRANS_PAIRS_SIMD_MAX_NEEDLES); if (search->matches_bitmap) { return tr_trans_pairs_next_match_neon(search); } if ((size_t)(search->send - search->s) >= sizeof(uint8x16_t)) { - int i; + unsigned int i; uint8x16_t masks[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES]; - for (i = 0; i < search->needles_count; i++) { + for (i = 0; i < needles_count; i++) { masks[i] = vdupq_n_u8(search->needles[i]); } @@ -9733,11 +9732,11 @@ tr_trans_pairs_search_neon(struct tr_trans_pairs_search *search) const uint8x16_t bytes = vld1q_u8(search->s); uint8x16_t matches[TR_TRANS_PAIRS_SIMD_MAX_NEEDLES]; - for (i = 0; i < search->needles_count; i++) { + for (i = 0; i < needles_count; i++) { matches[i] = vceqq_u8(bytes, masks[i]); } - for (i = 1; i < search->needles_count; i++) { + for (i = 1; i < needles_count; i++) { matches[0] = vorrq_u8(matches[0], matches[i]); } diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 112a4654921a3a..eaa2d02887d3d3 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -3356,6 +3356,40 @@ def test_threaded_flush end.each {|th| th.join} end + def test_close_discards_write_buffer_in_sync_mode + IO.pipe do |r, w| + w.sync = false + w.write("buffered") + + # Enabling sync marks the write buffer as non-authoritative, so close + # must abandon the pending bytes rather than replaying them. + w.sync = true + assert_nothing_raised { w.close } + + assert_equal("", r.read) + end + end + + def test_close_flushes_write_buffer_when_not_sync + IO.pipe do |r, w| + w.sync = false + w.write("data") + w.close + assert_equal("data", r.read) + end + end + + def test_sync_write_is_not_lost_on_close + IO.pipe do |r, w| + w.sync = true + payload = "x" * 200_000 + reader = Thread.new { r.read } + w.write(payload) + w.close + assert_equal(payload, reader.value) + end + end + def test_flush_in_finalizer1 bug3910 = '[ruby-dev:42341]' tmp = Tempfile.open("bug3910") {|t|