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
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
env:
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
RUN_OPTS: ${{ matrix.run_opts }}
SPECOPTS: ${{ matrix.specopts }}
SPECOPTS: ${{ matrix.specopts }} -B../src/spec/zjit.mspec
TESTOPTS: ${{ matrix.testopts }}
RUST_BACKTRACE: 1
ZJIT_RB_BUG: 1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
RUN_OPTS: ${{ matrix.run_opts }}
YJIT_BENCH_OPTS: ${{ matrix.yjit_bench_opts }}
SPECOPTS: ${{ matrix.specopts }}
SPECOPTS: ${{ matrix.specopts }} -B../src/spec/zjit.mspec
TESTOPTS: ${{ matrix.testopts }}
RUBY_DEBUG: ci
BUNDLE_JOBS: 8 # for yjit-bench
Expand Down
44 changes: 21 additions & 23 deletions set.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ set_i_add(VALUE set, VALUE item)
* call-seq:
* add?(object) -> self or nil
*
* Like #add, but returns +nil+ if +object+ is already in +self+:
* Like #add, but returns +nil+ if the given +object+ is already in +self+:
*
* set = Set[0, 1, 2]
* set.add?(:foo) # => Set[0, 1, 2, :foo]
Expand Down Expand Up @@ -2432,41 +2432,39 @@ rb_set_size(VALUE set)
* or greater than a given object.
* - #==: Returns whether +self+ and a given enumerable are equal,
* as determined by Object#eql?.
*
* === Methods for Querying
*
* - #compare_by_identity?:
* Returns whether the set considers only identity
* Returns whether +self+ considers only identity
* when comparing elements.
* - #size (aliased as #length):
* Returns the count of elements.
* - #empty?:
* Returns whether the set has no elements.
* - #include? (aliased as #member? and #===):
* Returns whether a given object is an element in the set.
* - #subset? (aliased as #<=):
* Returns whether a given object is a subset of the set.
* - #proper_subset? (aliased as #<):
* Returns whether a given enumerable is a proper subset of the set.
* - #superset? (aliased as #>=):
* Returns whether a given enumerable is a superset of the set.
* Returns whether the given enumerable is a proper subset of +self+.
* - #proper_superset? (aliased as #>):
* Returns whether a given enumerable is a proper superset of the set.
* Returns whether the given enumerable is a proper superset of +self+.
* - #subset? (aliased as #<=):
* Returns whether the given object is a subset of +self+.
* - #superset? (aliased as #>=):
* Returns whether the given enumerable is a superset of +self+.
*
* === Methods for Querying
*
* - #disjoint?:
* Returns +true+ if the set and a given enumerable
* have no common elements, +false+ otherwise.
* Returns whether no element of the given enumerable is present in +self+.
* - #empty?:
* Returns whether +self+ contains no elements.
* - #include? (aliased as #member? and #===):
* Returns whether the given object is an element of +self+.
* - #intersect?:
* Returns +true+ if the set and a given enumerable:
* have any common elements, +false+ otherwise.
* Returns whether +self+ and the given enumerable have any elements in common.
* - #size (aliased as #length):
* Returns the number of elements in +self+.
*
* === Methods for Assigning
*
* - #add (aliased as #<<):
* Adds the given object to +self+, returns +self+.
* Adds the given object to +self+; returns +self+.
* - #add?:
* Like #add, but returns +nil+ if the given object is already in +self+.
* - #merge:
* Adds the elements of the given enumerables to +self+; returns +self+.
* Adds each element of each of the given enumerables to +self+; returns +self+.
* - #replace:
* Replaces the contents of +self+ with the contents of the given enumerable;
* returns +self+.
Expand Down
2 changes: 2 additions & 0 deletions spec/zjit.mspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Fails with --zjit-disable-hir-opt. See https://github.com/Shopify/ruby/issues/970
MSpec.register(:exclude, "Kernel#eval updates a local in a scope above a surrounding block scope")
3 changes: 3 additions & 0 deletions test/.excludes-zjit/TestKeywordArguments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# See <https://github.com/Shopify/ruby/issues/970>.
# This tests fail with --zjit-disable-hir-opt
exclude(:test_required_keyword_with_newline, 'local assignment within eval')
8 changes: 8 additions & 0 deletions test/.excludes-zjit/TestParse.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# See <https://github.com/Shopify/ruby/issues/970>.
# These tests fail with --zjit-disable-hir-opt
exclude(:test_utf8_bom, 'local assignment within eval')
exclude(:test_pow_asgn, 'local assignment within eval')
exclude(:test_backquote, 'local assignment within eval')
exclude(:test_dot_in_next_line, 'local assignment within eval')
exclude(:test_here_document, 'local assignment within eval')
exclude(:test_magic_comment, 'local assignment within eval')
3 changes: 3 additions & 0 deletions test/.excludes-zjit/TestString.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# See <https://github.com/Shopify/ruby/issues/970>.
# This tests fail with --zjit-disable-hir-opt
exclude(:test_unknown_string_option, 'local assignment within eval')
3 changes: 3 additions & 0 deletions test/.excludes-zjit/TestString2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# See <https://github.com/Shopify/ruby/issues/970>.
# This tests fail with --zjit-disable-hir-opt
exclude(:test_unknown_string_option, 'local assignment within eval')
75 changes: 74 additions & 1 deletion test/digest/test_digest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,80 @@ class TestCRC32 < Test::Unit::TestCase
Data1 => "352441c2",
Data2 => "171a3f5f",
}
end if defined?(Digest::CRC32)

