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
32 changes: 24 additions & 8 deletions ext/stringio/stringio.c
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,18 @@ strio_to_read(VALUE self)
* call-seq:
* eof? -> true or false
*
* Returns +true+ if positioned at end-of-stream, +false+ otherwise;
* see {Position}[rdoc-ref:IO@Position].
* Returns whether +self+ is positioned at end-of-stream:
*
* strio = StringIO.new('foo')
* strio.pos # => 0
* strio.eof? # => false
* strio.read # => "foo"
* strio.pos # => 3
* strio.eof? # => true
* strio.close_read
* strio.eof? # Raises IOError: not opened for reading
*
* Raises IOError if the stream is not opened for reading.
* Related: StringIO#pos.
*/
static VALUE
strio_eof(VALUE self)
Expand Down Expand Up @@ -2039,12 +2047,20 @@ strio_truncate(VALUE self, VALUE len)
}

/*
* call-seq:
* strio.external_encoding => encoding
* call-seq:
* external_encoding -> encoding or nil
*
* Returns an Encoding object that represents the encoding of the string;
* see {Encoding}[rdoc-ref:Encoding]:
*
* strio = StringIO.new('foo')
* strio.external_encoding # => #<Encoding:UTF-8>
*
* Returns +nil+ if +self+ has no string and is in write mode:
*
* strio = StringIO.new(nil, 'w+')
* strio.external_encoding # => nil
*
* Returns the Encoding object that represents the encoding of the file.
* If the stream is write mode and no encoding is specified, returns
* +nil+.
*/

static VALUE
Expand Down
12 changes: 8 additions & 4 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,9 @@ total_final_slots_count(rb_objspace_t *objspace)
#define GC_INCREMENTAL_SWEEP_SLOT_COUNT 2048
#define GC_INCREMENTAL_SWEEP_POOL_SLOT_COUNT 1024
#define is_lazy_sweeping(objspace) (GC_ENABLE_LAZY_SWEEP && has_sweeping_pages(objspace))
/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */
#define needs_continue_sweeping(objspace, heap) \
((heap)->free_pages == NULL && is_lazy_sweeping(objspace))

#if SIZEOF_LONG == SIZEOF_VOIDP
# define obj_id_to_ref(objid) ((objid) ^ FIXNUM_FLAG) /* unset FIXNUM_FLAG */
Expand Down Expand Up @@ -2022,7 +2025,10 @@ static void
gc_continue(rb_objspace_t *objspace, rb_heap_t *heap)
{
unsigned int lock_lev;
gc_enter(objspace, gc_enter_event_continue, &lock_lev);
bool needs_gc = is_incremental_marking(objspace) || needs_continue_sweeping(objspace, heap);
if (!needs_gc) return;

gc_enter(objspace, gc_enter_event_continue, &lock_lev); // takes vm barrier, try to avoid

/* Continue marking if in incremental marking. */
if (is_incremental_marking(objspace)) {
Expand All @@ -2031,9 +2037,7 @@ gc_continue(rb_objspace_t *objspace, rb_heap_t *heap)
}
}

