diff --git a/compar.c b/compar.c index 0fb7e5f6584e73..142cb12a0cfe77 100644 --- a/compar.c +++ b/compar.c @@ -95,10 +95,13 @@ cmpint(VALUE x, VALUE y) /* * call-seq: - * obj > other -> true or false + * self > other -> true or false * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value greater than 0. + * Returns whether +self+ is "greater than" +other+; + * equivalent to (self <=> other) > 0: + * + * 'foo' > 'foo' # => false + * 'food' > 'foo' # => true */ static VALUE diff --git a/enumerator.c b/enumerator.c index 292ddb0419fb8a..b60036a3dedc17 100644 --- a/enumerator.c +++ b/enumerator.c @@ -34,79 +34,93 @@ /* * Document-class: Enumerator * - * A class which allows both internal and external iteration. + * \Class \Enumerator supports: * - * An Enumerator can be created by the following methods. - * - Object#to_enum - * - Object#enum_for - * - Enumerator.new + * - {External iteration}[rdoc-ref:Enumerator@External+Iteration]. + * - {Internal iteration}[rdoc-ref:Enumerator@Internal+Iteration]. * - * Most methods have two forms: a block form where the contents - * are evaluated for each item in the enumeration, and a non-block form - * which returns a new Enumerator wrapping the iteration. + * An \Enumerator may be created by the following methods: * - * enumerator = %w(one two three).each - * puts enumerator.class # => Enumerator + * - Object#to_enum. + * - Object#enum_for. + * - Enumerator.new. * - * enumerator.each_with_object("foo") do |item, obj| - * puts "#{obj}: #{item}" - * end + * In addition, certain Ruby methods return \Enumerator objects: + * a Ruby iterator method that accepts a block + * may return an \Enumerator if no block is given. + * There are many such methods, for example, in classes Array and Hash. + * (In the documentation for those classes, search for `new_enumerator`.) * - * # foo: one - * # foo: two - * # foo: three + * == Internal Iteration * - * enum_with_obj = enumerator.each_with_object("foo") - * puts enum_with_obj.class # => Enumerator + * In _internal iteration_, an iterator method drives the iteration + * and the caller's block handles the processing; + * this example uses method #each_with_index: * - * enum_with_obj.each do |item, obj| - * puts "#{obj}: #{item}" - * end + * words = %w[foo bar baz] # => ["foo", "bar", "baz"] + * enumerator = words.each # => # + * enumerator.each_with_index {|word, i| puts "#{i}: #{word}" } + * 0: foo + * 1: bar + * 2: baz * - * # foo: one - * # foo: two - * # foo: three + * Iterator methods in class \Enumerator include: * - * This allows you to chain Enumerators together. For example, you - * can map a list's elements to strings containing the index - * and the element as a string via: + * - #each: + * passes each item to the block. + * - #each_with_index: + * passes each item and its index to the block. + * - #each_with_object (aliased as #with_object): + * passes each item and a given object to the block. + * - #with_index: + * like #each_with_index, but starting at a given offset (instead of zero). * - * puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" } - * # => ["0:foo", "1:bar", "2:baz"] + * \Class \Enumerator includes module Enumerable, + * which provides many more iterator methods. * * == External Iteration * - * An Enumerator can also be used as an external iterator. - * For example, Enumerator#next returns the next value of the iterator - * or raises StopIteration if the Enumerator is at the end. - * - * e = [1,2,3].each # returns an enumerator object. - * puts e.next # => 1 - * puts e.next # => 2 - * puts e.next # => 3 - * puts e.next # raises StopIteration - * - * +next+, +next_values+, +peek+, and +peek_values+ are the only methods - * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+ internally). - * - * These methods do not affect other internal enumeration methods, - * unless the underlying iteration method itself has side-effect, e.g. IO#each_line. - * - * FrozenError will be raised if these methods are called against a frozen enumerator. - * Since +rewind+ and +feed+ also change state for external iteration, - * these methods may raise FrozenError too. - * - * External iteration differs *significantly* from internal iteration - * due to using a Fiber: - * - The Fiber adds some overhead compared to internal enumeration. - * - The stacktrace will only include the stack from the Enumerator, not above. - * - Fiber-local variables are *not* inherited inside the Enumerator Fiber, - * which instead starts with no Fiber-local variables. - * - Fiber storage variables *are* inherited and are designed - * to handle Enumerator Fibers. Assigning to a Fiber storage variable - * only affects the current Fiber, so if you want to change state - * in the caller Fiber of the Enumerator Fiber, you need to use an - * extra indirection (e.g., use some object in the Fiber storage + * In _external iteration_, the user's program both drives the iteration + * and handles the processing in stream-like fashion; + * this example uses method #next: + * + * words = %w[foo bar baz] + * enumerator = words.each + * enumerator.next # => "foo" + * enumerator.next # => "bar" + * enumerator.next # => "baz" + * enumerator.next # Raises StopIteration: iteration reached an end + * + * External iteration methods in class \Enumerator include: + * + * - #feed: + * sets the value that is next to be returned. + * - #next: + * returns the next value and increments the position. + * - #next_values: + * returns the next value in a 1-element array and increments the position. + * - #peek: + * returns the next value but does not increment the position. + * - #peek_values: + * returns the next value in a 1-element array but does not increment the position. + * - #rewind: + * sets the position to zero. + * + * Each of these methods raises FrozenError if called from a frozen \Enumerator. + * + * == External Iteration and \Fiber + * + * External iteration that uses Fiber differs *significantly* from internal iteration: + * + * - Using \Fiber adds some overhead compared to internal enumeration. + * - The stacktrace will only include the stack from the \Enumerator, not above. + * - \Fiber-local variables are *not* inherited inside the \Enumerator \Fiber, + * which instead starts with no \Fiber-local variables. + * - \Fiber storage variables *are* inherited and are designed + * to handle \Enumerator Fibers. Assigning to a \Fiber storage variable + * only affects the current \Fiber, so if you want to change state + * in the caller \Fiber of the \Enumerator \Fiber, you need to use an + * extra indirection (e.g., use some object in the \Fiber storage * variable and mutate some ivar of it). * * Concretely: @@ -126,7 +140,7 @@ * e.each { p _1 } * p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber) * - * == Convert External Iteration to Internal Iteration + * == Converting External Iteration to Internal Iteration * * You can use an external iterator to implement an internal iterator as follows: * @@ -445,28 +459,31 @@ convert_to_feasible_size_value(VALUE obj) /* * call-seq: - * Enumerator.new(size = nil) { |yielder| ... } + * Enumerator.new(size = nil) {|yielder| ... } * - * Creates a new Enumerator object, which can be used as an - * Enumerable. + * Returns a new \Enumerator object that can be used for iteration. * - * Iteration is defined by the given block, in - * which a "yielder" object, given as block parameter, can be used to - * yield a value by calling the +yield+ method (aliased as <<): + * The given block defines the iteration; + * it is called with a "yielder" object that can yield an object + * via a call to method yielder.yield: * - * fib = Enumerator.new do |y| - * a = b = 1 - * loop do - * y << a - * a, b = b, a + b + * fib = Enumerator.new do |yielder| + * n = next_n = 1 + * while true do + * yielder.yield(n) + * n, next_n = next_n, n + next_n * end * end * * fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] * - * The optional parameter can be used to specify how to calculate the size - * in a lazy fashion (see Enumerator#size). It can either be a value or - * a callable object. + * Parameter +size+ specifies how the size is to be calculated (see #size); + * it can either be a value or a callable object: + * + * Enumerator.new{}.size # => nil + * Enumerator.new(42){}.size # => 42 + * Enumerator.new(-> {42}){}.size # => 42 + * */ static VALUE enumerator_initialize(int argc, VALUE *argv, VALUE obj) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 8b3045fda322c4..ba755b1ad60f71 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -336,35 +336,6 @@ count_symbols(int argc, VALUE *argv, VALUE os) return hash; } -/* - * call-seq: - * ObjectSpace.count_nodes([result_hash]) -> hash - * - * Counts nodes for each node type. - * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: - * - * {:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...} - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * Note: - * The contents of the returned hash is implementation defined. - * It may be changed in future. - * - * This method is only expected to work with C Ruby. - */ - -static VALUE -count_nodes(int argc, VALUE *argv, VALUE os) -{ - return setup_hash(argc, argv); -} - static void cto_i(VALUE v, void *data) { @@ -834,7 +805,6 @@ Init_objspace(void) rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1); rb_define_module_function(rb_mObjSpace, "count_symbols", count_symbols, -1); - rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1); rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1); rb_define_module_function(rb_mObjSpace, "count_imemo_objects", count_imemo_objects, -1); diff --git a/hash.c b/hash.c index 28b729516474a7..c1cc13383b701b 100644 --- a/hash.c +++ b/hash.c @@ -4971,10 +4971,9 @@ rb_hash_ge(VALUE hash, VALUE other) /* * call-seq: - * self > other_hash -> true or false + * self > other -> true or false * - * Returns +true+ if the entries of +self+ are a proper superset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a proper superset of the entries of +other+: * * h = {foo: 0, bar: 1, baz: 2} * h > {foo: 0, bar: 1} # => true # Proper superset. diff --git a/numeric.c b/numeric.c index ef44febe1b29cd..e8df2a6aa0568c 100644 --- a/numeric.c +++ b/numeric.c @@ -1638,7 +1638,8 @@ rb_float_cmp(VALUE x, VALUE y) * call-seq: * self > other -> true or false * - * Returns +true+ if +self+ is numerically greater than +other+: + * Returns whether the value of +self+ is greater than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 > 1 # => true * 2.0 > 1.0 # => true @@ -4958,7 +4959,8 @@ fix_gt(VALUE x, VALUE y) * call-seq: * self > other -> true or false * - * Returns +true+ if the value of +self+ is greater than that of +other+: + * Returns whether the value of +self+ is greater than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 > 0 # => true * 1 > 1 # => false diff --git a/object.c b/object.c index 2b3535158fe305..7dda2b6c0d49cd 100644 --- a/object.c +++ b/object.c @@ -2009,14 +2009,23 @@ rb_mod_ge(VALUE mod, VALUE arg) /* * call-seq: - * mod > other -> true, false, or nil + * self > other -> true, false, or nil * - * Returns true if mod is an ancestor of other. Returns - * false if mod is the same as other - * or mod is a descendant of other. - * Returns nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "B > A".) + * If +self+ is a class, returns +true+ if +self+ is a superclass of +other+, + * returns +false+ if +self+ is the same as +other+ or if +self+ is a subclass + * of +other+, and returns +nil+ if there are no relationship between the two: + * + * Numeric > Float # => true + * Float > Numeric # => false + * Float > Float # => false + * Float > Hash # => nil + * + * If +self+ is a module, returns +true+ if +other+ includes +self+, + * returns +false+ if +self+ is the same as +other+ or if +self+ includes + * +other+, and returns +nil+ if there are no relationship between the two: + * + * Enumerable > Array # => true + * Enumerable > String # => nil * */ diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 2c0c6195e6ba46..78947a095bf44c 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -76,16 +76,6 @@ def test_count_objects_size_with_wrong_type assert_raise(TypeError) { ObjectSpace.count_objects_size(0) } end - def test_count_nodes - res = ObjectSpace.count_nodes - assert_not_empty(res) - arg = {} - ObjectSpace.count_nodes(arg) - assert_not_empty(arg) - bug8014 = '[ruby-core:53130] [Bug #8014]' - assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014) - end if false - def test_count_tdata_objects res = ObjectSpace.count_tdata_objects assert_not_empty(res) diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 6488661c2d3a6c..53b056d3359c85 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -657,6 +657,26 @@ def foo(obj) RUBY end + def test_struct_aset_guards_recv_is_not_frozen + assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 }) + def foo(obj) + obj.foo = 123 + end + + Foo = Struct.new(:foo) + obj = Foo.new(123) + 100.times do + foo(obj) + end + obj.freeze + begin + foo(obj) + rescue FrozenError + :ok + end + RUBY + end + def test_getblockparam assert_compiles(<<~'RUBY', insns: [:getblockparam]) def foo &blk diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests index 6d9ecb40992e26..4bcb5707a51d9d 100644 --- a/tool/rbs_skip_tests +++ b/tool/rbs_skip_tests @@ -37,6 +37,9 @@ TestInstanceNetHTTPResponse depending on external resources test_TOPDIR(RbConfigSingletonTest) `TOPDIR` is `nil` during CI while RBS type is declared as `String` +# Failing because ObjectSpace.count_nodes has been removed +test_count_nodes(ObjectSpaceTest) + ## Unknown failures # NoMethodError: undefined method 'inspect' for an instance of RBS::UnitTest::Convertibles::ToInt diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 0fc39322fb13b6..1cf8247a2a905a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -8973,6 +8973,17 @@ fn gen_struct_aset( assert!(unsafe { RB_TYPE_P(comptime_recv, RUBY_T_STRUCT) }); assert!((off as i64) < unsafe { RSTRUCT_LEN(comptime_recv) }); + // Even if the comptime recv was not frozen, future recv may be. So we need to emit a guard + // that the recv is not frozen. + // We know all structs are heap objects, so we can check the flag directly. + let recv = asm.stack_opnd(1); + let recv = asm.load(recv); + let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS)); + asm.test(flags, (RUBY_FL_FREEZE as u64).into()); + asm.jnz(Target::side_exit(Counter::opt_aset_frozen)); + + // Not frozen, so we can proceed. + asm_comment!(asm, "struct aset"); let val = asm.stack_pop(1); diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 84549fa5d34963..105def2fff8577 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -489,6 +489,7 @@ make_counters! { opt_aset_not_array, opt_aset_not_fixnum, opt_aset_not_hash, + opt_aset_frozen, opt_case_dispatch_megamorphic,