def test_digest_byte_order
# CRC32("abc") = 0x352441C2; raw digest bytes must be big-endian (MSB first)
assert_equal [0x35, 0x24, 0x41, 0xC2], Digest::CRC32.digest("abc").bytes
end

def test_digest_length
assert_equal 4, Digest::CRC32.new.digest_length
end

def test_block_length
assert_equal 8, Digest::CRC32.new.block_length
end

def test_empty_string
assert_equal "00000000", Digest::CRC32.hexdigest("")
end

def test_single_null_byte
assert_equal "d202ef8d", Digest::CRC32.hexdigest("\x00".b)
end

def test_high_bytes
# Exercises the unsigned byte masking (& 0xFF) in the update loop
assert_equal "08eaaf6d", Digest::CRC32.hexdigest("\xFF\xFE\xFD".b)
end

def test_all_byte_values
# Exercises every entry in the lookup table
assert_equal "29058c73", Digest::CRC32.hexdigest((0..255).map(&:chr).join.b)
end

def test_incremental_equals_one_shot
inc = Digest::CRC32.new
inc << "a" << "b" << "c"
assert_equal "352441c2", inc.hexdigest
end

def test_clone_mid_stream_independence
d = Digest::CRC32.new
d << "ab"
copy = d.clone
d << "c"
copy << "c"
assert_equal d.hexdigest, copy.hexdigest
end

def test_reset
d = Digest::CRC32.new
d << "abc"
d.reset
d << "abc"
assert_equal "352441c2", d.hexdigest
end

def test_digest_is_non_destructive
d = Digest::CRC32.new
d << "abc"
assert_equal d.hexdigest, d.hexdigest
end

def test_digest_bang_resets_state
d = Digest::CRC32.new
d << "abc"
d.hexdigest!
assert_equal "00000000", d.hexdigest
end

def test_initialize_copy_into_frozen_raises
dest = Digest::CRC32.allocate
dest.freeze
assert_raise(FrozenError) { dest.send(:initialize_copy, Digest::CRC32.new) }
end
end

class TestBase < Test::Unit::TestCase
def test_base
Expand Down
3 changes: 2 additions & 1 deletion zjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);

enum zjit_struct_offsets {
ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param)
ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param),
ISEQ_BODY_OFFSET_OUTER_VARIABLES = offsetof(struct rb_iseq_constant_body, outer_variables)
};

