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
56 changes: 26 additions & 30 deletions ext/json/parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,7 @@ static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfi
case '"': {
VALUE string = json_string_unescape(state, config, start, state->cursor, is_name, &positions);
state->cursor++;
return json_push_value(state, config, string);
return string;
}
case '\\': {
if (RB_LIKELY(positions.size < JSON_MAX_UNESCAPE_POSITIONS)) {
Expand Down Expand Up @@ -1271,12 +1271,16 @@ ALWAYS_INLINE(static) VALUE json_parse_string(JSON_ParserState *state, JSON_Pars
raise_parse_error("unexpected end of input, expected closing \"", state);
}

VALUE string;
if (RB_LIKELY(*state->cursor == '"')) {
VALUE string = json_string_fastpath(state, config, start, state->cursor, is_name);
string = json_string_fastpath(state, config, start, state->cursor, is_name);
state->cursor++;
return json_push_value(state, config, string);
}
return json_parse_escaped_string(state, config, is_name, start);
else {
string = json_parse_escaped_string(state, config, is_name, start);
}

return string;
}

#if JSON_CPU_LITTLE_ENDIAN_64BITS
Expand Down Expand Up @@ -1450,7 +1454,7 @@ static inline void json_value_completed(json_frame *frame)
frame->phase = (enum json_frame_phase) frame->type;
}

static inline bool json_match_keyword(JSON_ParserState *state, const char *keyword, size_t offset)
ALWAYS_INLINE(static) bool json_match_keyword(JSON_ParserState *state, const char *keyword, size_t offset)
{
// It is assumed that since `keyword` is always a literal, the compiler is able to constantize this
// `strlen` and several other computations in that routine, such as eliminating the `if (resumable)` branch.
Expand Down Expand Up @@ -1487,77 +1491,67 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
JSON_PHASE_VALUE:
json_eat_whitespace(state);

VALUE value;
switch (peek(state)) {
case 'n':
if (json_match_keyword(state, "null", 0)) {
json_push_value(state, config, Qnil);
json_value_completed(frame);
value = Qnil;
break;
}

raise_parse_error("unexpected token %s", state);
case 't':
if (json_match_keyword(state, "true", 0)) {
json_push_value(state, config, Qtrue);
json_value_completed(frame);
value = Qtrue;
break;
}

raise_parse_error("unexpected token %s", state);
case 'f':
if (json_match_keyword(state, "false", 1)) {
json_push_value(state, config, Qfalse);
json_value_completed(frame);
value = Qfalse;
break;
}

raise_parse_error("unexpected token %s", state);
case 'N':
// Note: memcmp with a small power of two compile to an integer comparison
if (config->allow_nan && json_match_keyword(state, "NaN", 1)) {
json_push_value(state, config, CNaN);
json_value_completed(frame);
value = CNaN;
break;
}

raise_parse_error("unexpected token %s", state);
case 'I':
if (config->allow_nan && json_match_keyword(state, "Infinity", 0)) {
json_push_value(state, config, CInfinity);
json_value_completed(frame);
value = CInfinity;
break;
}

raise_parse_error("unexpected token %s", state);
case '-': {
state->cursor++;
if (config->allow_nan && json_match_keyword(state, "Infinity", 0)) {
json_push_value(state, config, CMinusInfinity);
json_value_completed(frame);
break;
value = CMinusInfinity;
} else {
value = json_parse_negative_number(state, config);
}

json_push_value(state, config, json_parse_negative_number(state, config));
json_value_completed(frame);
break;
}
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
json_push_value(state, config, json_parse_positive_number(state, config));
json_value_completed(frame);
value = json_parse_positive_number(state, config);
break;
case '"':
// %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"}
json_parse_string(state, config, false);
json_value_completed(frame);
value = json_parse_string(state, config, false);
break;
case '[': {
state->cursor++;
json_eat_whitespace(state);

if (peek(state) == ']') {
state->cursor++;
json_push_value(state, config, json_decode_array(state, config, 0));
json_value_completed(frame);
value = json_decode_array(state, config, 0);
break;
}

Expand All @@ -1584,8 +1578,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)

if (peek(state) == '}') {
state->cursor++;
json_push_value(state, config, json_decode_object(state, config, 0));
json_value_completed(frame);
value = json_decode_object(state, config, 0);
break;
}

Expand All @@ -1611,6 +1604,9 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
default:
raise_parse_error("unexpected character: %s", state);
}

json_push_value(state, config, value);
json_value_completed(frame);
break;
}

Expand All @@ -1621,7 +1617,7 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
json_eat_whitespace(state);