/* Continue sweeping if in lazy sweeping or the previous incremental
* marking finished and did not yield a free page. */
if (heap->free_pages == NULL && is_lazy_sweeping(objspace)) {
if (needs_continue_sweeping(objspace, heap)) {
gc_sweep_continue(objspace, heap);
}

Expand Down
8 changes: 4 additions & 4 deletions lib/rubygems/package/tar_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Gem::Package::TarHeader
##
# Pack format for a tar header

PACK_FORMAT = "a100" + # name
PACK_FORMAT = ("a100" + # name
"a8" + # mode
"a8" + # uid
"a8" + # gid
Expand All @@ -71,12 +71,12 @@ class Gem::Package::TarHeader
"a32" + # gname
"a8" + # devmajor
"a8" + # devminor
"a155" # prefix
"a155").freeze # prefix

##
# Unpack format for a tar header

UNPACK_FORMAT = "A100" + # name
UNPACK_FORMAT = ("A100" + # name
"A8" + # mode
"A8" + # uid
"A8" + # gid
Expand All @@ -91,7 +91,7 @@ class Gem::Package::TarHeader
"A32" + # gname
"A8" + # devmajor
"A8" + # devminor
"A155" # prefix
"A155").freeze # prefix

attr_reader(*FIELDS)

Expand Down
36 changes: 0 additions & 36 deletions lib/rubygems/specification_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,49 +190,13 @@ def validate_duplicate_dependencies # :nodoc:

##
# Checks that the gem does not depend on itself.
# Checks that dependencies use requirements as we recommend. Warnings are
# issued when dependencies are open-ended or overly strict for semantic
# versioning.

def validate_dependencies # :nodoc:
warning_messages = []
@specification.dependencies.each do |dep|
if dep.name == @specification.name # warn on self reference
warning_messages << "Self referencing dependency is unnecessary and strongly discouraged."
end

prerelease_dep = dep.requirements_list.any? do |req|
Gem::Requirement.new(req).prerelease?
end

warning_messages << "prerelease dependency on #{dep} is not recommended" if
prerelease_dep && !@specification.version.prerelease?

open_ended = dep.requirement.requirements.all? do |op, version|
!version.prerelease? && [">", ">="].include?(op)
end

next unless open_ended
op, dep_version = dep.requirement.requirements.first

segments = dep_version.segments

base = segments.first 2

recommendation = if [">", ">="].include?(op) && segments == [0]
" use a bounded requirement, such as \"~> x.y\""
else
bugfix = if op == ">"
", \"> #{dep_version}\""
elsif op == ">=" && base != segments
", \">= #{dep_version}\""
end

" if #{dep.name} is semantically versioned, use:\n" \
" add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}"
end

warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n"
end
if warning_messages.any?
warning_messages.each {|warning_message| warning warning_message }
Expand Down
28 changes: 28 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,24 @@ def test
}, insns: [:opt_new], call_threshold: 2
end

def test_opt_newarray_send_include_p
assert_compiles '[true, false]', %q{
def test(x)
[:y, 1, Object.new].include?(x)
end
[test(1), test("n")]
}, insns: [:opt_newarray_send], call_threshold: 1
end

def test_opt_duparray_send_include_p
assert_compiles '[true, false]', %q{
def test(x)
[:y, 1].include?(x)
end
[test(1), test("n")]
}, insns: [:opt_duparray_send], call_threshold: 1
end

def test_new_hash_empty
assert_compiles '{}', %q{
def test = {}
Expand Down Expand Up @@ -2550,6 +2568,16 @@ def test(str)
}, call_threshold: 2
end

def test_string_bytesize_multibyte
assert_compiles '4', %q{
def test(s)
s.bytesize
end

test("💎")
}, call_threshold: 2
end

def test_nil_value_nil_opt_with_guard
assert_compiles 'true', %q{
def test(val) = val.nil?
Expand Down
19 changes: 19 additions & 0 deletions test/rubygems/test_gem_package_tar_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ def setup
@tar_header = Gem::Package::TarHeader.new header
end

def test_decode_in_ractor
new_header = Ractor.new(@tar_header.to_s) do |str|
Gem::Package::TarHeader.from StringIO.new str
end.value

assert_headers_equal @tar_header, new_header
end if defined?(Ractor) && Ractor.instance_methods.include?(:value)

def test_encode_in_ractor
header_bytes = @tar_header.to_s

new_header = Ractor.new(header_bytes) do |str|
header = Gem::Package::TarHeader.from StringIO.new str
header.to_s
end.value

assert_headers_equal header_bytes, new_header
end if defined?(Ractor) && Ractor.instance_methods.include?(:value)

def test_self_from
io = TempIO.new @tar_header.to_s

Expand Down
22 changes: 1 addition & 21 deletions test/rubygems/test_gem_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2671,27 +2671,7 @@ def test_validate_dependencies
@a1.validate
end

expected = <<-EXPECTED
#{w}: prerelease dependency on b (>= 1.0.rc1) is not recommended
#{w}: prerelease dependency on c (>= 2.0.rc2, development) is not recommended
#{w}: open-ended dependency on i (>= 1.2) is not recommended
if i is semantically versioned, use:
add_runtime_dependency "i", "~> 1.2"
#{w}: open-ended dependency on j (>= 1.2.3) is not recommended
if j is semantically versioned, use:
add_runtime_dependency "j", "~> 1.2", ">= 1.2.3"
#{w}: open-ended dependency on k (> 1.2) is not recommended
if k is semantically versioned, use:
add_runtime_dependency "k", "~> 1.2", "> 1.2"
#{w}: open-ended dependency on l (> 1.2.3) is not recommended
if l is semantically versioned, use:
add_runtime_dependency "l", "~> 1.2", "> 1.2.3"
#{w}: open-ended dependency on o (>= 0) is not recommended
use a bounded requirement, such as "~> x.y"
#{w}: See https://guides.rubygems.org/specification-reference/ for help
EXPECTED

assert_equal expected, @ui.error, "warning"
assert_equal "", @ui.error, "warning"
end
end

Expand Down
55 changes: 55 additions & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)),
&Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)),
&Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)),
&Insn::BoxFixnum { val, state } => gen_box_fixnum(jit, asm, opnd!(val), &function.frame_state(state)),
Insn::Test { val } => gen_test(asm, opnd!(val)),
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Expand Down Expand Up @@ -450,6 +451,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::LoadSelf => gen_load_self(),
&Insn::LoadField { recv, id, offset, return_type: _ } => gen_load_field(asm, opnd!(recv), id, offset),
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)),
&Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)),
&Insn::ArrayMax { state, .. }
| &Insn::FixnumDiv { state, .. }
| &Insn::Throw { state, .. }
Expand Down Expand Up @@ -1328,6 +1331,50 @@ fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd {
asm_ccall!(asm, rb_jit_array_len, array)
}

