Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Note: We're only listing outstanding class updates.
given names, raising `KeyError` for missing names unless a block is
given. [[Feature #21781]]

* Integer

* `Integer#bit_count` is added. It returns the number of `1` bits in the
binary representation of a non-negative integer (its population count).
[[Feature #20163]]

* Kernel

* `Kernel#autoload_relative` and `Module#autoload_relative` are added.
Expand Down Expand Up @@ -242,6 +248,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable.

[Feature #8948]: https://bugs.ruby-lang.org/issues/8948
[Feature #15330]: https://bugs.ruby-lang.org/issues/15330
[Feature #20163]: https://bugs.ruby-lang.org/issues/20163
[Feature #21390]: https://bugs.ruby-lang.org/issues/21390
[Feature #21768]: https://bugs.ruby-lang.org/issues/21768
[Feature #21781]: https://bugs.ruby-lang.org/issues/21781
Expand Down
55 changes: 39 additions & 16 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -3731,14 +3731,26 @@ dir_collect_children(VALUE dir)

/*
* call-seq:
* children -> array
* scan -> entries
* scan {|entry_name, entry_type| ... } -> nil
*
* Returns an array of the entry names in +self+ along with their type
* except for <tt>'.'</tt> and <tt>'..'</tt>:
* Scans the entries in +self+, except for <tt>'.'</tt> and <tt>'..'</tt>.
*
* With no block given, returns +entries+, an array of 2-element arrays.
* Each nested array contains an entry name and its type:
*
* dir = Dir.new('/example')
* dir.scan # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]]
*
* With a block given, calls the block with each entry name and its type;
* returns +nil+:
*
* entries = []
* dir.scan do |entry_name, entry_type|
* entries << [entry_name, entry_type]
* end # => nil
* entries # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]]
*
*/
static VALUE
dir_scan_children(VALUE dir)
Expand Down Expand Up @@ -3784,25 +3796,36 @@ dir_s_children(int argc, VALUE *argv, VALUE io)

/*
* call-seq:
* Dir.scan(dirpath) -> entries
* Dir.scan(dirpath, encoding: 'UTF-8') -> entries
* Dir.scan(dirpath) {|entry_name, entry_type| ... } -> nil
* Dir.scan(dirpath, encoding: 'UTF-8') {|entry_name, entry_type| ... } -> nil
* Dir.scan(dirpath) -> [[entry_name, entry_type], ...]
* Dir.scan(dirpath, encoding: 'UTF-8') -> [[entry_name, entry_type], ...]
*
* Yields or returns an array of the entry names in the directory at +dirpath+
* associated with their type, except for <tt>'.'</tt> and <tt>'..'</tt>;
* sets the given encoding onto each returned entry name.
* Scans the entries in the directory at +dirpath+, except for <tt>'.'</tt>
* and <tt>'..'</tt>.
*
* With no block given, returns +entries+, an array of 2-element arrays.
* Each nested array contains an entry name and its type:
*
* The type symbol is one of:
* ``<code>:file</code>'', ``<code>:directory</code>'',
* ``<code>:characterSpecial</code>'', ``<code>:blockSpecial</code>'',
* ``<code>:fifo</code>'', ``<code>:link</code>'',
* or ``<code>:socket</code>'':
* Dir.scan('/example') # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]]
*
* Dir.children('/example') # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]]
* Dir.children('/example').first.first.encoding
* With a block given, calls the block with each entry name and its type;
* returns +nil+:
*
* entries = []
* Dir.scan('/example') do |entry_name, entry_type|
* entries << [entry_name, entry_type]
* end # => nil
* entries # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]]
*
* The type symbol is one of +:file+, +:directory+, +:characterSpecial+,
* +:blockSpecial+, +:fifo+, +:link+, +:socket+, or +:unknown+.
*
* The given +encoding+ is used as the external encoding for each entry name:
*
* Dir.scan('/example').first.first.encoding
* # => #<Encoding:UTF-8>
* Dir.children('/example', encoding: 'US-ASCII').first.encoding
* Dir.scan('/example', encoding: 'US-ASCII').first.first.encoding
* # => #<Encoding:US-ASCII>
*
* See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding].
Expand Down
2 changes: 1 addition & 1 deletion doc/contributing/documentation_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ the browser when you edit source files:
make html-server
```

Then visit http://localhost:4000 in your browser.
Then visit `http://localhost:4000` in your browser.
To use a different port: `make html-server RDOC_SERVER_PORT=8080`.

If you don't have a build directory, follow the [quick start
Expand Down
2 changes: 1 addition & 1 deletion doc/maintainers.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ consensus on ruby-core/ruby-dev.

* *No maintainer*
* https://github.com/ruby/English
* https://rubygems.org/gems/English
* https://rubygems.org/gems/english

#### lib/delegate.rb

Expand Down
54 changes: 50 additions & 4 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2283,7 +2283,6 @@ def world_readable?() File.world_readable?(@path) end
# :markup: markdown
#
# call-seq:
#
# readable_real? -> true or false
#
# Like #readable?, but checks against the real user and group ids
Expand Down Expand Up @@ -2425,7 +2424,24 @@ def sticky?() FileTest.sticky?(@path) end
#
def symlink?() FileTest.symlink?(@path) end

# See <tt>FileTest.writable?</tt>.
# :markup: markdown
#
# call-seq:
# writable? => true or false
#
# Returns whether the path in `self` points to an entry that is writable
# by the owner and group of the current process:
#
# ```ruby
# pn = Pathname('/tmp/secret.txt')
# pn.write('foo')
# pn.writable? # => true
# pn.chmod(0o000)
# pn.writable? # => false
# pn.delete
# Pathname('nosuch').writable? # => false
# ```
#
def writable?() FileTest.writable?(@path) end

# :markup: markdown
Expand All @@ -2452,10 +2468,40 @@ def writable?() FileTest.writable?(@path) end
#
def world_writable?() File.world_writable?(@path) end

# See <tt>FileTest.writable_real?</tt>.
# :markup: markdown
#
# call-seq:
# writable_real? -> true or false
#
# Like #writable?, but checks against the real user and group ids
# instead of the effective ids.
def writable_real?() FileTest.writable_real?(@path) end

# See <tt>FileTest.zero?</tt>.
# :markup: markdown
#
# call-seq:
# zero? -> true or false
#
# Returns whether the entry represented by `self` exists and has size zero:
#
# ```
# dir_pn = Pathname('example_dir')
# dir_pn.zero? # => false # Dir does not exist.
# dir_pn.mkdir
# dir_pn.zero? # => false # Directory never has size zero.
# dir_pn.empty? # => true # But this one is empty.
#
# file_pn = Pathname('example_dir/example.txt')
# file_pn.zero? # => false # File does not exist.
# file_pn.write('')
# file_pn.zero? # => true
# file_pn.write('foo')
# file_pn.zero? # => false
#
# file_pn.delete
# dir_pn.delete
# ```
#
def zero?() FileTest.zero?(@path) end
end

Expand Down
6 changes: 5 additions & 1 deletion set.c
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,7 @@ set_i_xor(VALUE set, VALUE other)
set_merge_enum_into(tmp, other);
set_iter(tmp, set_xor_i, (st_data_t)new_set);
}
set_compact_after_delete(set);

return new_set;
}
Expand Down Expand Up @@ -1523,6 +1524,7 @@ set_remove_enum_from(VALUE set, VALUE arg)
else {
rb_block_call(arg, enum_method_id(arg), 0, 0, set_remove_block, (VALUE)set);
}
set_compact_after_delete(set);
}

/*
Expand Down Expand Up @@ -1667,6 +1669,7 @@ set_i_keep_if(VALUE set)
rb_check_frozen(set);

set_iter(set, set_keep_if_i, (st_data_t)RSET_TABLE(set));
set_compact_after_delete(set);

return set;
}
Expand Down Expand Up @@ -1696,6 +1699,7 @@ set_i_select(VALUE set)
set_table *table = RSET_TABLE(set);
size_t n = set_table_size(table);
set_iter(set, set_keep_if_i, (st_data_t)table);
set_compact_after_delete(set);

return (n == set_table_size(table)) ? Qnil : set;
}
Expand Down Expand Up @@ -1731,6 +1735,7 @@ set_i_replace(VALUE set, VALUE other)
set_table_clear(RSET_TABLE(set));
set_merge_enum_into(set, other);
}
set_compact_after_delete(set);

return set;
}
Expand Down Expand Up @@ -2384,7 +2389,6 @@ rb_set_size(VALUE set)
* - {Assigning}[rdoc-ref:Set@Methods+for+Assigning]
* - {Deleting}[rdoc-ref:Set@Methods+for+Deleting]
* - {Converting}[rdoc-ref:Set@Methods+for+Converting]
* - {Iterating}[rdoc-ref:Set@Methods+for+Iterating]
* - {And more....}[rdoc-ref:Set@Other+Methods]
*
* === Methods for Creating a \Set
Expand Down
10 changes: 4 additions & 6 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -5136,10 +5136,8 @@ static VALUE get_pat(VALUE);
*
* regexp = Regexp.new(pattern)
*
* - Computes +matchdata+, which will be either a MatchData object or +nil+
* (see Regexp#match):
*
* matchdata = regexp.match(self[offset..])
* - Calls <tt>regexp.match</tt> with +self+ to compute +matchdata+.
* If +offset+ is given, it is also passed (see Regexp#match).
*
* With no block given, returns the computed +matchdata+ or +nil+:
*
Expand Down Expand Up @@ -5187,8 +5185,8 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str)
*
* regexp = Regexp.new(pattern)
*
* Returns +true+ if <tt>self[offset..].match(regexp)</tt> returns a MatchData object,
* +false+ otherwise:
* The search for +regexp+ in +self+ begins at the given character +offset+.
* Returns +true+ if a match is found, +false+ otherwise:
*
* 'foo'.match?(/o/) # => true
* 'foo'.match?('o') # => true
Expand Down
21 changes: 20 additions & 1 deletion yjit/src/backend/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ pub const C_SP_STEP: i32 = 16;
impl CodeBlock {
// The maximum number of bytes that can be generated by emit_jmp_ptr.
pub fn jmp_ptr_bytes(&self) -> usize {
Self::jmp_ptr_bytes_for_region(self.virtual_region_size())
}

// Split out of jmp_ptr_bytes() so the range boundary can be tested without
// allocating a region that large.
fn jmp_ptr_bytes_for_region(region_size: usize) -> usize {
// b instruction's offset is encoded as imm26 times 4. It can jump to
// +/-128MiB, so this can be used when --yjit-exec-mem-size <= 128.
let num_insns = if b_offset_fits_bits(self.virtual_region_size() as i64 / 4) {
// The widest in-region branch spans the region minus one instruction.
let num_insns = if b_offset_fits_bits((region_size as i64 - 4) / 4) {
1 // b instruction
} else {
5 // 4 instructions to load a 64-bit absolute address + br instruction
Expand Down Expand Up @@ -1382,6 +1389,18 @@ mod tests {
(Assembler::new(0), CodeBlock::new_dummy(1024))
}

#[test]
fn test_jmp_ptr_bytes_at_b_range_boundary() {
// The widest branch in a 128MiB region spans 128MiB - 4 bytes, which is
// still the largest offset a b can encode, so one instruction is enough.
// This is the default region size, so it is the common case.
assert_eq!(4, CodeBlock::jmp_ptr_bytes_for_region(128 * 1024 * 1024));

// One instruction further and a b can no longer span the region, so we
// have to reserve room for an absolute load-address plus br.
assert_eq!(20, CodeBlock::jmp_ptr_bytes_for_region(128 * 1024 * 1024 + 4));
}

#[test]
fn test_emit_add() {
let (mut asm, mut cb) = setup_asm();
Expand Down
30 changes: 3 additions & 27 deletions zjit/src/backend/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,46 +99,22 @@ impl From<&Opnd> for A64Opnd {
}
}

/// Call emit_jmp_ptr and immediately invalidate the written range.
/// This is needed when next_page also moves other_cb that is not invalidated
/// by compile_with_regs. Doing it here allows you to avoid invalidating a lot
/// more than necessary when other_cb jumps from a position early in the page.
/// This invalidates a small range of cb twice, but we accept the small cost.
fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) {
let start = cb.get_write_ptr();
emit_jmp_ptr(cb, dst_ptr, true);
let end = cb.get_write_ptr();
trace_compile_phase("invalidate_icache", || {
unsafe { rb_jit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) };
});
}

fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) {
fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr) {
let src_addr = cb.get_write_ptr().as_offset();
let dst_addr = dst_ptr.as_offset();

// If the offset is short enough, then we'll use the
// branch instruction. Otherwise, we'll move the
// destination into a register and use the branch
// register instruction.
let num_insns = if b_offset_fits_bits((dst_addr - src_addr) / 4) {
if b_offset_fits_bits((dst_addr - src_addr) / 4) {
b(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32));
1
} else {
let num_insns = emit_load_value(cb, Assembler::EMIT_OPND, dst_addr as u64);
br(cb, Assembler::EMIT_OPND);
num_insns + 1
};

if padding {
// Make sure it's always a consistent number of
// instructions in case it gets patched and has to
// use the other branch.
assert!(num_insns * 4 <= cb.jmp_ptr_bytes());
for _ in num_insns..(cb.jmp_ptr_bytes() / 4) {
nop(cb);
}
}
}

/// Emit the required instructions to load the given value into the
Expand Down Expand Up @@ -1478,7 +1454,7 @@ impl Assembler {
Insn::Jmp(target) => {
match *target {
Target::CodePtr(dst_ptr) => {
emit_jmp_ptr(cb, dst_ptr, true);
emit_jmp_ptr(cb, dst_ptr);
},
Target::Label(label_idx) => {
// Reserve space for a single B instruction
Expand Down
Loading