if (RB_LIKELY(peek(state) == '"')) {
json_parse_string(state, config, true);
json_push_value(state, config, json_parse_string(state, config, true));
frame->phase = JSON_PHASE_OBJECT_COLON;
goto JSON_PHASE_OBJECT_COLON;
} else {
Expand Down
7 changes: 4 additions & 3 deletions zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use std::mem::take;
use std::rc::Rc;
use crate::bitset::BitSet;
use crate::codegen::{local_size_and_idx_to_ep_offset, perf_symbol_range_start, perf_symbol_range_end};
use crate::cruby::{IseqPtr, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary};
use crate::cruby::{IseqPtr, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, vm_stack_canary, YarvInsnIdx };
use crate::hir::{Invariant, SideExitReason};
use crate::hir;
use crate::options::{TraceExits, PerfMap, get_option};
use crate::cruby::VALUE;
use crate::payload::IseqVersionRef;
use crate::payload::{IseqVersionRef, get_or_create_iseq_payload};
use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, side_exit_counter, CompileError};
use crate::virtualmem::CodePtr;
use crate::asm::{CodeBlock, Label};
Expand Down Expand Up @@ -2402,7 +2402,8 @@ impl Assembler

fn compile_exit_recompile(asm: &mut Assembler, exit: &SideExit) {
if let Some(recompile) = &exit.recompile {

let payload = get_or_create_iseq_payload(exit.iseq);
payload.reset_profiles_remaining(recompile.insn_idx as YarvInsnIdx);
use crate::codegen::exit_recompile;
asm_comment!(asm, "profile and maybe recompile");
asm_ccall!(asm, exit_recompile,
Expand Down
34 changes: 18 additions & 16 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,9 +699,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
let val_type = function.type_of(*val);
gen_has_type(jit, asm, opnd!(val), val_type, *expected)
}
Insn::GuardType { val, guard_type, state } => {
let val_type = function.type_of(*val);
gen_guard_type(jit, asm, opnd!(val), val_type, *guard_type, &function.frame_state(*state))
&Insn::GuardType { val, guard_type, state, recompile } => {
let val_type = function.type_of(val);
gen_guard_type(jit, asm, opnd!(val), val_type, guard_type, recompile, &function.frame_state(state))
}
&Insn::GuardBitEquals { val, expected, reason, state, recompile } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, recompile, &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)),
Expand All @@ -711,9 +711,11 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
Insn::CCall { cfunc, recv, args, name, owner: _, 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).
// There's no test case for this because no core cfuncs have this many parameters. But C extensions could have such methods.
Insn::CCallWithFrame { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() =>
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs),
// We're currently emitting a CCallWithFrame for `super` in to a cfunction.
// We can't lower to `gen_send_without_block` because the
// source opcode isn't necessarily `opt_send_without_block`
// and so the interpreter stack layout may be incompatible.
Insn::CCallWithFrame { cd, state, args, block, .. } if args.len() + 1 > C_ARG_OPNDS.len() => return Err(*state),
Insn::CCallWithFrame { cfunc, recv, name, args, cme, state, block, .. } =>
gen_ccall_with_frame(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *block, &function.frame_state(*state)),
Insn::CCallVariadic { cfunc, recv, name, args, cme, state, block, return_type: _, elidable: _ } => {
Expand Down Expand Up @@ -2523,33 +2525,33 @@ fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_typ
}

/// Compile a type check with a side exit
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_type: Type, guard_type: Type, state: &FrameState) -> lir::Opnd {
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_type: Type, guard_type: Type, recompile: Option<Recompile>, state: &FrameState) -> lir::Opnd {
let is_known_heap_basic_object = val_type.is_subtype(types::HeapBasicObject);
gen_incr_counter(asm, Counter::guard_type_count);
if guard_type.is_subtype(types::Fixnum) {
asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64));
asm.jz(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jz(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_subtype(types::Flonum) {
// Flonum: (val & RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG
let masked = asm.and(val, Opnd::UImm(RUBY_FLONUM_MASK as u64));
asm.cmp(masked, Opnd::UImm(RUBY_FLONUM_FLAG as u64));
asm.jne(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jne(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_subtype(types::StaticSymbol) {
// Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG
// Use 8-bit comparison like YJIT does.
// If `val` is a constant (rare but possible), put it in a register to allow masking.
let val = asm.load_imm(val);
asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64));
asm.jne(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jne(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_subtype(types::NilClass) {
asm.cmp(val, Qnil.into());
asm.jne(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jne(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_subtype(types::TrueClass) {
asm.cmp(val, Qtrue.into());
asm.jne(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jne(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_subtype(types::FalseClass) {
asm.cmp(val, Qfalse.into());
asm.jne(jit, side_exit(jit, state, GuardType(guard_type)));
asm.jne(jit, side_exit_with_recompile(jit, state, GuardType(guard_type), recompile));
} else if guard_type.is_immediate() {
// All immediate types' guard should have been handled above
panic!("unexpected immediate guard type: {guard_type}");
Expand All @@ -2560,7 +2562,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_t
// TODO: Max thinks codegen should not care about the shapes of the operands except to create them. (Shopify/ruby#685)
let val = asm.load_mem(val);

let side_exit = side_exit(jit, state, GuardType(guard_type));
let side_exit = side_exit_with_recompile(jit, state, GuardType(guard_type), recompile);
if !is_known_heap_basic_object {
// Check if it's a special constant
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
Expand All @@ -2577,7 +2579,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_t
asm.cmp(klass, Opnd::Value(expected_class));
asm.jne(jit, side_exit);
} else if let Some(builtin_type) = guard_type.builtin_type_equivalent() {
let side = side_exit(jit, state, GuardType(guard_type));
let side = side_exit_with_recompile(jit, state, GuardType(guard_type), recompile);

if !is_known_heap_basic_object {
// Check special constant
Expand All @@ -2596,7 +2598,7 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_t
asm.cmp(tag, Opnd::UImm(builtin_type as u64));
asm.jne(jit, side);
} else if guard_type.bit_equal(types::HeapBasicObject) {
let side_exit = side_exit(jit, state, GuardType(guard_type));
let side_exit = side_exit_with_recompile(jit, state, GuardType(guard_type), recompile);
asm.cmp(val, Opnd::Value(Qfalse));
asm.je(jit, side_exit.clone());
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
Expand Down
46 changes: 46 additions & 0 deletions zjit/src/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,52 @@ fn test_invokesuper_to_cfunc_varargs() {
"#), @r#"["MyString", true]"#);
}

#[test]
fn test_invokesuper_to_cfunc_with_too_many_args_exits() {
unsafe extern "C" fn test_six_args(
_self: VALUE,
a: VALUE,
b: VALUE,
c: VALUE,
d: VALUE,
e: VALUE,
f: VALUE,
) -> VALUE {
unsafe { rb_ary_new_from_args(6, a, b, c, d, e, f) }
}

with_rubyvm(|| {
let superclass = define_class("ZJITSixArgs", unsafe { rb_cObject });
unsafe {
rb_define_method(
superclass,
c"six".as_ptr(),
Some(std::mem::transmute::<
unsafe extern "C" fn(VALUE, VALUE, VALUE, VALUE, VALUE, VALUE, VALUE) -> VALUE,
unsafe extern "C" fn(VALUE) -> VALUE,
>(test_six_args)),
6,
);
}
});

assert_snapshot!(assert_compiles_allowing_exits(r#"
class ZJITSixArgsSubclass < ZJITSixArgs
def six(a, b, c, d, e, f)
super
end
end

def test
ZJITSixArgsSubclass.new.six(1, 2, 3, 4, 5, 6)
end

test
test
test
"#), @"[1, 2, 3, 4, 5, 6]");
}

#[test]
fn test_string_new_preserves_string_arg() {
assert_snapshot!(inspect(r#"
Expand Down
9 changes: 8 additions & 1 deletion zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
use std::convert::From;
use std::ffi::{c_void, CString, CStr};
use std::fmt::{Debug, Display, Formatter};
use std::os::raw::{c_char, c_int, c_uint};
use std::os::raw::{c_char, c_int, c_long, c_uint};
use std::panic::{catch_unwind, UnwindSafe};

use crate::cast::IntoUsize as _;
Expand Down Expand Up @@ -132,6 +132,7 @@ unsafe extern "C" {
pub fn rb_float_new(d: f64) -> VALUE;

pub fn rb_hash_empty_p(hash: VALUE) -> VALUE;
pub fn rb_ary_new_from_args(n: c_long, ...) -> VALUE;
pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE;
pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE;
pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE;
Expand Down Expand Up @@ -165,6 +166,12 @@ unsafe extern "C" {
pub fn rb_vm_stack_canary() -> VALUE;
pub fn rb_vm_push_cfunc_frame(cme: *const rb_callable_method_entry_t, recv_idx: c_int);
pub fn rb_obj_class(klass: VALUE) -> VALUE;
pub fn rb_define_method(
klass: VALUE,
mid: *const c_char,
func: Option<unsafe extern "C" fn(args: VALUE) -> VALUE>,
arity: c_int,
);
pub fn rb_vm_objtostring(reg_cfp: CfpPtr, recv: VALUE, cd: *const rb_call_data) -> VALUE;
}

Expand Down
Loading