diff options
-rw-r--r-- | .github/workflows/windows.yml | 2 | ||||
-rw-r--r-- | NEWS.md | 20 | ||||
-rw-r--r-- | common.mk | 15 | ||||
-rw-r--r-- | defs/gmake.mk | 2 | ||||
-rw-r--r-- | doc/globals.rdoc | 2 | ||||
-rw-r--r-- | enumerator.c | 94 | ||||
-rw-r--r-- | file.c | 2 | ||||
-rw-r--r-- | gc.c | 43 | ||||
-rw-r--r-- | gc/README.md | 9 | ||||
-rw-r--r-- | gc/default/default.c | 66 | ||||
-rw-r--r-- | gc/gc.h | 19 | ||||
-rw-r--r-- | gc/mmtk/mmtk.c | 18 | ||||
-rw-r--r-- | object.c | 56 | ||||
-rw-r--r-- | prism/templates/lib/prism/visitor.rb.erb | 2 | ||||
-rw-r--r-- | ractor.c | 7 | ||||
-rw-r--r-- | re.c | 2 | ||||
-rw-r--r-- | scheduler.c | 5 | ||||
-rw-r--r-- | set.c | 1 | ||||
-rw-r--r-- | shape.c | 14 | ||||
-rw-r--r-- | spec/ruby/core/file/birthtime_spec.rb | 13 | ||||
-rw-r--r-- | spec/ruby/core/file/stat/birthtime_spec.rb | 7 | ||||
-rw-r--r-- | spec/ruby/core/kernel/inspect_spec.rb | 30 | ||||
-rw-r--r-- | test/ruby/test_object.rb | 13 | ||||
-rw-r--r-- | test/ruby/test_set.rb | 6 | ||||
-rw-r--r-- | test/ruby/test_zjit.rb | 42 | ||||
-rwxr-xr-x | tool/fetch-bundled_gems.rb | 14 | ||||
-rw-r--r-- | variable.c | 33 | ||||
-rw-r--r-- | variable.h | 1 | ||||
-rw-r--r-- | zjit/src/codegen.rs | 20 | ||||
-rw-r--r-- | zjit/src/hir.rs | 147 |
30 files changed, 535 insertions, 170 deletions
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6c8f09660d..fa3d2f659e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -71,7 +71,7 @@ jobs: bundler: none windows-toolchain: none - - name: Install libraries with scoop + - name: Install tools with scoop run: | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser iwr -useb get.scoop.sh | iex @@ -14,6 +14,26 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Kernel + + * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, + allowing control over which instance variables are displayed in the `#inspect` string: + + ```ruby + class DatabaseConfig + def initialize(host, user, password) + @host = host + @user = user + @password = password + end + + private def instance_variables_to_inspect = [:@host, :@user] + end + + conf = DatabaseConfig.new("localhost", "root", "hunter2") + conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root"> + ``` + * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -1562,18 +1562,6 @@ extract-gems$(sequential): PHONY extract-gems$(sequential): $(HAVE_GIT:yes=clone-bundled-gems-src) -clone-bundled-gems-src: PHONY - $(Q) $(BASERUBY) -C "$(srcdir)" \ - -Itool/lib -rbundled_gem -answ \ - -e 'BEGIN {git = $$git}' \ - -e 'gem, _, repo, rev = *$$F' \ - -e 'next if !rev or /^#/=~gem' \ - -e 'gemdir = "gems/src/#{gem}"' \ - -e 'BundledGem.checkout(gemdir, repo, rev, git: git)' \ - -e 'BundledGem.dummy_gemspec("#{gemdir}/#{gem}.gemspec")' \ - -- -git="$(GIT)" \ - gems/bundled_gems - outdate-bundled-gems: PHONY $(Q) $(BASERUBY) $(tooldir)/[email protected] --make="$(MAKE)" --mflags="$(MFLAGS)" \ --ruby-platform=$(arch) --ruby-version=$(ruby_version) \ @@ -1623,7 +1611,8 @@ yes-install-for-test-bundled-gems: yes-update-default-gemspecs "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" test-bundled-gems-fetch: yes-test-bundled-gems-fetch -yes-test-bundled-gems-fetch: +yes-test-bundled-gems-fetch: clone-bundled-gems-src +clone-bundled-gems-src: PHONY $(Q) $(BASERUBY) -C $(srcdir)/gems ../tool/fetch-bundled_gems.rb BUNDLED_GEMS="$(BUNDLED_GEMS)" src bundled_gems no-test-bundled-gems-fetch: diff --git a/defs/gmake.mk b/defs/gmake.mk index 87fc8021b2..a81d82eadd 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -365,7 +365,7 @@ $(srcdir)/.bundle/.timestamp: define build-gem $(srcdir)/gems/src/$(1)/.git: | $(srcdir)/gems/src $(ECHO) Cloning $(4) - $(Q) $(GIT) clone $(4) $$(@D) + $(Q) $(GIT) clone --depth=1 --no-tags $(4) $$(@D) $(bundled-gem-revision): \ $(if $(if $(wildcard $$(@)),$(filter $(3),$(shell cat $$(@)))),,PHONY) \ diff --git a/doc/globals.rdoc b/doc/globals.rdoc index 9d9fc57e6e..9466005be7 100644 --- a/doc/globals.rdoc +++ b/doc/globals.rdoc @@ -137,7 +137,7 @@ English - <tt>$DEFAULT_INPUT</tt>. An output stream, initially <tt>$stdout</tt>. -English - <tt>$DEFAULT_OUTPUT +English - <tt>$DEFAULT_OUTPUT</tt> === <tt>$.</tt> (Input Position) diff --git a/enumerator.c b/enumerator.c index faaa77cb49..b91b2eb940 100644 --- a/enumerator.c +++ b/enumerator.c @@ -162,10 +162,9 @@ */ VALUE rb_cEnumerator; static VALUE rb_cLazy; -static ID id_rewind, id_new, id_to_enum, id_each_entry; +static ID id_rewind, id_to_enum, id_each_entry; static ID id_next, id_result, id_receiver, id_arguments, id_memo, id_method, id_force; -static ID id_begin, id_end, id_step, id_exclude_end; -static VALUE sym_each, sym_cycle, sym_yield; +static VALUE sym_each, sym_yield; static VALUE lazy_use_super_method; @@ -3748,6 +3747,55 @@ enumerator_s_product(int argc, VALUE *argv, VALUE klass) return obj; } +struct arith_seq { + struct enumerator enumerator; + VALUE begin; + VALUE end; + VALUE step; + bool exclude_end; +}; + +RUBY_REFERENCES(arith_seq_refs) = { + RUBY_REF_EDGE(struct enumerator, obj), + RUBY_REF_EDGE(struct enumerator, args), + RUBY_REF_EDGE(struct enumerator, fib), + RUBY_REF_EDGE(struct enumerator, dst), + RUBY_REF_EDGE(struct enumerator, lookahead), + RUBY_REF_EDGE(struct enumerator, feedvalue), + RUBY_REF_EDGE(struct enumerator, stop_exc), + RUBY_REF_EDGE(struct enumerator, size), + RUBY_REF_EDGE(struct enumerator, procs), + + RUBY_REF_EDGE(struct arith_seq, begin), + RUBY_REF_EDGE(struct arith_seq, end), + RUBY_REF_EDGE(struct arith_seq, step), + RUBY_REF_END +}; + +static const rb_data_type_t arith_seq_data_type = { + "arithmetic_sequence", + { + RUBY_REFS_LIST_PTR(arith_seq_refs), + RUBY_TYPED_DEFAULT_FREE, + NULL, // Nothing allocated externally, so don't need a memsize function + NULL, + }, + .parent = &enumerator_data_type, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE +}; + +static VALUE +arith_seq_allocate(VALUE klass) +{ + struct arith_seq *ptr; + VALUE enum_obj; + + enum_obj = TypedData_Make_Struct(klass, struct arith_seq, &arith_seq_data_type, ptr); + ptr->enumerator.obj = Qundef; + + return enum_obj; +} + /* * Document-class: Enumerator::ArithmeticSequence * @@ -3765,12 +3813,16 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, rb_enumerator_size_func *size_fn, VALUE beg, VALUE end, VALUE step, int excl) { - VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq), + VALUE aseq = enumerator_init(arith_seq_allocate(rb_cArithSeq), obj, meth, argc, argv, size_fn, Qnil, rb_keyword_given_p()); - rb_ivar_set(aseq, id_begin, beg); - rb_ivar_set(aseq, id_end, end); - rb_ivar_set(aseq, id_step, step); - rb_ivar_set(aseq, id_exclude_end, RBOOL(excl)); + struct arith_seq *ptr; + TypedData_Get_Struct(aseq, struct arith_seq, &enumerator_data_type, ptr); + + RB_OBJ_WRITE(aseq, &ptr->begin, beg); + RB_OBJ_WRITE(aseq, &ptr->end, end); + RB_OBJ_WRITE(aseq, &ptr->step, step); + ptr->exclude_end = excl; + return aseq; } @@ -3783,7 +3835,9 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, static inline VALUE arith_seq_begin(VALUE self) { - return rb_ivar_get(self, id_begin); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->begin; } /* @@ -3794,7 +3848,9 @@ arith_seq_begin(VALUE self) static inline VALUE arith_seq_end(VALUE self) { - return rb_ivar_get(self, id_end); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->end; } /* @@ -3806,7 +3862,9 @@ arith_seq_end(VALUE self) static inline VALUE arith_seq_step(VALUE self) { - return rb_ivar_get(self, id_step); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->step; } /* @@ -3817,13 +3875,17 @@ arith_seq_step(VALUE self) static inline VALUE arith_seq_exclude_end(VALUE self) { - return rb_ivar_get(self, id_exclude_end); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return RBOOL(ptr->exclude_end); } static inline int arith_seq_exclude_end_p(VALUE self) { - return RTEST(arith_seq_exclude_end(self)); + struct arith_seq *ptr; + TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr); + return ptr->exclude_end; } int @@ -4664,7 +4726,6 @@ void Init_Enumerator(void) { id_rewind = rb_intern_const("rewind"); - id_new = rb_intern_const("new"); id_next = rb_intern_const("next"); id_result = rb_intern_const("result"); id_receiver = rb_intern_const("receiver"); @@ -4674,12 +4735,7 @@ Init_Enumerator(void) id_force = rb_intern_const("force"); id_to_enum = rb_intern_const("to_enum"); id_each_entry = rb_intern_const("each_entry"); - id_begin = rb_intern_const("begin"); - id_end = rb_intern_const("end"); - id_step = rb_intern_const("step"); - id_exclude_end = rb_intern_const("exclude_end"); sym_each = ID2SYM(id_each); - sym_cycle = ID2SYM(rb_intern_const("cycle")); sym_yield = ID2SYM(rb_intern_const("yield")); InitVM(Enumerator); @@ -543,7 +543,7 @@ rb_stat_new(const struct stat *st) if (st) { #if RUBY_USE_STATX # define CP(m) .stx_ ## m = st->st_ ## m -# define CP_32(m) .stx_ ## m = (__u32)st->st_ ## m +# define CP_32(m) .stx_ ## m = (uint32_t)st->st_ ## m # define CP_TS(m) .stx_ ## m = stat_ ## m ## spec(st) rb_st->stat = (struct statx){ .stx_mask = STATX_BASIC_STATS, @@ -131,45 +131,45 @@ #include "shape.h" unsigned int -rb_gc_vm_lock(void) +rb_gc_vm_lock(const char *file, int line) { unsigned int lev = 0; - RB_VM_LOCK_ENTER_LEV(&lev); + rb_vm_lock_enter(&lev, file, line); return lev; } void -rb_gc_vm_unlock(unsigned int lev) +rb_gc_vm_unlock(unsigned int lev, const char *file, int line) { - RB_VM_LOCK_LEAVE_LEV(&lev); + rb_vm_lock_leave(&lev, file, line); } unsigned int -rb_gc_cr_lock(void) +rb_gc_cr_lock(const char *file, int line) { unsigned int lev; - RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev); + rb_vm_lock_enter_cr(GET_RACTOR(), &lev, file, line); return lev; } void -rb_gc_cr_unlock(unsigned int lev) +rb_gc_cr_unlock(unsigned int lev, const char *file, int line) { - RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev); + rb_vm_lock_leave_cr(GET_RACTOR(), &lev, file, line); } unsigned int -rb_gc_vm_lock_no_barrier(void) +rb_gc_vm_lock_no_barrier(const char *file, int line) { unsigned int lev = 0; - RB_VM_LOCK_ENTER_LEV_NB(&lev); + rb_vm_lock_enter_nb(&lev, file, line); return lev; } void -rb_gc_vm_unlock_no_barrier(unsigned int lev) +rb_gc_vm_unlock_no_barrier(unsigned int lev, const char *file, int line) { - RB_VM_LOCK_LEAVE_LEV_NB(&lev); + rb_vm_lock_leave_nb(&lev, file, line); } void @@ -1783,9 +1783,9 @@ generate_next_object_id(void) // 64bit atomics are available return SIZET2NUM(RUBY_ATOMIC_SIZE_FETCH_ADD(object_id_counter, 1) * OBJ_ID_INCREMENT); #else - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); VALUE id = ULL2NUM(++object_id_counter * OBJ_ID_INCREMENT); - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); return id; #endif } @@ -1867,7 +1867,7 @@ class_object_id(VALUE klass) { VALUE id = RUBY_ATOMIC_VALUE_LOAD(RCLASS(klass)->object_id); if (!id) { - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); id = generate_next_object_id(); VALUE existing_id = RUBY_ATOMIC_VALUE_CAS(RCLASS(klass)->object_id, 0, id); if (existing_id) { @@ -1876,7 +1876,7 @@ class_object_id(VALUE klass) else if (RB_UNLIKELY(id2ref_tbl)) { st_insert(id2ref_tbl, id, klass); } - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); } return id; } @@ -1946,9 +1946,9 @@ object_id(VALUE obj) } if (UNLIKELY(rb_gc_multi_ractor_p() && rb_ractor_shareable_p(obj))) { - unsigned int lock_lev = rb_gc_vm_lock(); + unsigned int lock_lev = RB_GC_VM_LOCK(); VALUE id = object_id0(obj); - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); return id; } @@ -1983,7 +1983,7 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) { rb_objspace_t *objspace = objspace_ptr; - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); if (!id2ref_tbl) { rb_gc_vm_barrier(); // stop other ractors @@ -2007,7 +2007,7 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) VALUE obj; bool found = st_lookup(id2ref_tbl, object_id, &obj) && !rb_gc_impl_garbage_object_p(objspace, obj); - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); if (found) { return obj; @@ -4093,7 +4093,8 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) ); } else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { + uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID((VALUE)key)); + for (uint32_t i = 0; i < fields_count; i++) { if (SPECIAL_CONST_P(fields_tbl->as.shape.fields[i])) continue; int ivar_ret = iter_data->callback(fields_tbl->as.shape.fields[i], iter_data->data); diff --git a/gc/README.md b/gc/README.md index 102b24e24e..cb71357973 100644 --- a/gc/README.md +++ b/gc/README.md @@ -15,12 +15,17 @@ Two GC implementations are included in Ruby: > [!IMPORTANT] > Ruby's modular GC feature is experimental and subject to change. There may be bugs or performance impacts. Use at your own risk. +### Building Ruby with Modular GC + 1. Configure Ruby with the `--with-modular-gc=<dir>` option, where `dir` is the directory you want to place the built GC libraries into. 2. Build Ruby as usual. -3. Build your desired GC implementation with `make install-modular-gc MODULAR_GC=<impl>`. This will build the GC implementation and place the built library into the `dir` specified in step 1. `impl` can be one of: + +### Building GC implementations shipped with Ruby + +1. Build your desired GC implementation with `make install-modular-gc MODULAR_GC=<impl>`. This will build the GC implementation and place the built library into the `dir` specified in step 1. `impl` can be one of: - `default`: The default GC that Ruby ships with. - `mmtk`: The GC that uses [MMTk](https://www.mmtk.io/) as the back-end. See Ruby-specific details in the [ruby/mmtk](https://github.com/ruby/mmtk) repository. -4. Run your desired GC implementation by setting the `RUBY_GC_LIBRARY=<lib>` environment variable, where `lib` could be `default`, `mmtk`, or your own implementation (as long as you place it in the `dir` specified in step 1). +2. Run your desired GC implementation by setting the `RUBY_GC_LIBRARY=<lib>` environment variable, where `lib` could be `default`, `mmtk`, or your own implementation (as long as you place it in the `dir` specified in step 1). ## Modular GC API diff --git a/gc/default/default.c b/gc/default/default.c index 5664b3dd90..40d39d6f17 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1229,7 +1229,7 @@ check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int ter { int err = 0; - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (SPECIAL_CONST_P(obj)) { fprintf(stderr, "check_rvalue_consistency: %p is a special const.\n", (void *)obj); @@ -1319,7 +1319,7 @@ check_rvalue_consistency_force(rb_objspace_t *objspace, const VALUE obj, int ter } } } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); if (err > 0 && terminate) { rb_bug("check_rvalue_consistency_force: there is %d errors.", err); @@ -2140,7 +2140,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, #if RGENGC_CHECK_MODE newobj_fill(obj, 0, 0, 0); - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { check_rvalue_consistency(objspace, obj); @@ -2151,7 +2151,7 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, if (RVALUE_REMEMBERED(objspace, obj)) rb_bug("newobj: %s is remembered.", rb_obj_info(obj)); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); #endif if (RB_UNLIKELY(wb_protected == FALSE)) { @@ -2363,7 +2363,7 @@ newobj_cache_miss(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size bool unlock_vm = false; if (!vm_locked) { - lev = rb_gc_cr_lock(); + lev = RB_GC_CR_LOCK(); unlock_vm = true; } @@ -2387,7 +2387,7 @@ newobj_cache_miss(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size } if (unlock_vm) { - rb_gc_cr_unlock(lev); + RB_GC_CR_UNLOCK(lev); } if (RB_UNLIKELY(obj == Qfalse)) { @@ -2416,7 +2416,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_new VALUE obj; unsigned int lev; - lev = rb_gc_cr_lock(); + lev = RB_GC_CR_LOCK(); { if (RB_UNLIKELY(during_gc || ruby_gc_stressful)) { if (during_gc) { @@ -2438,7 +2438,7 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, rb_ractor_new obj = newobj_alloc(objspace, cache, heap_idx, true); newobj_init(klass, flags, wb_protected, objspace, obj); } - rb_gc_cr_unlock(lev); + RB_GC_CR_UNLOCK(lev); return obj; } @@ -2753,7 +2753,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) RBASIC(obj)->flags |= FL_FINALIZE; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (st_lookup(finalizer_table, obj, &data)) { table = (VALUE)data; @@ -2766,7 +2766,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) for (i = 0; i < len; i++) { VALUE recv = RARRAY_AREF(table, i); if (rb_equal(recv, block)) { - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return recv; } } @@ -2780,7 +2780,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) st_add_direct(finalizer_table, obj, table); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return block; } @@ -2794,9 +2794,9 @@ rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj) st_data_t data = obj; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); st_delete(finalizer_table, &data, 0); - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); FL_UNSET(obj, FL_FINALIZE); } @@ -2810,7 +2810,7 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) if (!FL_TEST(obj, FL_FINALIZE)) return; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (RB_LIKELY(st_lookup(finalizer_table, obj, &data))) { table = rb_ary_dup((VALUE)data); RARRAY_ASET(table, 0, rb_obj_id(dest)); @@ -2820,7 +2820,7 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) else { rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj)); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } static VALUE @@ -2864,7 +2864,7 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) next_zombie = RZOMBIE(zombie)->next; page = GET_HEAP_PAGE(zombie); - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); run_final(objspace, zombie); { @@ -2878,7 +2878,7 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie) heap_page_add_freeobj(objspace, page, zombie); page->heap->total_freed_objects++; } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); zombie = next_zombie; } @@ -3247,7 +3247,7 @@ read_barrier_handler(uintptr_t address) rb_bug("read_barrier_handler: segmentation fault at %p", (void *)address); } - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); { unlock_page_body(objspace, page_body); @@ -3255,7 +3255,7 @@ read_barrier_handler(uintptr_t address) invalidate_moved_page(objspace, GET_HEAP_PAGE(address)); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } #endif @@ -5180,7 +5180,7 @@ gc_verify_internal_consistency(void *objspace_ptr) { rb_objspace_t *objspace = objspace_ptr; - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); { rb_gc_vm_barrier(); // stop other ractors @@ -5191,7 +5191,7 @@ gc_verify_internal_consistency(void *objspace_ptr) } during_gc = prev_during_gc; } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } static void @@ -5952,11 +5952,11 @@ gc_writebarrier_generational(VALUE a, VALUE b, rb_objspace_t *objspace) /* mark `a' and remember (default behavior) */ if (!RVALUE_REMEMBERED(objspace, a)) { - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { rgengc_remember(objspace, a); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); gc_report(1, objspace, "gc_writebarrier_generational: %s (remembered) -> %s\n", rb_obj_info(a), rb_obj_info(b)); } @@ -6029,7 +6029,7 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) else { bool retry = false; /* slow path */ - int lev = rb_gc_vm_lock_no_barrier(); + int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (is_incremental_marking(objspace)) { gc_writebarrier_incremental(a, b, objspace); @@ -6038,7 +6038,7 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) retry = true; } } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); if (retry) goto retry; } @@ -6057,7 +6057,7 @@ rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj) gc_report(2, objspace, "rb_gc_writebarrier_unprotect: %s %s\n", rb_obj_info(obj), RVALUE_REMEMBERED(objspace, obj) ? " (already remembered)" : ""); - unsigned int lev = rb_gc_vm_lock_no_barrier(); + unsigned int lev = RB_GC_VM_LOCK_NO_BARRIER(); { if (RVALUE_OLD_P(objspace, obj)) { gc_report(1, objspace, "rb_gc_writebarrier_unprotect: %s\n", rb_obj_info(obj)); @@ -6079,7 +6079,7 @@ rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj) RB_DEBUG_COUNTER_INC(obj_wb_unprotect); MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(obj), obj); } - rb_gc_vm_unlock_no_barrier(lev); + RB_GC_VM_UNLOCK_NO_BARRIER(lev); } } @@ -6292,7 +6292,7 @@ garbage_collect(rb_objspace_t *objspace, unsigned int reason) { int ret; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); { #if GC_PROFILE_MORE_DETAIL objspace->profile.prepare_time = getrusage_time(); @@ -6306,7 +6306,7 @@ garbage_collect(rb_objspace_t *objspace, unsigned int reason) ret = gc_start(objspace, reason); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return ret; } @@ -6590,7 +6590,7 @@ gc_clock_end(struct timespec *ts) static inline void gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_lev) { - *lock_lev = rb_gc_vm_lock(); + *lock_lev = RB_GC_VM_LOCK(); switch (event) { case gc_enter_event_rest: @@ -6629,7 +6629,7 @@ gc_exit(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_l gc_report(1, objspace, "gc_exit: %s [%s]\n", gc_enter_event_cstr(event), gc_current_status(objspace)); during_gc = FALSE; - rb_gc_vm_unlock(*lock_lev); + RB_GC_VM_UNLOCK(*lock_lev); } #ifndef MEASURE_GC @@ -9106,7 +9106,7 @@ gc_verify_compaction_references(int argc, VALUE* argv, VALUE self) /* Clear the heap. */ rb_gc_impl_start(objspace, true, true, true, false); - unsigned int lev = rb_gc_vm_lock(); + unsigned int lev = RB_GC_VM_LOCK(); { gc_rest(objspace); @@ -9162,7 +9162,7 @@ gc_verify_compaction_references(int argc, VALUE* argv, VALUE self) objspace->rcompactor.compare_func = compare_free_slots; } } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); rb_gc_impl_start(rb_gc_get_objspace(), true, true, true, true); @@ -35,6 +35,13 @@ enum rb_gc_vm_weak_tables { RB_GC_VM_WEAK_TABLE_COUNT }; +#define RB_GC_VM_LOCK() rb_gc_vm_lock(__FILE__, __LINE__) +#define RB_GC_VM_UNLOCK(lev) rb_gc_vm_unlock(lev, __FILE__, __LINE__) +#define RB_GC_CR_LOCK() rb_gc_cr_lock(__FILE__, __LINE__) +#define RB_GC_CR_UNLOCK(lev) rb_gc_cr_unlock(lev, __FILE__, __LINE__) +#define RB_GC_VM_LOCK_NO_BARRIER() rb_gc_vm_lock_no_barrier(__FILE__, __LINE__) +#define RB_GC_VM_UNLOCK_NO_BARRIER(lev) rb_gc_vm_unlock_no_barrier(lev, __FILE__, __LINE__) + #if USE_MODULAR_GC # define MODULAR_GC_FN #else @@ -57,12 +64,12 @@ size_t rb_obj_memsize_of(VALUE obj); bool ruby_free_at_exit_p(void); void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data); -MODULAR_GC_FN unsigned int rb_gc_vm_lock(void); -MODULAR_GC_FN void rb_gc_vm_unlock(unsigned int lev); -MODULAR_GC_FN unsigned int rb_gc_cr_lock(void); -MODULAR_GC_FN void rb_gc_cr_unlock(unsigned int lev); -MODULAR_GC_FN unsigned int rb_gc_vm_lock_no_barrier(void); -MODULAR_GC_FN void rb_gc_vm_unlock_no_barrier(unsigned int lev); +MODULAR_GC_FN unsigned int rb_gc_vm_lock(const char *file, int line); +MODULAR_GC_FN void rb_gc_vm_unlock(unsigned int lev, const char *file, int line); +MODULAR_GC_FN unsigned int rb_gc_cr_lock(const char *file, int line); +MODULAR_GC_FN void rb_gc_cr_unlock(unsigned int lev, const char *file, int line); +MODULAR_GC_FN unsigned int rb_gc_vm_lock_no_barrier(const char *file, int line); +MODULAR_GC_FN void rb_gc_vm_unlock_no_barrier(unsigned int lev, const char *file, int line); MODULAR_GC_FN void rb_gc_vm_barrier(void); MODULAR_GC_FN size_t rb_gc_obj_optimal_size(VALUE obj); MODULAR_GC_FN void rb_gc_mark_children(void *objspace, VALUE obj); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 9e4ee9f3de..c318c6fe48 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -129,7 +129,7 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) struct objspace *objspace = rb_gc_get_objspace(); size_t starting_gc_count = objspace->gc_count; - int lock_lev = rb_gc_vm_lock(); + int lock_lev = RB_GC_VM_LOCK(); int err; if ((err = pthread_mutex_lock(&objspace->mutex)) != 0) { rb_bug("ERROR: cannot lock objspace->mutex: %s", strerror(err)); @@ -173,7 +173,7 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) if ((err = pthread_mutex_unlock(&objspace->mutex)) != 0) { rb_bug("ERROR: cannot release objspace->mutex: %s", strerror(err)); } - rb_gc_vm_unlock(lock_lev); + RB_GC_VM_UNLOCK(lock_lev); } static size_t @@ -927,7 +927,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) RBASIC(obj)->flags |= FL_FINALIZE; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (st_lookup(objspace->finalizer_table, obj, &data)) { table = (VALUE)data; @@ -940,7 +940,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) for (i = 0; i < len; i++) { VALUE recv = RARRAY_AREF(table, i); if (rb_equal(recv, block)) { - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return recv; } } @@ -954,7 +954,7 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) st_add_direct(objspace->finalizer_table, obj, table); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); return block; } @@ -966,9 +966,9 @@ rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj) st_data_t data = obj; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); st_delete(objspace->finalizer_table, &data, 0); - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); FL_UNSET(obj, FL_FINALIZE); } @@ -982,7 +982,7 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) if (!FL_TEST(obj, FL_FINALIZE)) return; - int lev = rb_gc_vm_lock(); + int lev = RB_GC_VM_LOCK(); if (RB_LIKELY(st_lookup(objspace->finalizer_table, obj, &data))) { table = rb_ary_dup((VALUE)data); RARRAY_ASET(table, 0, rb_obj_id(dest)); @@ -992,7 +992,7 @@ rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) else { rb_bug("rb_gc_copy_finalizer: FL_FINALIZE set but not found in finalizer_table: %s", rb_obj_info(obj)); } - rb_gc_vm_unlock(lev); + RB_GC_VM_UNLOCK(lev); } static int @@ -83,6 +83,7 @@ static VALUE rb_cFalseClass_to_s; #define id_init_dup idInitialize_dup #define id_const_missing idConst_missing #define id_to_f idTo_f +static ID id_instance_variables_to_inspect; #define CLASS_OR_MODULE_P(obj) \ (!SPECIAL_CONST_P(obj) && \ @@ -733,11 +734,17 @@ rb_inspect(VALUE obj) static int inspect_i(ID id, VALUE value, st_data_t a) { - VALUE str = (VALUE)a; + VALUE *args = (VALUE *)a, str = args[0], ivars = args[1]; /* need not to show internal data */ if (CLASS_OF(value) == 0) return ST_CONTINUE; if (!rb_is_instance_id(id)) return ST_CONTINUE; + if (!NIL_P(ivars)) { + VALUE name = ID2SYM(id); + for (long i = 0; RARRAY_AREF(ivars, i) != name; ) { + if (++i >= RARRAY_LEN(ivars)) return ST_CONTINUE; + } + } if (RSTRING_PTR(str)[0] == '-') { /* first element */ RSTRING_PTR(str)[0] = '#'; rb_str_cat2(str, " "); @@ -752,13 +759,15 @@ inspect_i(ID id, VALUE value, st_data_t a) } static VALUE -inspect_obj(VALUE obj, VALUE str, int recur) +inspect_obj(VALUE obj, VALUE a, int recur) { + VALUE *args = (VALUE *)a, str = args[0]; + if (recur) { rb_str_cat2(str, " ..."); } else { - rb_ivar_foreach(obj, inspect_i, str); + rb_ivar_foreach(obj, inspect_i, a); } rb_str_cat2(str, ">"); RSTRING_PTR(str)[0] = '#'; @@ -791,17 +800,47 @@ inspect_obj(VALUE obj, VALUE str, int recur) * end * end * Bar.new.inspect #=> "#<Bar:0x0300c868 @bar=1>" + * + * If _obj_ responds to +instance_variables_to_inspect+, then only + * the instance variables listed in the returned array will be included + * in the inspect string. + * + * + * class DatabaseConfig + * def initialize(host, user, password) + * @host = host + * @user = user + * @password = password + * end + * + * private + * def instance_variables_to_inspect = [:@host, :@user] + * end + * + * conf = DatabaseConfig.new("localhost", "root", "hunter2") + * conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root"> */ static VALUE rb_obj_inspect(VALUE obj) { - if (rb_ivar_count(obj) > 0) { - VALUE str; + VALUE ivars = rb_check_funcall(obj, id_instance_variables_to_inspect, 0, 0); + st_index_t n = 0; + if (UNDEF_P(ivars)) { + n = rb_ivar_count(obj); + ivars = Qnil; + } + else if (!NIL_P(ivars)) { + Check_Type(ivars, T_ARRAY); + n = RARRAY_LEN(ivars); + } + if (n > 0) { VALUE c = rb_class_name(CLASS_OF(obj)); - - str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj); - return rb_exec_recursive(inspect_obj, obj, str); + VALUE args[2] = { + rb_sprintf("-<%"PRIsVALUE":%p", c, (void*)obj), + ivars + }; + return rb_exec_recursive(inspect_obj, obj, (VALUE)args); } else { return rb_any_to_s(obj); @@ -4600,6 +4639,7 @@ void Init_Object(void) { id_dig = rb_intern_const("dig"); + id_instance_variables_to_inspect = rb_intern_const("instance_variables_to_inspect"); InitVM(Object); } diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index a1eac38dc4..b1a03c3f1a 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -34,7 +34,7 @@ module Prism # # class FooCalls < Prism::Visitor # def visit_call_node(node) - # if node.name == "foo" + # if node.name == :foo # # Do something with the node # end # @@ -1658,10 +1658,9 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) if (d.stop) return 1; } else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (!UNDEF_P(fields_tbl->as.shape.fields[i])) { - CHECK_AND_REPLACE(fields_tbl->as.shape.fields[i]); - } + uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + for (uint32_t i = 0; i < fields_count; i++) { + CHECK_AND_REPLACE(fields_tbl->as.shape.fields[i]); } } } @@ -1666,7 +1666,7 @@ rb_reg_prepare_re(VALUE re, VALUE str) RSTRING_GETMEM(unescaped, ptr, len); /* If there are no other users of this regex, then we can directly overwrite it. */ - if (RREGEXP(re)->usecnt == 0) { + if (ruby_single_main_ractor && RREGEXP(re)->usecnt == 0) { regex_t tmp_reg; r = onig_new_without_alloc(&tmp_reg, (UChar *)ptr, (UChar *)(ptr + len), reg->options, enc, diff --git a/scheduler.c b/scheduler.c index 80c0278933..11faca01d3 100644 --- a/scheduler.c +++ b/scheduler.c @@ -1061,9 +1061,8 @@ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*functi operation->data2 = NULL; operation->unblock_function = NULL; - // If the blocking operation was never executed, return Qundef to signal - // the caller to use rb_nogvl instead - if (current_status != RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED) { + // If the blocking operation was never executed, return Qundef to signal the caller to use rb_nogvl instead + if (current_status == RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED) { return Qundef; } @@ -528,6 +528,7 @@ set_i_initialize_copy(VALUE set, VALUE other) set_free_embedded(sobj); set_copy(&sobj->table, RSET_TABLE(other)); + rb_gc_writebarrier_remember(set); return set; } @@ -1391,12 +1391,14 @@ static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE va static VALUE edges(VALUE edges) { VALUE hash = rb_hash_new(); - if (SINGLE_CHILD_P(edges)) { - rb_shape_t *child = SINGLE_CHILD(edges); - collect_keys_and_values(child->edge_name, (VALUE)child, &hash); - } - else { - rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash); + if (edges) { + if (SINGLE_CHILD_P(edges)) { + rb_shape_t *child = SINGLE_CHILD(edges); + collect_keys_and_values(child->edge_name, (VALUE)child, &hash); + } + else { + rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash); + } } return hash; } diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb index ff43aa7cef..f82eaf7cca 100644 --- a/spec/ruby/core/file/birthtime_spec.rb +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -1,6 +1,11 @@ require_relative '../../spec_helper' platform_is :windows, :darwin, :freebsd, :netbsd, :linux do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] + describe "File.birthtime" do before :each do @file = __FILE__ @@ -14,20 +19,20 @@ platform_is :windows, :darwin, :freebsd, :netbsd, :linux do File.birthtime(@file) File.birthtime(@file).should be_kind_of(Time) rescue NotImplementedError => e - skip e.message if e.message.start_with?("birthtime() function") + e.message.should.start_with?(*not_implemented_messages) end it "accepts an object that has a #to_path method" do File.birthtime(@file) # Avoid to failure of mock object with old Kernel and glibc File.birthtime(mock_to_path(@file)) rescue NotImplementedError => e - e.message.should.start_with?("birthtime() function") + e.message.should.start_with?(*not_implemented_messages) end it "raises an Errno::ENOENT exception if the file is not found" do -> { File.birthtime('bogus') }.should raise_error(Errno::ENOENT) rescue NotImplementedError => e - e.message.should.start_with?("birthtime() function") + e.message.should.start_with?(*not_implemented_messages) end end @@ -45,7 +50,7 @@ platform_is :windows, :darwin, :freebsd, :netbsd, :linux do @file.birthtime @file.birthtime.should be_kind_of(Time) rescue NotImplementedError => e - e.message.should.start_with?("birthtime() function") + e.message.should.start_with?(*not_implemented_messages) end end end diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index 5350a571aa..adecee15b0 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -3,6 +3,11 @@ require_relative '../../../spec_helper' platform_is(:windows, :darwin, :freebsd, :netbsd, *ruby_version_is("3.5") { :linux }, ) do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] + describe "File::Stat#birthtime" do before :each do @file = tmp('i_exist') @@ -18,7 +23,7 @@ platform_is(:windows, :darwin, :freebsd, :netbsd, st.birthtime.should be_kind_of(Time) st.birthtime.should <= Time.now rescue NotImplementedError => e - e.message.should.start_with?("birthtime() function") + e.message.should.start_with?(*not_implemented_messages) end end end diff --git a/spec/ruby/core/kernel/inspect_spec.rb b/spec/ruby/core/kernel/inspect_spec.rb index 1f9ce834ab..e60f7576c5 100644 --- a/spec/ruby/core/kernel/inspect_spec.rb +++ b/spec/ruby/core/kernel/inspect_spec.rb @@ -28,4 +28,34 @@ describe "Kernel#inspect" do end obj.inspect.should be_kind_of(String) end + + ruby_version_is "3.5" do + it "calls #instance_variables_to_inspect private method to know which variables to display" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = %i[@host @user @does_not_exist] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == '#<Object:0x00 @host="localhost", @user="root">' + + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = [] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == "#<Object:0x00>" + end + end end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 7d00422629..9074e54df5 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -950,6 +950,19 @@ class TestObject < Test::Unit::TestCase assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect) x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6) assert_match(/@\u{3046}=6\b/, x.inspect) + + x = Object.new + x.singleton_class.class_eval do + private def instance_variables_to_inspect = [:@host, :@user] + end + + x.instance_variable_set(:@host, "localhost") + x.instance_variable_set(:@user, "root") + x.instance_variable_set(:@password, "hunter2") + s = x.inspect + assert_include(s, "@host=\"localhost\"") + assert_include(s, "@user=\"root\"") + assert_not_include(s, "@password=") end def test_singleton_methods diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 2bb7858eb2..3a8568762a 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -130,6 +130,12 @@ class TC_Set < Test::Unit::TestCase assert_equal(Set['a','b','c'], set) set = Set[1,2] + ret = set.replace(Set.new('a'..'c')) + + assert_same(set, ret) + assert_equal(Set['a','b','c'], set) + + set = Set[1,2] assert_raise(ArgumentError) { set.replace(3) } diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 3b64887f92..47a9f6f7dc 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -205,6 +205,48 @@ class TestZJIT < Test::Unit::TestCase }, insns: [:opt_gt], call_threshold: 2 end + def test_opt_empty_p + assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p]) + def test(x) = x.empty? + return test([1]), test("1"), test({}) + RUBY + end + + def test_opt_succ + assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ]) + def test(obj) = obj.succ + return test(-1), test("A") + RUBY + end + + def test_opt_and + assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and]) + def test(x, y) = x & y + return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3]) + RUBY + end + + def test_opt_or + assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or]) + def test(x, y) = x | y + return test(0b1000, 3), test([3, 2, 1], [1, 2, 3]) + RUBY + end + + def test_opt_not + assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) + def test(obj) = !obj + return test(nil), test(false), test(0) + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2]) + def test(haystack) = /needle/ =~ haystack + return test("kneedle"), test("") + RUBY + end + def test_opt_ge assert_compiles '[false, true, true]', %q{ def test(a, b) = a >= b diff --git a/tool/fetch-bundled_gems.rb b/tool/fetch-bundled_gems.rb index f50bda360a..b76feefd94 100755 --- a/tool/fetch-bundled_gems.rb +++ b/tool/fetch-bundled_gems.rb @@ -24,20 +24,22 @@ next unless n next if n =~ /^#/ next if bundled_gems&.all? {|pat| !File.fnmatch?(pat, n)} -if File.directory?(n) - puts "updating #{color.notice(n)} ..." - system("git", "fetch", "--all", chdir: n) or abort -else +unless File.exist?("#{n}/.git") puts "retrieving #{color.notice(n)} ..." - system(*%W"git clone #{u} #{n}") or abort + system(*%W"git clone --depth=1 --no-tags #{u} #{n}") or abort end if r puts "fetching #{color.notice(r)} ..." system("git", "fetch", "origin", r, chdir: n) or abort + c = r +else + c = ["v#{v}", v].find do |c| + puts "fetching #{color.notice(c)} ..." + system("git", "fetch", "origin", "refs/tags/#{c}:refs/tags/#{c}", chdir: n) + end or abort end -c = r || "v#{v}" checkout = %w"git -c advice.detachedHead=false checkout" print %[checking out #{color.notice(c)} (v=#{color.info(v)}] print %[, r=#{color.info(r)}] if r diff --git a/variable.c b/variable.c index 6d0e9832e7..a54bebcec0 100644 --- a/variable.c +++ b/variable.c @@ -1228,19 +1228,10 @@ gen_fields_tbl_bytes(size_t n) } static struct gen_fields_tbl * -gen_fields_tbl_resize(struct gen_fields_tbl *old, uint32_t n) +gen_fields_tbl_resize(struct gen_fields_tbl *old, uint32_t new_capa) { - RUBY_ASSERT(n > 0); - - uint32_t len = old ? old->as.shape.fields_count : 0; - struct gen_fields_tbl *fields_tbl = xrealloc(old, gen_fields_tbl_bytes(n)); - - fields_tbl->as.shape.fields_count = n; - for (; len < n; len++) { - fields_tbl->as.shape.fields[len] = Qundef; - } - - return fields_tbl; + RUBY_ASSERT(new_capa > 0); + return xrealloc(old, gen_fields_tbl_bytes(new_capa)); } void @@ -1253,7 +1244,8 @@ rb_mark_generic_ivar(VALUE obj) rb_mark_tbl_no_pin(fields_tbl->as.complex.table); } else { - for (uint32_t i = 0; i < fields_tbl->as.shape.fields_count; i++) { + uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + for (uint32_t i = 0; i < fields_count; i++) { rb_gc_mark_movable(fields_tbl->as.shape.fields[i]); } } @@ -1290,7 +1282,7 @@ rb_generic_ivar_memsize(VALUE obj) return sizeof(struct gen_fields_tbl) + st_memsize(fields_tbl->as.complex.table); } else { - return gen_fields_tbl_bytes(fields_tbl->as.shape.fields_count); + return gen_fields_tbl_bytes(RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj))); } } return 0; @@ -1299,21 +1291,12 @@ rb_generic_ivar_memsize(VALUE obj) static size_t gen_fields_tbl_count(VALUE obj, const struct gen_fields_tbl *fields_tbl) { - uint32_t i; - size_t n = 0; - if (rb_shape_obj_too_complex_p(obj)) { - n = st_table_size(fields_tbl->as.complex.table); + return st_table_size(fields_tbl->as.complex.table); } else { - for (i = 0; i < fields_tbl->as.shape.fields_count; i++) { - if (!UNDEF_P(fields_tbl->as.shape.fields[i])) { - n++; - } - } + return RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); } - - return n; } VALUE diff --git a/variable.h b/variable.h index a95fcc563d..54b7fc5461 100644 --- a/variable.h +++ b/variable.h @@ -15,7 +15,6 @@ struct gen_fields_tbl { union { struct { - uint32_t fields_count; VALUE fields[1]; } shape; struct { diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0dbe815c71..e32534b283 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -275,6 +275,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined() Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(jit, asm, *cfun, args)?, Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), + Insn::SetGlobal { id, val, state: _ } => gen_setglobal(asm, *id, opnd!(val)), + Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), Insn::SetIvar { self_val, id, val, state: _ } => gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), _ => { debug!("ZJIT: gen_function: unexpected insn {:?}", insn); @@ -317,6 +319,24 @@ fn gen_setivar(asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd) -> Opnd { ) } +/// Look up global variables +fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd { + asm_comment!(asm, "call rb_gvar_get"); + asm.ccall( + rb_gvar_get as *const u8, + vec![Opnd::UImm(id.0)], + ) +} + +/// Set global variables +fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) -> Opnd { + asm_comment!(asm, "call rb_gvar_set"); + asm.ccall( + rb_gvar_set as *const u8, + vec![Opnd::UImm(id.0), val], + ) +} + /// Compile an interpreter entry block to be inserted into an ISEQ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 86e87d72ac..47b961badf 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -392,6 +392,11 @@ pub enum Insn { Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache }, + /// Get a global variable named `id` + GetGlobal { id: ID, state: InsnId }, + /// Set a global variable named `id` to `val` + SetGlobal { id: ID, val: InsnId, state: InsnId }, + //NewObject? /// Get an instance variable `id` from `self_val` GetIvar { self_val: InsnId, id: ID, state: InsnId }, @@ -459,7 +464,7 @@ impl Insn { Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_) | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } - | Insn::ArrayPush { .. } | Insn::SideExit { .. } => false, + | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } => false, _ => true, } } @@ -625,6 +630,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::DefinedIvar { self_val, id, .. } => write!(f, "DefinedIvar {self_val}, :{}", id.contents_lossy().into_owned()), Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy().into_owned()), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy().into_owned()), + Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy().into_owned()), + Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy().into_owned()), Insn::ToArray { val, .. } => write!(f, "ToArray {val}"), Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"), Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"), @@ -982,6 +989,8 @@ impl Function { } &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) }, + &GetGlobal { id, state } => GetGlobal { id, state }, + &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, &GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state }, &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val, state }, &ToArray { val, state } => ToArray { val: find!(val), state }, @@ -1012,7 +1021,7 @@ impl Function { assert!(self.insns[insn.0].has_output()); match &self.insns[insn.0] { Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), - Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_) + Insn::SetGlobal { .. } | Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_) | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } => @@ -1063,6 +1072,7 @@ impl Function { Insn::DefinedIvar { .. } => types::BasicObject, Insn::GetConstantPath { .. } => types::BasicObject, Insn::ArrayMax { .. } => types::BasicObject, + Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, @@ -1568,7 +1578,8 @@ impl Function { | Insn::Test { val } | Insn::IsNil { val } => worklist.push_back(val), - Insn::GuardType { val, state, .. } + Insn::SetGlobal { val, state, .. } + | Insn::GuardType { val, state, .. } | Insn::GuardBitEquals { val, state, .. } | Insn::ToArray { val, state } | Insn::ToNewArray { val, state } => { @@ -1635,6 +1646,7 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + Insn::GetGlobal { state, .. } | Insn::SideExit { state } => worklist.push_back(state), } } @@ -2355,6 +2367,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { break; // Don't enqueue the next block as a successor } + // These are opt_send_without_block and all the opt_* instructions + // specialized to a certain method that could also be serviced + // using the general send implementation. The optimizer start from + // a general send for all of these later in the pipeline. YARVINSN_opt_nil_p | YARVINSN_opt_plus | YARVINSN_opt_minus | @@ -2371,6 +2387,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { YARVINSN_opt_length | YARVINSN_opt_size | YARVINSN_opt_aref | + YARVINSN_opt_empty_p | + YARVINSN_opt_succ | + YARVINSN_opt_and | + YARVINSN_opt_or | + YARVINSN_opt_not | + YARVINSN_opt_regexpmatch2 | YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; @@ -2425,6 +2447,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let send = fun.push_insn(block, Insn::Send { self_val: recv, call_info: CallInfo { method_name }, cd, blockiseq, args, state: exit_id }); state.stack_push(send); } + YARVINSN_getglobal => { + let id = ID(get_arg(pc, 0).as_u64()); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let result = fun.push_insn(block, Insn::GetGlobal { id, state: exit_id }); + state.stack_push(result); + } + YARVINSN_setglobal => { + let id = ID(get_arg(pc, 0).as_u64()); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let val = state.stack_pop()?; + fun.push_insn(block, Insn::SetGlobal { id, val, state: exit_id }); + } YARVINSN_getinstancevariable => { let id = ID(get_arg(pc, 0).as_u64()); // ic is in arg 1 @@ -3702,6 +3736,35 @@ mod tests { } #[test] + fn test_setglobal() { + eval(" + def test = $foo = 1 + test + "); + assert_method_hir_with_opcode("test", YARVINSN_setglobal, expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + SetGlobal :$foo, v2 + Return v2 + "#]]); + } + + #[test] + fn test_getglobal() { + eval(" + def test = $foo + test + "); + assert_method_hir_with_opcode("test", YARVINSN_getglobal, expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:BasicObject = GetGlobal :$foo + Return v3 + "#]]); + } + + #[test] fn test_splatarray_mut() { eval(" def test(a) = [*a] @@ -3807,6 +3870,84 @@ mod tests { } #[test] + fn opt_empty_p() { + eval(" + def test(x) = x.empty? + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_empty_p, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:BasicObject = SendWithoutBlock v1, :empty? + Return v4 + "#]]); + } + + #[test] + fn opt_succ() { + eval(" + def test(x) = x.succ + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_succ, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:BasicObject = SendWithoutBlock v1, :succ + Return v4 + "#]]); + } + + #[test] + fn opt_and() { + eval(" + def test(x, y) = x & y + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_and, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + v5:BasicObject = SendWithoutBlock v1, :&, v2 + Return v5 + "#]]); + } + + #[test] + fn opt_or() { + eval(" + def test(x, y) = x | y + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_or, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + v5:BasicObject = SendWithoutBlock v1, :|, v2 + Return v5 + "#]]); + } + + #[test] + fn opt_not() { + eval(" + def test(x) = !x + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_not, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:BasicObject = SendWithoutBlock v1, :! + Return v4 + "#]]); + } + + #[test] + fn opt_regexpmatch2() { + eval(" + def test(regexp, matchee) = regexp =~ matchee + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_regexpmatch2, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + v5:BasicObject = SendWithoutBlock v1, :=~, v2 + Return v5 + "#]]); + } + + #[test] fn test_branchnil() { eval(" def test(x) = x&.itself |