diff --git a/NEWS.md b/NEWS.md index 117aae5736bcb0..17cb9ba982d613 100644 --- a/NEWS.md +++ b/NEWS.md @@ -216,7 +216,7 @@ The following bundled gems are updated. * rbs 3.9.5 * debug 1.11.0 * base64 0.3.0 -* bigdecimal 3.2.2 +* bigdecimal 3.3.1 * drb 2.2.3 * syslog 0.3.0 * csv 3.3.5 diff --git a/concurrent_set.c b/concurrent_set.c index 19b4b6c373559f..87279384eb8893 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -352,8 +352,7 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) VALUE rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) { - // Assume locking and barrier (which there is no assert for). - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); @@ -391,8 +390,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) void rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key, void *data), void *data) { - // Assume locking and barrier (which there is no assert for). - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); diff --git a/gc/default/default.c b/gc/default/default.c index c23adae0627988..af386a9793ae1d 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -6633,8 +6633,6 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ switch (event) { case gc_enter_event_rest: - if (!is_marking(objspace)) break; - // fall through case gc_enter_event_start: case gc_enter_event_continue: // stop other ractors diff --git a/gems/bundled_gems b/gems/bundled_gems index d8fa9642ce1eb8..9f49934794f930 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -25,7 +25,7 @@ racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong base64 0.3.0 https://github.com/ruby/base64 -bigdecimal 3.2.2 https://github.com/ruby/bigdecimal +bigdecimal 3.3.1 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.1.1 https://github.com/ruby/resolv-replace diff --git a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb index 45f5ebffc702f6..01772bf9af7d8e 100644 --- a/spec/ruby/library/bigdecimal/BigDecimal_spec.rb +++ b/spec/ruby/library/bigdecimal/BigDecimal_spec.rb @@ -156,8 +156,10 @@ BigDecimal("-12345.6E-1").should == -reference end - it "raises ArgumentError when Float is used without precision" do - -> { BigDecimal(1.0) }.should raise_error(ArgumentError) + version_is BigDecimal::VERSION, "3.3.0" do + it "allows Float without precision" do + BigDecimal(1.2).should == BigDecimal("1.2") + end end it "returns appropriate BigDecimal zero for signed zero" do @@ -259,8 +261,8 @@ def to_s; "cheese"; end end it "produces the expected result" do - @c.should == BigDecimal("-0.666667e-9") - @c.to_s.should == "-0.666667e-9" + @c.round(15).should == BigDecimal("-0.666667e-9") + @c.round(15).to_s.should == "-0.666667e-9" end it "produces the correct class for other arithmetic operators" do diff --git a/spec/ruby/library/bigdecimal/add_spec.rb b/spec/ruby/library/bigdecimal/add_spec.rb index 542713011d6a93..9cdab7d910844d 100644 --- a/spec/ruby/library/bigdecimal/add_spec.rb +++ b/spec/ruby/library/bigdecimal/add_spec.rb @@ -73,14 +73,6 @@ # BigDecimal("0.88").add(0.0, 1).should == BigDecimal("0.9") # end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.add(object, 1).should == BigDecimal("0.1E16") - end - end - describe "with Rational" do it "produces a BigDecimal" do (@three + Rational(500, 2)).should == BigDecimal("0.253e3") diff --git a/spec/ruby/library/bigdecimal/core_spec.rb b/spec/ruby/library/bigdecimal/core_spec.rb index acee4dcf564ea6..5097d708656b73 100644 --- a/spec/ruby/library/bigdecimal/core_spec.rb +++ b/spec/ruby/library/bigdecimal/core_spec.rb @@ -20,9 +20,12 @@ describe "BigDecimal#log" do it "handles high-precision Rational arguments" do - result = BigDecimal('0.22314354220170971436137296411949880462556361100856391620766259404746040597133837784E0') + # log(BigDecimal(r, 50), 50) + result1 = BigDecimal('0.22314354220170971436137296411949880462556361100856e0') + # log(BigDecimal(r, 1000), 50) + result2 = BigDecimal('0.22314354220170971436137296411949880462556361100853e0') r = Rational(1_234_567_890, 987_654_321) - BigMath.log(r, 50).should == result + [result1, result2].should include(BigMath.log(r, 50).mult(1, 50)) end end diff --git a/spec/ruby/library/bigdecimal/divmod_spec.rb b/spec/ruby/library/bigdecimal/divmod_spec.rb index 294f01cba01484..c519783d145a00 100644 --- a/spec/ruby/library/bigdecimal/divmod_spec.rb +++ b/spec/ruby/library/bigdecimal/divmod_spec.rb @@ -154,12 +154,19 @@ class BigDecimal end end - it "returns an array of zero and the dividend if the divisor is Infinity" do - @regular_vals.each do |val| - array = val.divmod(@infinity) - array.length.should == 2 - array[0].should == @zero - array[1].should == val + version_is BigDecimal::VERSION, "3.3.0" do + it "returns an array of zero and the dividend or minus one and Infinity if the divisor is Infinity" do + @regular_vals.each do |val| + array = val.divmod(@infinity) + array.length.should == 2 + if val >= 0 + array[0].should == @zero + array[1].should == val + else + array[0].should == @one_minus + array[1].should == @infinity + end + end end end diff --git a/spec/ruby/library/bigdecimal/mult_spec.rb b/spec/ruby/library/bigdecimal/mult_spec.rb index b7f8044b0b2202..2353df9cb8e9a9 100644 --- a/spec/ruby/library/bigdecimal/mult_spec.rb +++ b/spec/ruby/library/bigdecimal/mult_spec.rb @@ -21,12 +21,4 @@ @e.mult(@one, 1).should be_close(@one, @tolerance) @e3_minus.mult(@one, 1).should be_close(0, @tolerance2) end - - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@e3_minus).and_return([@e3_minus, @e3_plus]) - @e3_minus.mult(object, 1).should == BigDecimal("9") - end - end end diff --git a/spec/ruby/library/bigdecimal/remainder_spec.rb b/spec/ruby/library/bigdecimal/remainder_spec.rb index bac5f37ba968c1..0eb06f7ef1d402 100644 --- a/spec/ruby/library/bigdecimal/remainder_spec.rb +++ b/spec/ruby/library/bigdecimal/remainder_spec.rb @@ -37,9 +37,11 @@ @neg_int.remainder(@pos_frac).should == @neg_int - @pos_frac * (@neg_int / @pos_frac).truncate end - it "returns NaN used with zero" do - @mixed.remainder(@zero).should.nan? - @zero.remainder(@zero).should.nan? + version_is BigDecimal::VERSION, "3.3.0" do + it "raises ZeroDivisionError used with zero" do + -> { @mixed.remainder(@zero) }.should raise_error(ZeroDivisionError) + -> { @zero.remainder(@zero) }.should raise_error(ZeroDivisionError) + end end it "returns zero if used on zero" do diff --git a/spec/ruby/library/bigdecimal/shared/modulo.rb b/spec/ruby/library/bigdecimal/shared/modulo.rb index aa5c5a640b2cc2..eeb030fd23c05c 100644 --- a/spec/ruby/library/bigdecimal/shared/modulo.rb +++ b/spec/ruby/library/bigdecimal/shared/modulo.rb @@ -101,10 +101,16 @@ @infinity_minus.send(@method, @infinity).should.nan? end - it "returns the dividend if the divisor is Infinity" do - @one.send(@method, @infinity).should == @one - @one.send(@method, @infinity_minus).should == @one - @frac_2.send(@method, @infinity_minus).should == @frac_2 + version_is BigDecimal::VERSION, "3.3.0" do + it "returns the dividend if the divisor is Infinity and signs are same" do + @one.send(@method, @infinity).should == @one + (-@frac_2).send(@method, @infinity_minus).should == -@frac_2 + end + + it "returns the divisor if the divisor is Infinity and signs are different" do + (-@one).send(@method, @infinity).should == @infinity + @frac_2.send(@method, @infinity_minus).should == @infinity_minus + end end it "raises TypeError if the argument cannot be coerced to BigDecimal" do diff --git a/spec/ruby/library/bigdecimal/shared/power.rb b/spec/ruby/library/bigdecimal/shared/power.rb index 568a08589b494f..6dafb638e27b22 100644 --- a/spec/ruby/library/bigdecimal/shared/power.rb +++ b/spec/ruby/library/bigdecimal/shared/power.rb @@ -10,8 +10,8 @@ e = BigDecimal("1.00000000000000000000123456789") one = BigDecimal("1") ten = BigDecimal("10") - # The tolerance is dependent upon the size of BASE_FIG - tolerance = BigDecimal("1E-70") + # Accuracy is at least ndigits(== 30) + DOUBLE_FIG(== 16) + tolerance = BigDecimal("1E-46") ten_powers = BigDecimal("1E10000") pi = BigDecimal("3.14159265358979") e3_minus.send(@method, 2).should == e3_minus_power_2 diff --git a/spec/ruby/library/bigdecimal/sub_spec.rb b/spec/ruby/library/bigdecimal/sub_spec.rb index bddfec2186d6a4..3b62a0c794b510 100644 --- a/spec/ruby/library/bigdecimal/sub_spec.rb +++ b/spec/ruby/library/bigdecimal/sub_spec.rb @@ -35,14 +35,6 @@ @frac_1.sub(@frac_1, 1000000).should == @zero end - describe "with Object" do - it "tries to coerce the other operand to self" do - object = mock("Object") - object.should_receive(:coerce).with(@frac_3).and_return([@frac_3, @frac_4]) - @frac_3.sub(object, 1).should == BigDecimal("-0.9E15") - end - end - describe "with Rational" do it "produces a BigDecimal" do (@three - Rational(500, 2)).should == BigDecimal('-0.247e3') diff --git a/string.c b/string.c index 331f48313600f4..0ee0ab744856d7 100644 --- a/string.c +++ b/string.c @@ -600,8 +600,7 @@ rb_obj_is_fstring_table(VALUE obj) void rb_gc_free_fstring(VALUE obj) { - // Assume locking and barrier (which there is no assert for) - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); rb_concurrent_set_delete_by_identity(fstring_table_obj, obj); diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index c6dbc01dc2e129..faf717096a4c77 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1153,6 +1153,14 @@ def test = [1,2,3] } end + def test_array_fixnum_aref + assert_compiles '3', %q{ + def test(x) = [1,2,3][x] + test(2) + test(2) + }, call_threshold: 2, insns: [:opt_aref] + end + def test_new_range_inclusive assert_compiles '1..5', %q{ def test(a, b) = a..b diff --git a/vm_sync.c b/vm_sync.c index ba311a00e97838..6d93720701bad9 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -25,6 +25,20 @@ RUBY_ASSERT_vm_locking(void) } } +void +RUBY_ASSERT_vm_locking_with_barrier(void) +{ + if (rb_multi_ractor_p()) { + rb_vm_t *vm = GET_VM(); + VM_ASSERT(vm_locked(vm)); + + if (vm->ractor.cnt > 1) { + /* Written to only when holding both ractor.sync and ractor.sched lock */ + VM_ASSERT(vm->ractor.sched.barrier_waiting); + } + } +} + void RUBY_ASSERT_vm_unlocking(void) { diff --git a/vm_sync.h b/vm_sync.h index 457be2c6b888aa..15dfff4d0b9fe1 100644 --- a/vm_sync.h +++ b/vm_sync.h @@ -142,11 +142,14 @@ rb_vm_lock_leave_cr(struct rb_ractor_struct *cr, unsigned int *levp, const char #if RUBY_DEBUG > 0 void RUBY_ASSERT_vm_locking(void); +void RUBY_ASSERT_vm_locking_with_barrier(void); void RUBY_ASSERT_vm_unlocking(void); #define ASSERT_vm_locking() RUBY_ASSERT_vm_locking() +#define ASSERT_vm_locking_with_barrier() RUBY_ASSERT_vm_locking_with_barrier() #define ASSERT_vm_unlocking() RUBY_ASSERT_vm_unlocking() #else #define ASSERT_vm_locking() +#define ASSERT_vm_locking_with_barrier() #define ASSERT_vm_unlocking() #endif diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 64b235b838baff..b40986c0f6e154 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -125,6 +125,7 @@ fn main() { .allowlist_function("rb_ary_unshift_m") .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") + .allowlist_function("rb_ary_entry") .allowlist_function("rb_class_attached_object") .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4b9331e05b0892..35791bc0d7ae2f 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -355,6 +355,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), + Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), &Insn::ObjectAllocClass { class, state } => gen_object_alloc_class(asm, class, &function.frame_state(state)), Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)), @@ -1241,6 +1242,16 @@ fn gen_new_array( new_array } +/// Compile array access (array[index]) +fn gen_aref_fixnum( + asm: &mut Assembler, + array: Opnd, + index: Opnd, +) -> lir::Opnd { + let unboxed_idx = asm.rshift(index, Opnd::UImm(1)); + asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) +} + /// Compile a new hash instruction fn gen_new_hash( jit: &mut JITState, diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index ab442841ff267a..ea1bf68acc4173 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -786,6 +786,7 @@ unsafe extern "C" { pub fn rb_ary_resurrect(ary: VALUE) -> VALUE; pub fn rb_ary_cat(ary: VALUE, train: *const VALUE, len: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_push(ary: VALUE, elem: VALUE) -> VALUE; + pub fn rb_ary_entry(ary: VALUE, off: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_ary_concat(lhs: VALUE, rhs: VALUE) -> VALUE; pub fn rb_hash_new() -> VALUE; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 2fe8eb79700349..8837457afaadee 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -577,6 +577,7 @@ pub enum Insn { ArrayExtend { left: InsnId, right: InsnId, state: InsnId }, /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, + ArrayArefFixnum { array: InsnId, index: InsnId }, HashDup { val: InsnId, state: InsnId }, @@ -888,6 +889,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } + Insn::ArrayArefFixnum { array, index, .. } => { + write!(f, "ArrayArefFixnum {array}, {index}") + } Insn::NewHash { elements, .. } => { write!(f, "NewHash")?; let mut prefix = " "; @@ -1579,6 +1583,7 @@ impl Function { &NewHash { ref elements, state } => NewHash { elements: find_vec!(elements), state: find!(state) }, &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, + &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, @@ -1664,6 +1669,7 @@ impl Function { Insn::ToRegexp { .. } => types::RegexpExact, Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, + Insn::ArrayArefFixnum { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, Insn::HashDup { .. } => types::HashExact, Insn::NewRange { .. } => types::RangeExact, @@ -1969,6 +1975,16 @@ impl Function { } } } + if self.type_of(idx_val).is_subtype(types::Fixnum) { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); + let fixnum_idx = self.push_insn(block, Insn::GuardType { val: idx_val, guard_type: types::Fixnum, state }); + let result = self.push_insn(block, Insn::ArrayArefFixnum { + array: self_val, + index: fixnum_idx, + }); + self.make_equal_to(orig_insn_id, result); + return; + } } self.push_insn_id(block, orig_insn_id); } @@ -2687,6 +2703,10 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + &Insn::ArrayArefFixnum { array, index } => { + worklist.push_back(array); + worklist.push_back(index); + } &Insn::Send { recv, ref args, state, .. } | &Insn::SendForward { recv, ref args, state, .. } | &Insn::SendWithoutBlock { recv, ref args, state, .. } @@ -12564,4 +12584,34 @@ mod opt_tests { Return v23 "); } + + #[test] + fn test_array_aref_fixnum() { + eval(" + def test + arr = [1, 2, 3] + arr[0] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) + v30:BasicObject = ArrayArefFixnum v15, v18 + CheckInterrupts + Return v30 + "); + } }