fn gen_array_include(
jit: &JITState,
asm: &mut Assembler,
elements: Vec<Opnd>,
target: Opnd,
state: &FrameState,
) -> lir::Opnd {
gen_prepare_non_leaf_call(jit, asm, state);

let num: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");

// After gen_prepare_non_leaf_call, the elements are spilled to the Ruby stack.
// The elements are at the bottom of the virtual stack, followed by the target.
// Get a pointer to the first element on the Ruby stack.
let stack_bottom = state.stack().len() - elements.len() - 1;
let elements_ptr = asm.lea(Opnd::mem(64, SP, stack_bottom as i32 * SIZEOF_VALUE_I32));

unsafe extern "C" {
fn rb_vm_opt_newarray_include_p(ec: EcPtr, num: c_long, elts: *const VALUE, target: VALUE) -> VALUE;
}
asm.ccall(
rb_vm_opt_newarray_include_p as *const u8,
vec![EC, num.into(), elements_ptr, target],
)
}

fn gen_dup_array_include(
jit: &JITState,
asm: &mut Assembler,
ary: VALUE,
target: Opnd,
state: &FrameState,
) -> lir::Opnd {
gen_prepare_non_leaf_call(jit, asm, state);

unsafe extern "C" {
fn rb_vm_opt_duparray_include_p(ec: EcPtr, ary: VALUE, target: VALUE) -> VALUE;
}
asm.ccall(
rb_vm_opt_duparray_include_p as *const u8,
vec![EC, ary.into(), target],
)
}

/// Compile a new hash instruction
fn gen_new_hash(
jit: &mut JITState,
Expand Down Expand Up @@ -1549,6 +1596,14 @@ fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd {
asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse))
}

fn gen_box_fixnum(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, state: &FrameState) -> lir::Opnd {
// Load the value, then test for overflow and tag it
let val = asm.load(val);
let shifted = asm.lshift(val, Opnd::UImm(1));
asm.jo(side_exit(jit, state, BoxFixnumOverflow));
asm.or(shifted, Opnd::UImm(RUBY_FIXNUM_FLAG as u64))
}

fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd {
gen_prepare_leaf_call_with_gc(asm, state);

Expand Down
1 change: 1 addition & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,7 @@ pub(crate) mod ids {
name: freeze
name: minusat content: b"-@"
name: aref content: b"[]"
name: len
name: _as_heap
}

Expand Down
23 changes: 22 additions & 1 deletion zjit/src/cruby_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub fn init() -> Annotations {
annotate!(rb_mKernel, "itself", inline_kernel_itself);
annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p);
annotate!(rb_mKernel, "===", inline_eqq);
annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "bytesize", inline_string_bytesize);
annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "getbyte", inline_string_getbyte);
Expand Down Expand Up @@ -305,6 +305,27 @@ fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins
None
}


fn inline_string_bytesize(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
if args.is_empty() && fun.likely_a(recv, types::String, state) {
let recv = fun.coerce_to(block, recv, types::String, state);
let len = fun.push_insn(block, hir::Insn::LoadField {
recv,
id: ID!(len),
offset: RUBY_OFFSET_RSTRING_LEN as i32,
return_type: types::CInt64,
});

let result = fun.push_insn(block, hir::Insn::BoxFixnum {
val: len,
state,
});

return Some(result);
}
None
}

fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
let &[index] = args else { return None; };
if fun.likely_a(index, types::Fixnum, state) {
Expand Down
Loading