// Special JITFrame used by all C method calls. We don't control the native
Expand Down
4 changes: 4 additions & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ fn main() {
.allowlist_function("rb_zjit_iseq_inspect")
.allowlist_function("rb_zjit_iseq_insn_set")
.allowlist_function("rb_zjit_local_id")
.allowlist_function("rb_id_table_lookup")
.allowlist_function("rb_set_cfp_(pc|sp)")
.allowlist_function("rb_c_method_tracing_currently_enabled")
.allowlist_function("rb_zjit_method_tracing_currently_enabled")
Expand Down Expand Up @@ -449,6 +450,9 @@ fn main() {
.blocklist_type("ID")
.blocklist_type("rb_iseq_constant_body")

// We only need id_table as an opaque pointer to pass to its APIs
.opaque_type("rb_id_table")

// Avoid binding to stuff we don't use
.blocklist_item("rb_thread_struct.*")
.opaque_type("rb_thread_struct.*")
Expand Down
3 changes: 3 additions & 0 deletions zjit/src/backend/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,9 @@ impl Assembler {
Insn::PosMarker(..) => {
pos_markers.push((insn_idx, cb.get_write_ptr()))
}
Insn::PosMarkerAtBlockEnd(..) => {
unreachable!("PosMarkerAtBlockEnd should have been lowered by linearize_instructions");
},
Insn::BakeString(text) => {
for byte in text.as_bytes() {
cb.write_byte(*byte);
Expand Down
48 changes: 46 additions & 2 deletions zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,10 @@ pub enum Insn {
// Mark a position in the generated code
PosMarker(PosMarkerFn),

/// Mark a position at the end of the current LIR block.
/// This is lowered to PosMarker during linearize_instructions().
PosMarkerAtBlockEnd(PosMarkerFn),

/// Shift a value right by a certain amount (signed).
RShift { opnd: Opnd, shift: Opnd, out: Opnd },

Expand Down Expand Up @@ -970,7 +974,8 @@ macro_rules! for_each_operand_impl {
Insn::Comment(_) |
Insn::CPop { .. } |
Insn::PadPatchPoint |
Insn::PosMarker(_) => {},
Insn::PosMarker(_) |
Insn::PosMarkerAtBlockEnd(_) => {},

Insn::CPopInto(opnd) |
Insn::CPush(opnd) |
Expand Down Expand Up @@ -1149,6 +1154,7 @@ impl Insn {
Insn::PatchPoint(..) => "PatchPoint",
Insn::PadPatchPoint => "PadPatchPoint",
Insn::PosMarker(_) => "PosMarker",
Insn::PosMarkerAtBlockEnd(_) => "PosMarkerAtBlockEnd",
Insn::RShift { .. } => "RShift",
Insn::Store { .. } => "Store",
Insn::Sub { .. } => "Sub",
Expand Down Expand Up @@ -1827,8 +1833,14 @@ impl Assembler
}

// Process each instruction, expanding branch params if needed
let mut block_end_pos_marker = None;
for insn in &block.insns {
self.expand_branch_insn(insn, &mut insns);
if let Insn::PosMarkerAtBlockEnd(marker) = insn {
assert!(block_end_pos_marker.is_none(), "only one PosMarkerAtBlockEnd is supported per block");
block_end_pos_marker = Some(marker.clone());
} else {
self.expand_branch_insn(insn, &mut insns);
}
}

// Eliminate redundant jumps: if the last instruction is an
Expand All @@ -1843,6 +1855,10 @@ impl Assembler
}
}

if let Some(marker) = block_end_pos_marker {
insns.push(Insn::PosMarker(marker));
}

// Make sure we don't stomp on the next function
if block_id.0 == num_blocks - 1 {
push_insns_with_perf_symbol(&mut insns, "PadPatchPoint", |insns| {
Expand Down Expand Up @@ -3786,6 +3802,34 @@ impl Assembler {
self.push_insn(Insn::PosMarker(Rc::new(marker_fn)));
}

/// Insert a marker that linearize_instructions lowers to a PosMarker at the
/// end of the current block, after its terminator instructions.
pub fn pos_marker_at_block_end(&mut self, marker_fn: impl Fn(CodePtr, &CodeBlock) + 'static) {
let block_id = self.current_block_id;
let block = &self.basic_blocks[block_id.0];
let len = block.insns.len();
assert!(
len > 0 && block.insns[len - 1].is_terminator(),
"pos_marker_at_block_end requires the current block to end with a terminator"
);

// A block may end with two terminators, such as a conditional jump
// followed by an unconditional jump. Keep the pseudo-instruction before
// the whole terminator suffix so the LIR block itself still ends with
// terminators; linearize_instructions emits the real PosMarker after the
// block's instructions.
let insert_pos = if block.insns.get(len - 2).is_some_and(Insn::is_terminator) {
len - 2
} else {
len - 1
};

self.idx += 1;
let block = &mut self.basic_blocks[block_id.0];
block.insns.insert(insert_pos, Insn::PosMarkerAtBlockEnd(Rc::new(marker_fn)));
block.insn_ids.insert(insert_pos, None);
}

#[must_use]
pub fn rshift(&mut self, opnd: Opnd, shift: Opnd) -> Opnd {
let out = self.new_vreg(Opnd::match_num_bits(&[opnd, shift]));
Expand Down
3 changes: 3 additions & 0 deletions zjit/src/backend/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,9 @@ impl Assembler {
Insn::PosMarker(..) => {
pos_markers.push((insn_idx, cb.get_write_ptr()));
},
Insn::PosMarkerAtBlockEnd(..) => {
unreachable!("PosMarkerAtBlockEnd should have been lowered by linearize_instructions");
},

Insn::BakeString(text) => {
for byte in text.as_bytes() {
Expand Down
Loading