diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 5e54d39e9fa6b9..8c0ca54e0b100b 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -244,7 +244,7 @@ jobs: - { uses: './.github/actions/compilers', name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' }, timeout-minutes: 5 } - { uses: './.github/actions/compilers', name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' }, timeout-minutes: 5 } - - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit' }, timeout-minutes: 5 } + - { uses: './.github/actions/compilers', name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 5 } compileX: name: 'omnibus compilations, #10' diff --git a/NEWS.md b/NEWS.md index 784b92b717d1c5..e7d1da919e631d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -234,30 +234,32 @@ releases. The following default gem is added. -* win32-registry 0.1.1 +* win32-registry 0.1.2 The following default gems are updated. * RubyGems 4.0.1 * bundler 4.0.1 -* date 3.5.0 +* date 3.5.1 * digest 3.2.1 * english 0.8.1 * erb 6.0.0 * etc 1.4.6 * fcntl 1.3.0 * fileutils 1.8.0 +* forwardable 1.4.0 * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.4.0.dev +* ipaddr 1.2.8 * json 2.17.1 * net-http 0.8.0 * openssl 4.0.0.pre -* optparse 0.8.0 +* optparse 0.8.1 * pp 0.6.3 * prism 1.6.0 -* psych 5.2.6 -* resolv 0.6.3 +* psych 5.3.0 +* resolv 0.7.0 * stringio 3.1.9.dev * strscan 3.1.6.dev * timeout 0.5.0 diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 81dc2d6b8d04be..13c4652d3760a8 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1494,6 +1494,9 @@ class C unless a[i].equal?(b[i]) raise [a[i], b[i]].inspect end + unless a[i] == i.to_s + raise [i, a[i], b[i]].inspect + end end :ok } diff --git a/box.c b/box.c index 0ce8d0aee02f0f..7907e0ff632a1e 100644 --- a/box.c +++ b/box.c @@ -760,7 +760,7 @@ static int cleanup_local_extension_i(VALUE key, VALUE value, VALUE arg) { #if defined(_WIN32) - HMODULE h = (HMODULE)NUM2SVALUE(value); + HMODULE h = (HMODULE)NUM2PTR(value); WCHAR module_path[MAXPATHLEN]; DWORD len = GetModuleFileNameW(h, module_path, numberof(module_path)); diff --git a/compile.c b/compile.c index a5d821eb810cf1..bcf22243cfc7af 100644 --- a/compile.c +++ b/compile.c @@ -610,8 +610,6 @@ branch_coverage_valid_p(rb_iseq_t *iseq, int first_line) return 1; } -#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - static VALUE setup_branch(const rb_code_location_t *loc, const char *type, VALUE structure, VALUE key) { diff --git a/concurrent_set.c b/concurrent_set.c index 3aa61507aaa7d2..234b6408b6b938 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -4,6 +4,9 @@ #include "ruby/atomic.h" #include "vm_sync.h" +#define CONCURRENT_SET_CONTINUATION_BIT ((VALUE)1 << (sizeof(VALUE) * CHAR_BIT - 1)) +#define CONCURRENT_SET_HASH_MASK (~CONCURRENT_SET_CONTINUATION_BIT) + enum concurrent_set_special_values { CONCURRENT_SET_EMPTY, CONCURRENT_SET_DELETED, @@ -24,6 +27,36 @@ struct concurrent_set { struct concurrent_set_entry *entries; }; +static void +concurrent_set_mark_continuation(struct concurrent_set_entry *entry, VALUE curr_hash_and_flags) +{ + if (curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT) return; + + RUBY_ASSERT((curr_hash_and_flags & CONCURRENT_SET_HASH_MASK) != 0); + + VALUE new_hash = curr_hash_and_flags | CONCURRENT_SET_CONTINUATION_BIT; + VALUE prev_hash = rbimpl_atomic_value_cas(&entry->hash, curr_hash_and_flags, new_hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + + // At the moment we only expect to be racing concurrently against another + // thread also setting the continuation bit. + // In the future if deletion is concurrent this will need adjusting + RUBY_ASSERT(prev_hash == curr_hash_and_flags || prev_hash == new_hash); + (void)prev_hash; +} + +static VALUE +concurrent_set_hash(const struct concurrent_set *set, VALUE key) +{ + VALUE hash = set->funcs->hash(key); + hash &= CONCURRENT_SET_HASH_MASK; + if (hash == 0) { + hash ^= CONCURRENT_SET_HASH_MASK; + } + RUBY_ASSERT(hash != 0); + RUBY_ASSERT(!(hash & CONCURRENT_SET_CONTINUATION_BIT)); + return hash; +} + static void concurrent_set_free(void *ptr) { @@ -134,20 +167,16 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj); for (int i = 0; i < old_capacity; i++) { - struct concurrent_set_entry *entry = &old_entries[i]; - VALUE key = rbimpl_atomic_value_exchange(&entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE); + struct concurrent_set_entry *old_entry = &old_entries[i]; + VALUE key = rbimpl_atomic_value_exchange(&old_entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(key != CONCURRENT_SET_MOVED); if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT) continue; if (!RB_SPECIAL_CONST_P(key) && rb_objspace_garbage_object_p(key)) continue; - VALUE hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - if (hash == 0) { - // Either in-progress insert or extremely unlikely 0 hash. - // Re-calculate the hash. - hash = old_set->funcs->hash(key); - } - RUBY_ASSERT(hash == old_set->funcs->hash(key)); + VALUE hash = rbimpl_atomic_value_load(&old_entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK; + RUBY_ASSERT(hash != 0); + RUBY_ASSERT(hash == concurrent_set_hash(old_set, key)); // Insert key into new_set. struct concurrent_set_probe probe; @@ -156,20 +185,19 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr) while (true) { struct concurrent_set_entry *entry = &new_set->entries[idx]; - if (entry->key == CONCURRENT_SET_EMPTY) { - new_set->size++; + if (entry->hash == CONCURRENT_SET_EMPTY) { + RUBY_ASSERT(entry->key == CONCURRENT_SET_EMPTY); + new_set->size++; RUBY_ASSERT(new_set->size <= new_set->capacity / 2); - RUBY_ASSERT(entry->hash == 0); entry->key = key; entry->hash = hash; break; } - else { - RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); - } + RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); + entry->hash |= CONCURRENT_SET_CONTINUATION_BIT; idx = concurrent_set_probe_next(&probe); } } @@ -194,29 +222,48 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) VALUE set_obj; VALUE hash = 0; + struct concurrent_set *set; + struct concurrent_set_probe probe; + int idx; retry: set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); - struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + set = RTYPEDDATA_GET_DATA(set_obj); if (hash == 0) { // We don't need to recompute the hash on every retry because it should // never change. - hash = set->funcs->hash(key); + hash = concurrent_set_hash(set, key); } - RUBY_ASSERT(hash == set->funcs->hash(key)); + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); - struct concurrent_set_probe probe; - int idx = concurrent_set_probe_start(&probe, set, hash); + idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE); + VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK; + bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT; + + if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) { + return 0; + } + + if (curr_hash != hash) { + if (!continuation) { + return 0; + } + idx = concurrent_set_probe_next(&probe); + continue; + } + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { case CONCURRENT_SET_EMPTY: - return 0; + // In-progress insert: hash written but key not yet + break; case CONCURRENT_SET_DELETED: break; case CONCURRENT_SET_MOVED: @@ -225,13 +272,9 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) goto retry; default: { - VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - if (curr_hash != 0 && curr_hash != hash) break; - if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) { // This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object. - // Skip it and mark it as deleted. - rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + // Skip it and let the GC pass clean it up break; } @@ -241,6 +284,10 @@ rb_concurrent_set_find(VALUE *set_obj_ptr, VALUE key) return curr_key; } + if (!continuation) { + return 0; + } + break; } } @@ -254,38 +301,65 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) { RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT); - bool inserting = false; - VALUE set_obj; - VALUE hash = 0; + // First attempt to find + { + VALUE result = rb_concurrent_set_find(set_obj_ptr, key); + if (result) return result; + } - retry: - set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); + // First time we need to call create, and store the hash + VALUE set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); RUBY_ASSERT(set_obj); - struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); - if (hash == 0) { - // We don't need to recompute the hash on every retry because it should - // never change. - hash = set->funcs->hash(key); - } - RUBY_ASSERT(hash == set->funcs->hash(key)); + struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); + key = set->funcs->create(key, data); + VALUE hash = concurrent_set_hash(set, key); struct concurrent_set_probe probe; - int idx = concurrent_set_probe_start(&probe, set, hash); + int idx; + + goto start_search; + +retry: + // On retries we only need to load the hash object + set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE); + RUBY_ASSERT(set_obj); + set = RTYPEDDATA_GET_DATA(set_obj); + + RUBY_ASSERT(hash == concurrent_set_hash(set, key)); + +start_search: + idx = concurrent_set_probe_start(&probe, set, hash); while (true) { struct concurrent_set_entry *entry = &set->entries[idx]; + VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE); + VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK; + bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT; + + if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) { + // Reserve this slot for our hash value + curr_hash_and_flags = rbimpl_atomic_value_cas(&entry->hash, CONCURRENT_SET_EMPTY, hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + if (curr_hash_and_flags != CONCURRENT_SET_EMPTY) { + // Lost race, retry same slot to check winner's hash + continue; + } + + // CAS succeeded, so these are the values stored + curr_hash_and_flags = hash; + curr_hash = hash; + + // Fall through to try to claim key + } + + if (curr_hash != hash) { + goto probe_next; + } + VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE); switch (curr_key) { case CONCURRENT_SET_EMPTY: { - // Not in set - if (!inserting) { - key = set->funcs->create(key, data); - RUBY_ASSERT(hash == set->funcs->hash(key)); - inserting = true; - } - rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED); // Load_factor reached at 75% full. ex: prev_size: 32, capacity: 64, load_factor: 50%. @@ -293,14 +367,12 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) if (UNLIKELY(load_factor_reached)) { concurrent_set_try_resize(set_obj, set_obj_ptr); - goto retry; } - curr_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); - if (curr_key == CONCURRENT_SET_EMPTY) { - rbimpl_atomic_value_store(&entry->hash, hash, RBIMPL_ATOMIC_RELAXED); - + VALUE prev_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + if (prev_key == CONCURRENT_SET_EMPTY) { + RUBY_ASSERT(rb_concurrent_set_find(set_obj_ptr, key) == key); RB_GC_GUARD(set_obj); return key; } @@ -317,41 +389,58 @@ rb_concurrent_set_find_or_insert(VALUE *set_obj_ptr, VALUE key, void *data) case CONCURRENT_SET_MOVED: // Wait RB_VM_LOCKING(); - goto retry; - default: { - VALUE curr_hash = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_RELAXED); - if (curr_hash != 0 && curr_hash != hash) break; - + default: + // We're never GC during our search + // If the continuation bit wasn't set at the start of our search, + // any concurrent find with the same hash value would also look at + // this location and try to swap curr_key if (UNLIKELY(!RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) { - // This is a weakref set, so after marking but before sweeping is complete we may find a matching garbage object. - // Skip it and mark it as deleted. - rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_DELETED, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); - break; + if (continuation) { + goto probe_next; + } + rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_EMPTY, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED); + continue; } if (set->funcs->cmp(key, curr_key)) { - // We've found a match. + // We've found a live match. RB_GC_GUARD(set_obj); - if (inserting) { - // We created key using set->funcs->create, but we didn't end - // up inserting it into the set. Free it here to prevent memory - // leaks. - if (set->funcs->free) set->funcs->free(key); - } + // We created key using set->funcs->create, but we didn't end + // up inserting it into the set. Free it here to prevent memory + // leaks. + if (set->funcs->free) set->funcs->free(key); return curr_key; } - break; - } } + probe_next: + RUBY_ASSERT(curr_hash_and_flags != CONCURRENT_SET_EMPTY); + concurrent_set_mark_continuation(entry, curr_hash_and_flags); idx = concurrent_set_probe_next(&probe); } } +static void +concurrent_set_delete_entry_locked(struct concurrent_set *set, struct concurrent_set_entry *entry) +{ + ASSERT_vm_locking_with_barrier(); + + if (entry->hash & CONCURRENT_SET_CONTINUATION_BIT) { + entry->hash = CONCURRENT_SET_CONTINUATION_BIT; + entry->key = CONCURRENT_SET_DELETED; + set->deleted_entries++; + } + else { + entry->hash = CONCURRENT_SET_EMPTY; + entry->key = CONCURRENT_SET_EMPTY; + set->size--; + } +} + VALUE rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) { @@ -359,7 +448,7 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) struct concurrent_set *set = RTYPEDDATA_GET_DATA(set_obj); - VALUE hash = set->funcs->hash(key); + VALUE hash = concurrent_set_hash(set, key); struct concurrent_set_probe probe; int idx = concurrent_set_probe_start(&probe, set, hash); @@ -379,8 +468,8 @@ rb_concurrent_set_delete_by_identity(VALUE set_obj, VALUE key) break; default: if (key == curr_key) { - entry->key = CONCURRENT_SET_DELETED; - set->deleted_entries++; + RUBY_ASSERT((entry->hash & CONCURRENT_SET_HASH_MASK) == hash); + concurrent_set_delete_entry_locked(set, entry); return curr_key; } break; @@ -399,7 +488,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key for (unsigned int i = 0; i < set->capacity; i++) { struct concurrent_set_entry *entry = &set->entries[i]; - VALUE key = set->entries[i].key; + VALUE key = entry->key; switch (key) { case CONCURRENT_SET_EMPTY: @@ -414,8 +503,7 @@ rb_concurrent_set_foreach_with_replace(VALUE set_obj, int (*callback)(VALUE *key case ST_STOP: return; case ST_DELETE: - set->entries[i].key = CONCURRENT_SET_DELETED; - set->deleted_entries++; + concurrent_set_delete_entry_locked(set, entry); break; } break; diff --git a/debug.c b/debug.c index b92faa8f369398..4daee2bd1cbd0d 100644 --- a/debug.c +++ b/debug.c @@ -708,4 +708,22 @@ ruby_debug_log_dump(const char *fname, unsigned int n) fclose(fp); } } + +#else + +#undef ruby_debug_log +void +ruby_debug_log(const char *file, int line, const char *func_name, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "[%s:%d] %s: ", file, line, func_name); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); +} + #endif // #if USE_RUBY_DEBUG_LOG diff --git a/doc/stringio/each_line.md b/doc/stringio/each_line.md new file mode 100644 index 00000000000000..e29640a12a9203 --- /dev/null +++ b/doc/stringio/each_line.md @@ -0,0 +1,189 @@ +With a block given calls the block with each remaining line (see "Position" below) in the stream; +returns `self`. + +Leaves stream position at end-of-stream. + +**No Arguments** + +With no arguments given, +reads lines using the default record separator +(global variable `$/`, whose initial value is `"\n"`). + +```ruby +strio = StringIO.new(TEXT) +strio.each_line {|line| p line } +strio.eof? # => true +``` + +Output: + +``` +"First line\n" +"Second line\n" +"\n" +"Fourth line\n" +"Fifth line\n" +``` + +**Argument `sep`** + +With only string argument `sep` given, +reads lines using that string as the record separator: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(' ') {|line| p line } +``` + +Output: + +``` +"First " +"line\nSecond " +"line\n\nFourth " +"line\nFifth " +"line\n" +``` + +**Argument `limit`** + +With only integer argument `limit` given, +reads lines using the default record separator; +also limits the size (in characters) of each line to the given limit: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(10) {|line| p line } +``` + +Output: + +``` +"First line" +"\n" +"Second lin" +"e\n" +"\n" +"Fourth lin" +"e\n" +"Fifth line" +"\n" +``` + +**Arguments `sep` and `limit`** + +With arguments `sep` and `limit` both given, +honors both: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(' ', 10) {|line| p line } +``` + +Output: + +``` +"First " +"line\nSecon" +"d " +"line\n\nFour" +"th " +"line\nFifth" +" " +"line\n" +``` + +**Position** + +As stated above, method `each` _remaining_ line in the stream. + +In the examples above each `strio` object starts with its position at beginning-of-stream; +but in other cases the position may be anywhere (see StringIO#pos): + +```ruby +strio = StringIO.new(TEXT) +strio.pos = 30 # Set stream position to character 30. +strio.each_line {|line| p line } +``` + +Output: + +``` +" line\n" +"Fifth line\n" +``` + +In all the examples above, the stream position is at the beginning of a character; +in other cases, that need not be so: + +```ruby +s = 'こんにちは' # Five 3-byte characters. +strio = StringIO.new(s) +strio.pos = 3 # At beginning of second character. +strio.each_line {|line| p line } +strio.pos = 4 # At second byte of second character. +strio.each_line {|line| p line } +strio.pos = 5 # At third byte of second character. +strio.each_line {|line| p line } +``` + +Output: + +``` +"んにちは" +"\x82\x93にちは" +"\x93にちは" +``` + +**Special Record Separators** + +Like some methods in class `IO`, StringIO.each honors two special record separators; +see {Special Line Separators}[https://docs.ruby-lang.org/en/master/IO.html#class-IO-label-Special+Line+Separator+Values]. + +```ruby +strio = StringIO.new(TEXT) +strio.each_line('') {|line| p line } # Read as paragraphs (separated by blank lines). +``` + +Output: + +``` +"First line\nSecond line\n\n" +"Fourth line\nFifth line\n" +``` + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(nil) {|line| p line } # "Slurp"; read it all. +``` + +Output: + +``` +"First line\nSecond line\n\nFourth line\nFifth line\n" +``` + +**Keyword Argument `chomp`** + +With keyword argument `chomp` given as `true` (the default is `false`), +removes trailing newline (if any) from each line: + +```ruby +strio = StringIO.new(TEXT) +strio.each_line(chomp: true) {|line| p line } +``` + +Output: + +``` +"First line" +"Second line" +"" +"Fourth line" +"Fifth line" +``` + +With no block given, returns a new {Enumerator}[https://docs.ruby-lang.org/en/master/Enumerator.html]. + + +Related: StringIO.each_byte, StringIO.each_char, StringIO.each_codepoint. diff --git a/doc/stringio/getbyte.rdoc b/doc/stringio/getbyte.rdoc index 48c334b5252a58..5e524941bca4ae 100644 --- a/doc/stringio/getbyte.rdoc +++ b/doc/stringio/getbyte.rdoc @@ -14,16 +14,18 @@ Returns +nil+ if at end-of-stream: Returns a byte, not a character: - s = 'тест' - s.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + s = 'Привет' + s.bytes + # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] strio = StringIO.new(s) - strio.getbyte # => 209 - strio.getbyte # => 130 + strio.getbyte # => 208 + strio.getbyte # => 159 s = 'こんにちは' - s.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] + s.bytes + # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] strio = StringIO.new(s) strio.getbyte # => 227 strio.getbyte # => 129 -Related: StringIO.getc. +Related: #each_byte, #ungetbyte, #getc. diff --git a/doc/stringio/getc.rdoc b/doc/stringio/getc.rdoc index c021789c911b8d..b2ab46843c8466 100644 --- a/doc/stringio/getc.rdoc +++ b/doc/stringio/getc.rdoc @@ -12,9 +12,9 @@ Returns +nil+ if at end-of-stream: Returns characters, not bytes: - strio = StringIO.new('тест') - strio.getc # => "т" - strio.getc # => "е" + strio = StringIO.new('Привет') + strio.getc # => "П" + strio.getc # => "р" strio = StringIO.new('こんにちは') strio.getc # => "こ" @@ -31,4 +31,4 @@ in other cases that need not be true: strio.pos = 5 # => 5 # At third byte of second character; returns byte. strio.getc # => "\x93" -Related: StringIO.getbyte. +Related: #getbyte, #putc, #ungetc. diff --git a/doc/stringio/gets.rdoc b/doc/stringio/gets.rdoc index 892c3feb53a9bf..bbefeb008ae245 100644 --- a/doc/stringio/gets.rdoc +++ b/doc/stringio/gets.rdoc @@ -19,10 +19,10 @@ With no arguments given, reads a line using the default record separator strio.eof? # => true strio.gets # => nil - strio = StringIO.new('тест') # Four 2-byte characters. + strio = StringIO.new('Привет') # Six 2-byte characters strio.pos # => 0 - strio.gets # => "тест" - strio.pos # => 8 + strio.gets # => "Привет" + strio.pos # => 12 Argument +sep+ @@ -67,11 +67,11 @@ but in other cases the position may be anywhere: The position need not be at a character boundary: - strio = StringIO.new('тест') # Four 2-byte characters. - strio.pos = 2 # At beginning of second character. - strio.gets # => "ест" - strio.pos = 3 # In middle of second character. - strio.gets # => "\xB5ст" + strio = StringIO.new('Привет') # Six 2-byte characters. + strio.pos = 2 # At beginning of second character. + strio.gets # => "ривет" + strio.pos = 3 # In middle of second character. + strio.gets # => "\x80ивет" Special Record Separators @@ -95,4 +95,5 @@ removes the trailing newline (if any) from the returned line: strio.gets # => "First line\n" strio.gets(chomp: true) # => "Second line" -Related: StringIO.each_line. +Related: #each_line, #readlines, +{Kernel#puts}[rdoc-ref:Kernel#puts]. diff --git a/doc/stringio/size.rdoc b/doc/stringio/size.rdoc new file mode 100644 index 00000000000000..9323adf8c3783a --- /dev/null +++ b/doc/stringio/size.rdoc @@ -0,0 +1,5 @@ +Returns the number of bytes in the string in +self+: + + StringIO.new('hello').size # => 5 # Five 1-byte characters. + StringIO.new('тест').size # => 8 # Four 2-byte characters. + StringIO.new('こんにちは').size # => 15 # Five 3-byte characters. diff --git a/doc/stringio/stringio.md b/doc/stringio/stringio.md index aebfa6d6f1f4c2..8931d1c30c7d79 100644 --- a/doc/stringio/stringio.md +++ b/doc/stringio/stringio.md @@ -324,7 +324,7 @@ a binary stream may not be changed to text. ### Encodings -A stream has an encoding; see the [encodings document][encodings document]. +A stream has an encoding; see [Encodings][encodings document]. The initial encoding for a new or re-opened stream depends on its [data mode][data mode]: @@ -683,7 +683,7 @@ Reading: - #each_codepoint: reads each remaining codepoint, passing it to the block. [bom]: https://en.wikipedia.org/wiki/Byte_order_mark -[encodings document]: https://docs.ruby-lang.org/en/master/encodings_rdoc.html +[encodings document]: https://docs.ruby-lang.org/en/master/language/encodings_rdoc.html [io class]: https://docs.ruby-lang.org/en/master/IO.html [kernel#puts]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-puts [kernel#readline]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-readline diff --git a/ext/-test-/fatal/invalid.c b/ext/-test-/fatal/invalid.c index 393465416a0f6c..6fd970b181191c 100644 --- a/ext/-test-/fatal/invalid.c +++ b/ext/-test-/fatal/invalid.c @@ -1,11 +1,5 @@ #include -#if SIZEOF_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULONG(x) -#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -# define NUM2PTR(x) NUM2ULL(x) -#endif - static VALUE invalid_call(VALUE obj, VALUE address) { diff --git a/ext/date/lib/date.rb b/ext/date/lib/date.rb index b33f6e65f466b0..0cb763017f22c0 100644 --- a/ext/date/lib/date.rb +++ b/ext/date/lib/date.rb @@ -4,7 +4,7 @@ require 'date_core' class Date - VERSION = "3.5.0" # :nodoc: + VERSION = "3.5.1" # :nodoc: # call-seq: # infinite? -> false diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index c84c7ed660a06d..45de8d1ff62f1d 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -752,6 +752,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser break; default: if ((unsigned char)*pe < 0x20) { + if (*pe == '\n') { + raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + } raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1); } raise_parse_error_at("invalid escape character in string: %s", state, pe - 1); diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 819c6e76996f61..aeb96d7701242d 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -4,28 +4,35 @@ struct rb_monitor { long count; - const VALUE owner; - const VALUE mutex; + VALUE owner; + VALUE mutex; }; static void monitor_mark(void *ptr) { struct rb_monitor *mc = ptr; - rb_gc_mark(mc->owner); - rb_gc_mark(mc->mutex); + rb_gc_mark_movable(mc->owner); + rb_gc_mark_movable(mc->mutex); } -static size_t -monitor_memsize(const void *ptr) +static void +monitor_compact(void *ptr) { - return sizeof(struct rb_monitor); + struct rb_monitor *mc = ptr; + mc->owner = rb_gc_location(mc->owner); + mc->mutex = rb_gc_location(mc->mutex); } static const rb_data_type_t monitor_data_type = { - "monitor", - {monitor_mark, RUBY_TYPED_DEFAULT_FREE, monitor_memsize,}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "monitor", + .function = { + .dmark = monitor_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = NULL, // Fully embeded + .dcompact = monitor_compact, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; static VALUE @@ -50,10 +57,10 @@ monitor_ptr(VALUE monitor) return mc; } -static int -mc_owner_p(struct rb_monitor *mc) +static bool +mc_owner_p(struct rb_monitor *mc, VALUE current_fiber) { - return mc->owner == rb_fiber_current(); + return mc->owner == current_fiber; } /* @@ -67,17 +74,44 @@ monitor_try_enter(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { + VALUE current_fiber = rb_fiber_current(); + if (!mc_owner_p(mc, current_fiber)) { if (!rb_mutex_trylock(mc->mutex)) { return Qfalse; } - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); + RB_OBJ_WRITE(monitor, &mc->owner, current_fiber); mc->count = 0; } mc->count += 1; return Qtrue; } + +struct monitor_args { + VALUE monitor; + struct rb_monitor *mc; + VALUE current_fiber; +}; + +static inline void +monitor_args_init(struct monitor_args *args, VALUE monitor) +{ + args->monitor = monitor; + args->mc = monitor_ptr(monitor); + args->current_fiber = rb_fiber_current(); +} + +static void +monitor_enter0(struct monitor_args *args) +{ + if (!mc_owner_p(args->mc, args->current_fiber)) { + rb_mutex_lock(args->mc->mutex); + RB_OBJ_WRITE(args->monitor, &args->mc->owner, args->current_fiber); + args->mc->count = 0; + } + args->mc->count++; +} + /* * call-seq: * enter -> nil @@ -87,27 +121,44 @@ monitor_try_enter(VALUE monitor) static VALUE monitor_enter(VALUE monitor) { - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); - mc->count = 0; - } - mc->count++; + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_enter0(&args); return Qnil; } +static inline void +monitor_check_owner0(struct monitor_args *args) +{ + if (!mc_owner_p(args->mc, args->current_fiber)) { + rb_raise(rb_eThreadError, "current fiber not owner"); + } +} + /* :nodoc: */ static VALUE monitor_check_owner(VALUE monitor) { - struct rb_monitor *mc = monitor_ptr(monitor); - if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current fiber not owner"); - } + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_check_owner0(&args); return Qnil; } +static void +monitor_exit0(struct monitor_args *args) +{ + monitor_check_owner(args->monitor); + + if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count); + args->mc->count--; + + if (args->mc->count == 0) { + RB_OBJ_WRITE(args->monitor, &args->mc->owner, Qnil); + rb_mutex_unlock(args->mc->mutex); + } +} + /* * call-seq: * exit -> nil @@ -117,17 +168,9 @@ monitor_check_owner(VALUE monitor) static VALUE monitor_exit(VALUE monitor) { - monitor_check_owner(monitor); - - struct rb_monitor *mc = monitor_ptr(monitor); - - if (mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)mc->count); - mc->count--; - - if (mc->count == 0) { - RB_OBJ_WRITE(monitor, &mc->owner, Qnil); - rb_mutex_unlock(mc->mutex); - } + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_exit0(&args); return Qnil; } @@ -144,7 +187,7 @@ static VALUE monitor_owned_p(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); - return (rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc)) ? Qtrue : Qfalse; + return rb_mutex_locked_p(mc->mutex) && mc_owner_p(mc, rb_fiber_current()) ? Qtrue : Qfalse; } static VALUE @@ -210,9 +253,10 @@ monitor_sync_body(VALUE monitor) } static VALUE -monitor_sync_ensure(VALUE monitor) +monitor_sync_ensure(VALUE v_args) { - return monitor_exit(monitor); + monitor_exit0((struct monitor_args *)v_args); + return Qnil; } /* @@ -226,8 +270,10 @@ monitor_sync_ensure(VALUE monitor) static VALUE monitor_synchronize(VALUE monitor) { - monitor_enter(monitor); - return rb_ensure(monitor_sync_body, monitor, monitor_sync_ensure, monitor); + struct monitor_args args; + monitor_args_init(&args, monitor); + monitor_enter0(&args); + return rb_ensure(monitor_sync_body, (VALUE)&args, monitor_sync_ensure, (VALUE)&args); } void diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index 3202b10296f549..2c2319e7a38e67 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,7 +2,7 @@ module Psych # The version of Psych you are using - VERSION = '5.2.6' + VERSION = '5.3.0' if RUBY_ENGINE == 'jruby' DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze diff --git a/ext/win32/win32-registry.gemspec b/ext/win32/win32-registry.gemspec index 9b65af4a5b9976..9bd57bd7d1368f 100644 --- a/ext/win32/win32-registry.gemspec +++ b/ext/win32/win32-registry.gemspec @@ -1,7 +1,7 @@ # frozen_string_literal: true Gem::Specification.new do |spec| spec.name = "win32-registry" - spec.version = "0.1.1" + spec.version = "0.1.2" spec.authors = ["U.Nakamura"] spec.email = ["usa@garbagecollect.jp"] diff --git a/gc.c b/gc.c index 79eec5d96bf7af..5f0f2307c8c9fd 100644 --- a/gc.c +++ b/gc.c @@ -2122,14 +2122,9 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) static VALUE id2ref(VALUE objid) { -#if SIZEOF_LONG == SIZEOF_VOIDP -#define NUM2PTR(x) NUM2ULONG(x) -#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP -#define NUM2PTR(x) NUM2ULL(x) -#endif objid = rb_to_int(objid); if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) { - VALUE ptr = NUM2PTR(objid); + VALUE ptr = (VALUE)NUM2PTR(objid); if (SPECIAL_CONST_P(ptr)) { if (ptr == Qtrue) return Qtrue; if (ptr == Qfalse) return Qfalse; diff --git a/include/ruby/internal/arithmetic/intptr_t.h b/include/ruby/internal/arithmetic/intptr_t.h index a354f4469cdf95..70090f88e6b37c 100644 --- a/include/ruby/internal/arithmetic/intptr_t.h +++ b/include/ruby/internal/arithmetic/intptr_t.h @@ -32,6 +32,18 @@ #define rb_int_new rb_int2inum /**< @alias{rb_int2inum} */ #define rb_uint_new rb_uint2inum /**< @alias{rb_uint2inum} */ +// These definitions are same as fiddle/conversions.h +#if SIZEOF_VOIDP <= SIZEOF_LONG +# define PTR2NUM(x) (LONG2NUM((long)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULONG(x))) +#elif SIZEOF_VOIDP <= SIZEOF_LONG_LONG +# define PTR2NUM(x) (LL2NUM((LONG_LONG)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULL(x))) +#else +// should have been an error in ruby/internal/value.h +# error Need integer for VALUE +#endif + RBIMPL_SYMBOL_EXPORT_BEGIN() /** diff --git a/internal/box.h b/internal/box.h index 72263cc9dc5e5d..b62b6a9bc946f2 100644 --- a/internal/box.h +++ b/internal/box.h @@ -3,16 +3,6 @@ #include "ruby/ruby.h" /* for VALUE */ -#if SIZEOF_VALUE <= SIZEOF_LONG -# define SVALUE2NUM(x) LONG2NUM((long)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x) -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x)) -# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x) -#else -# error Need integer for VALUE -#endif - /** * @author Ruby developers * @copyright This file is a part of the programming language Ruby. diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 76267c2cd18c57..175d6d9c6b6858 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -109,10 +109,8 @@ # +delegate.rb+. # module Forwardable - require 'forwardable/impl' - # Version of +forwardable.rb+ - VERSION = "1.3.3" + VERSION = "1.4.0" VERSION.freeze # Version for backward compatibility @@ -206,37 +204,33 @@ def self._delegator_method(obj, accessor, method, ali) if Module === obj ? obj.method_defined?(accessor) || obj.private_method_defined?(accessor) : obj.respond_to?(accessor, true) - accessor = "#{accessor}()" + accessor = "(#{accessor}())" end args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' method_call = ".__send__(:#{method}, #{args})" - if _valid_method?(method) + if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/) loc, = caller_locations(2,1) pre = "_ =" mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method " - method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}" - begin; - unless defined? _.#{method} - ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 - _#{method_call} - else - _.#{method}(#{args}) - end - end; + method_call = <<~RUBY.chomp + if defined?(_.#{method}) + _.#{method}(#{args}) + else + ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 + _#{method_call} + end + RUBY end - _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) - begin; + eval(<<~RUBY, nil, __FILE__, __LINE__ + 1) proc do def #{ali}(#{args}) - #{pre} - begin - #{accessor} - end#{method_call} + #{pre}#{accessor} + #{method_call} end end - end; + RUBY end end diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec index 9ad59c5f8a8830..1b539bcfcb3daf 100644 --- a/lib/forwardable/forwardable.gemspec +++ b/lib/forwardable/forwardable.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.licenses = ["Ruby", "BSD-2-Clause"] spec.required_ruby_version = '>= 2.4.0' - spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"] + spec.files = ["forwardable.gemspec", "lib/forwardable.rb"] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb deleted file mode 100644 index 0322c136db4a64..00000000000000 --- a/lib/forwardable/impl.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Forwardable - # :stopdoc: - - def self._valid_method?(method) - catch {|tag| - eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__) - } - rescue SyntaxError - false - else - true - end - - def self._compile_method(src, file, line) - eval(src, nil, file, line) - end -end diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 513b7778a92f0b..725ff309d27f48 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -41,7 +41,7 @@ class IPAddr # The version string - VERSION = "1.2.7" + VERSION = "1.2.8" # 32 bit mask for IPv4 IN4MASK = 0xffffffff diff --git a/lib/optparse.rb b/lib/optparse.rb index aae73c86b32787..97178e284bd53c 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -426,7 +426,7 @@ # class OptionParser # The version string - VERSION = "0.8.0" + VERSION = "0.8.1" # An alias for compatibility Version = VERSION diff --git a/lib/resolv.rb b/lib/resolv.rb index b98c7ecdd2aa32..0e62aaf8510496 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -35,7 +35,7 @@ class Resolv # The version string - VERSION = "0.6.3" + VERSION = "0.7.0" ## # Looks up the first IP address for +name+. diff --git a/load.c b/load.c index 466517f465b098..144f095b04d2d3 100644 --- a/load.c +++ b/load.c @@ -1345,7 +1345,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa reset_ext_config = true; ext_config_push(th, &prev_ext_config); handle = rb_vm_call_cfunc_in_box(box->top_self, load_ext, path, fname, path, box); - rb_hash_aset(box->ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle)); + rb_hash_aset(box->ruby_dln_libmap, path, PTR2NUM(handle)); break; } result = TAG_RETURN; @@ -1666,7 +1666,7 @@ rb_ext_resolve_symbol(const char* fname, const char* symbol) if (NIL_P(handle)) { return NULL; } - return dln_symbol((void *)NUM2SVALUE(handle), symbol); + return dln_symbol(NUM2PTR(handle), symbol); } void diff --git a/spec/ruby/optional/capi/ext/digest_spec.c b/spec/ruby/optional/capi/ext/digest_spec.c index 9993238cf227d3..65c8defa20adce 100644 --- a/spec/ruby/optional/capi/ext/digest_spec.c +++ b/spec/ruby/optional/capi/ext/digest_spec.c @@ -135,7 +135,9 @@ VALUE digest_spec_context_size(VALUE self, VALUE meta) { return SIZET2NUM(algo->ctx_size); } +#ifndef PTR2NUM #define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) +#endif VALUE digest_spec_context(VALUE self, VALUE digest) { return PTR2NUM(context); diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 257e4f1736a2d0..3f0fb7522dc671 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -164,6 +164,14 @@ def test_parse_complex_objects end end + def test_parse_control_chars_in_string + 0.upto(31) do |ord| + assert_raise JSON::ParserError do + parse(%("#{ord.chr}")) + end + end + end + def test_parse_arrays assert_equal([1,2,3], parse('[1,2,3]')) assert_equal([1.2,2,3], parse('[1.2,2,3]')) diff --git a/thread_sync.c b/thread_sync.c index 8967e24e341bad..9fb1639e9b7dd2 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -239,23 +239,34 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) } static void -mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber) +mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) { - rb_mutex_t *mutex = mutex_ptr(self); - mutex->thread = th->self; mutex->fiber_serial = rb_fiber_serial(fiber); } static void -mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self) +mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) { - rb_mutex_t *mutex = mutex_ptr(self); - - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); thread_mutex_insert(th, mutex); } +static inline bool +mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) +{ + if (mutex->fiber_serial == 0) { + RUBY_DEBUG_LOG("%p ok", mutex); + + mutex_locked(mutex, th, fiber); + return true; + } + else { + RUBY_DEBUG_LOG("%p ng", mutex); + return false; + } +} + /* * call-seq: * mutex.try_lock -> true or false @@ -266,21 +277,7 @@ mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self) VALUE rb_mutex_trylock(VALUE self) { - rb_mutex_t *mutex = mutex_ptr(self); - - if (mutex->fiber_serial == 0) { - RUBY_DEBUG_LOG("%p ok", mutex); - - rb_fiber_t *fiber = GET_EC()->fiber_ptr; - rb_thread_t *th = GET_THREAD(); - - mutex_locked(th, fiber, self); - return Qtrue; - } - else { - RUBY_DEBUG_LOG("%p ng", mutex); - return Qfalse; - } + return RBOOL(mutex_trylock(mutex_ptr(self), GET_THREAD(), GET_EC()->fiber_ptr)); } static VALUE @@ -321,7 +318,7 @@ do_mutex_lock(VALUE self, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (rb_mutex_trylock(self) == Qfalse) { + if (!mutex_trylock(mutex, th, fiber)) { if (mutex->fiber_serial == rb_fiber_serial(fiber)) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } @@ -342,7 +339,7 @@ do_mutex_lock(VALUE self, int interruptible_p) rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter); if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } } else { @@ -383,7 +380,7 @@ do_mutex_lock(VALUE self, int interruptible_p) // unlocked by another thread while sleeping if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } rb_ractor_sleeper_threads_dec(th->ractor); @@ -402,7 +399,7 @@ do_mutex_lock(VALUE self, int interruptible_p) } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ if (!mutex->fiber_serial) { - mutex_set_owner(self, th, fiber); + mutex_set_owner(mutex, th, fiber); } } else { @@ -421,7 +418,7 @@ do_mutex_lock(VALUE self, int interruptible_p) } if (saved_ints) th->ec->interrupt_flag = saved_ints; - if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(th, fiber, self); + if (mutex->fiber_serial == rb_fiber_serial(fiber)) mutex_locked(mutex, th, fiber); } RUBY_DEBUG_LOG("%p locked", mutex); diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 780f923b55598e..6945c6cdce52a6 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -427,7 +427,7 @@ def sync_default_gems(gem) end def check_prerelease_version(gem) - return if ["rubygems", "mmtk", "cgi"].include?(gem) + return if ["rubygems", "mmtk", "cgi", "pathname"].include?(gem) require "net/https" require "json" diff --git a/vm_core.h b/vm_core.h index 0a1914f57a955b..4195ea5e59c9a4 100644 --- a/vm_core.h +++ b/vm_core.h @@ -2084,9 +2084,9 @@ rb_current_execution_context(bool expect_ec) * and the address of the `ruby_current_ec` can be stored on a function * frame. However, this address can be mis-used after native thread * migration of a coroutine. - * 1) Get `ptr =&ruby_current_ec` op NT1 and store it on the frame. + * 1) Get `ptr = &ruby_current_ec` on NT1 and store it on the frame. * 2) Context switch and resume it on the NT2. - * 3) `ptr` is used on NT2 but it accesses to the TLS on NT1. + * 3) `ptr` is used on NT2 but it accesses the TLS of NT1. * This assertion checks such misusage. * * To avoid accidents, `GET_EC()` should be called once on the frame. diff --git a/vm_debug.h b/vm_debug.h index d0bc81574a31b5..cf80232f3a5eb0 100644 --- a/vm_debug.h +++ b/vm_debug.h @@ -88,6 +88,10 @@ void ruby_debug_log(const char *file, int line, const char *func_name, const cha void ruby_debug_log_print(unsigned int n); bool ruby_debug_log_filter(const char *func_name, const char *file_name); +// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified. +// You can use this macro for temporary usage (you should not commit it). +#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__) + #if RBIMPL_COMPILER_IS(GCC) && defined(__OPTIMIZE__) # define ruby_debug_log(...) \ RB_GNUC_EXTENSION_BLOCK( \ @@ -97,10 +101,6 @@ bool ruby_debug_log_filter(const char *func_name, const char *file_name); RBIMPL_WARNING_POP()) #endif -// convenient macro to log even if the USE_RUBY_DEBUG_LOG macro is not specified. -// You can use this macro for temporary usage (you should not commit it). -#define _RUBY_DEBUG_LOG(...) ruby_debug_log(__FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING, "" __VA_ARGS__) - #if USE_RUBY_DEBUG_LOG # define RUBY_DEBUG_LOG_ENABLED(func_name, file_name) \ (ruby_debug_log_mode && ruby_debug_log_filter(func_name, file_name)) diff --git a/yjit.c b/yjit.c index 6d909a0da61ee3..f3c256093eda0b 100644 --- a/yjit.c +++ b/yjit.c @@ -64,8 +64,6 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); // The "_yjit_" part is for trying to be informative. We might want different // suffixes for symbols meant for Rust and symbols meant for broader CRuby. -# define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - // For a given raw_sample (frame), set the hash with the caller's // name, file, and line number. Return the hash with collected frame_info. static void diff --git a/zjit.c b/zjit.c index 75cca281a45a91..05fb3e1f028ff6 100644 --- a/zjit.c +++ b/zjit.c @@ -31,12 +31,14 @@ #include +// This build config impacts the pointer tagging scheme and we only want to +// support one scheme for simplicity. +STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); + enum zjit_struct_offsets { ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param) }; -#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) - // For a given raw_sample (frame), set the hash with the caller's // name, file, and line number. Return the hash with collected frame_info. static void