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: 2 additions & 0 deletions doc/maintainers.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ consensus on ruby-core/ruby-dev.
* Kevin Newton ([kddnewton])
* Eileen Uchitelle ([eileencodes])
* Aaron Patterson ([tenderlove])
* Earlopain ([earlopain])
* https://github.com/ruby/prism
* https://rubygems.org/gems/prism

Expand Down Expand Up @@ -673,6 +674,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes
[colby-swandale]: https://github.com/colby-swandale
[drbrain]: https://github.com/drbrain
[duerst]: https://github.com/duerst
[earlopain]: https://github.com/earlopain
[eban]: https://github.com/eban
[eileencodes]: https://github.com/eileencodes
[hasumikin]: https://github.com/hasumikin
Expand Down
43 changes: 19 additions & 24 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -423,27 +423,28 @@ def dump_options_command_line(options)

# Return the value that should be dumped for the version option.
def dump_options_version(version)
checking =
case version
when "current"
RUBY_VERSION
when "latest"
nil
when "nearest"
if RUBY_VERSION <= "3.3"
"3.3"
elsif RUBY_VERSION >= "4.1"
"4.1"
else
RUBY_VERSION
end
case version
when "current"
version_string_to_number(RUBY_VERSION) || raise(CurrentVersionError, RUBY_VERSION)
when "latest", nil
0 # Handled in pm_parser_init
when "nearest"
dump = version_string_to_number(RUBY_VERSION)
return dump if dump
if RUBY_VERSION < "3.3"
version_string_to_number("3.3")
else
version
0 # Handled in pm_parser_init
end
else
version_string_to_number(version) || raise(ArgumentError, "invalid version: #{version}")
end
end

case checking
when nil
0 # Handled in pm_parser_init
# Converts a version string like "4.0.0" or "4.0" into a number.
# Returns nil if the version is unknown.
def version_string_to_number(version)
case version
when /\A3\.3(\.\d+)?\z/
1
when /\A3\.4(\.\d+)?\z/
Expand All @@ -452,12 +453,6 @@ def dump_options_version(version)
3
when /\A4\.1(\.\d+)?\z/
4
else
if version == "current"
raise CurrentVersionError, RUBY_VERSION
else
raise ArgumentError, "invalid version: #{version}"
end
end
end

Expand Down
20 changes: 8 additions & 12 deletions prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,18 +205,14 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version));
}
} else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) {
const char *nearest_version;

if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) {
nearest_version = "3.3";
} else if (ruby_version[0] > '4' || (ruby_version[0] == '4' && ruby_version[2] > '1')) {
nearest_version = "4.1";
} else {
nearest_version = ruby_version;
}

if (!pm_options_version_set(options, nearest_version, 3)) {
rb_raise(rb_eArgError, "invalid nearest version: %s", nearest_version);
if (!pm_options_version_set(options, ruby_version, 3)) {
// Prism doesn't know this specific version. Is it lower?
if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) {
options->version == PM_OPTIONS_VERSION_CRUBY_3_3;
} else {
// Must be higher.
options->version == PM_OPTIONS_VERSION_LATEST;
}
}
} else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) {
rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value);
Expand Down
4 changes: 4 additions & 0 deletions test/prism/api/parse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ def test_version_current
end
end

def test_nearest
assert Prism.parse_success?("1 + 1", version: "nearest")
end

def test_scopes
assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
Expand Down
56 changes: 0 additions & 56 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,12 +538,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, &function.frame_state(state)),
&Insn::GuardAnyBitSet { val, mask, reason, state } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)),
&Insn::GuardNoBitsSet { val, mask, reason, state } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)),
Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)),
Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)),
&Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
&Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
&Insn::GuardSuperMethodEntry { lep, cme, state } => no_output!(gen_guard_super_method_entry(jit, asm, opnd!(lep), cme, &function.frame_state(state))),
Insn::GetBlockHandler { lep } => gen_get_block_handler(asm, opnd!(lep)),
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)),
// Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args).
Expand Down Expand Up @@ -585,7 +581,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::ToArray { val, state } => { gen_to_array(jit, asm, opnd!(val), &function.frame_state(state)) },
&Insn::DefinedIvar { self_val, id, pushval, .. } => { gen_defined_ivar(asm, opnd!(self_val), id, pushval) },
&Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) },
&Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)),
Insn::LoadPC => gen_load_pc(asm),
Insn::LoadEC => gen_load_ec(),
&Insn::GetEP { level } => gen_get_ep(asm, level),
Expand Down Expand Up @@ -795,25 +790,6 @@ fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, ep_offset: u32, le
asm.load(Opnd::mem(VALUE_BITS, ep, offset))
}

fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd {
let recv = asm.load(recv);
// It's a heap object, so check the frozen flag
let flags = asm.load(Opnd::mem(64, recv, RUBY_OFFSET_RBASIC_FLAGS));
asm.test(flags, (RUBY_FL_FREEZE as u64).into());
// Side-exit if frozen
asm.jnz(side_exit(jit, state, GuardNotFrozen));
recv
}

fn gen_guard_not_shared(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd {
let recv = asm.load(recv);
// It's a heap object, so check the shared flag
let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS));
asm.test(flags, (RUBY_ELTS_SHARED as u64).into());
asm.jnz(side_exit(jit, state, SideExitReason::GuardNotShared));
recv
}

fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd {
asm.cmp(left, right);
asm.jge(side_exit(jit, state, SideExitReason::GuardLess));
Expand All @@ -826,28 +802,6 @@ fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right:
left
}

/// Guard that the method entry at ep[VM_ENV_DATA_INDEX_ME_CREF] matches the expected CME.
/// This ensures we're calling super from the expected method context.
fn gen_guard_super_method_entry(
jit: &JITState,
asm: &mut Assembler,
lep: Opnd,
cme: *const rb_callable_method_entry_t,
state: &FrameState,
) {
asm_comment!(asm, "guard super method entry");
let ep_me_opnd = Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF);
let ep_me = asm.load(ep_me_opnd);
asm.cmp(ep_me, Opnd::UImm(cme as u64));
asm.jne(side_exit(jit, state, SideExitReason::GuardSuperMethodEntry));
}

/// Get the block handler from ep[VM_ENV_DATA_INDEX_SPECVAL] at the local EP (LEP).
fn gen_get_block_handler(asm: &mut Assembler, lep: Opnd) -> Opnd {
asm_comment!(asm, "get block handler from LEP");
asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL))
}

fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd {
unsafe extern "C" {
fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE;
Expand Down Expand Up @@ -1243,16 +1197,6 @@ fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: Opnd, right:
asm_ccall!(asm, rb_ary_concat, left, right);
}

fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: ShapeId, state: &FrameState) -> Opnd {
gen_incr_counter(asm, Counter::guard_shape_count);
let shape_id_offset = unsafe { rb_shape_id_offset() };
let val = asm.load(val);
let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, val, shape_id_offset);
asm.cmp(shape_opnd, Opnd::UImm(shape.0 as u64));
asm.jne(side_exit(jit, state, SideExitReason::GuardShape(shape)));
val
}

fn gen_load_pc(asm: &mut Assembler) -> Opnd {
asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC))
}
Expand Down
3 changes: 3 additions & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,9 @@ pub(crate) mod ids {
name: _shape_id
name: _env_data_index_flags
name: _env_data_index_specval
name: _ep_method_entry
name: _ep_specval
name: _rbasic_flags
}

/// Get an CRuby `ID` to an interned string, e.g. a particular method name.
Expand Down
11 changes: 5 additions & 6 deletions zjit/src/cruby_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,8 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
{
let recv = fun.coerce_to(block, recv, types::ArrayExact, state);
let index = fun.coerce_to(block, index, types::Fixnum, state);
let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state });
let recv = fun.push_insn(block, hir::Insn::GuardNotShared { recv, state });
fun.guard_not_frozen(block, recv, state);
fun.guard_not_shared(block, recv, state);

// Bounds check: unbox Fixnum index and guard 0 <= idx < length.
let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
Expand Down Expand Up @@ -381,9 +381,8 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
// Only inline the case of no arguments.
let &[] = args else { return None; };
// We know that all Array are HeapObject, so no need to insert a GuardType(HeapObject).
let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state });
Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state }))
fun.guard_not_shared(block, recv, state);
Some(fun.push_insn(block, hir::Insn::ArrayPop { array: recv, state }))
}

fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
Expand Down Expand Up @@ -476,7 +475,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir
let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) });
let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state });
// We know that all String are HeapObject, so no need to insert a GuardType(HeapObject).
let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state });
fun.guard_not_frozen(block, recv, state);
let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value });
// String#setbyte returns the fixnum provided as its `value` argument back to the caller.
Some(value)
Expand Down
Loading