Skip to content

Commit d225bb8

Browse files
authored
ZJIT: Compile IsA into load + compare for String/Array/Hash (ruby#15878)
Resolves Shopify#880 Implemented this by using the code generation for `GuardType` as a reference. Not sure if this is the best way to go about it, but it seems to work.
1 parent c27ae8d commit d225bb8

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

test/ruby/test_zjit.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4417,6 +4417,60 @@ def test
44174417
}, call_threshold: 14, num_profiles: 5
44184418
end
44194419

4420+
def test_is_a_string_special_case
4421+
assert_compiles '[true, false, false, false, false, false]', %q{
4422+
def test(x)
4423+
x.is_a?(String)
4424+
end
4425+
test("foo")
4426+
[test("bar"), test(1), test(false), test(:foo), test([]), test({})]
4427+
}
4428+
end
4429+
4430+
def test_is_a_array_special_case
4431+
assert_compiles '[true, true, false, false, false, false, false]', %q{
4432+
def test(x)
4433+
x.is_a?(Array)
4434+
end
4435+
test([])
4436+
[test([1,2,3]), test([]), test(1), test(false), test(:foo), test("foo"), test({})]
4437+
}
4438+
end
4439+
4440+
def test_is_a_hash_special_case
4441+
assert_compiles '[true, true, false, false, false, false, false]', %q{
4442+
def test(x)
4443+
x.is_a?(Hash)
4444+
end
4445+
test({})
4446+
[test({:a => "b"}), test({}), test(1), test(false), test(:foo), test([]), test("foo")]
4447+
}
4448+
end
4449+
4450+
def test_is_a_hash_subclass
4451+
assert_compiles 'true', %q{
4452+
class MyHash < Hash
4453+
end
4454+
def test(x)
4455+
x.is_a?(Hash)
4456+
end
4457+
test({})
4458+
test(MyHash.new)
4459+
}
4460+
end
4461+
4462+
def test_is_a_normal_case
4463+
assert_compiles '[true, false]', %q{
4464+
class MyClass
4465+
end
4466+
def test(x)
4467+
x.is_a?(MyClass)
4468+
end
4469+
test("a")
4470+
[test(MyClass.new), test("a")]
4471+
}
4472+
end
4473+
44204474
private
44214475

44224476
# Assert that every method call in `test_script` can be compiled by ZJIT

zjit/src/codegen.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1743,7 +1743,46 @@ fn gen_dup_array_include(
17431743
}
17441744

17451745
fn gen_is_a(asm: &mut Assembler, obj: Opnd, class: Opnd) -> lir::Opnd {
1746-
asm_ccall!(asm, rb_obj_is_kind_of, obj, class)
1746+
let builtin_type = match class {
1747+
Opnd::Value(value) if value == unsafe { rb_cString } => Some(RUBY_T_STRING),
1748+
Opnd::Value(value) if value == unsafe { rb_cArray } => Some(RUBY_T_ARRAY),
1749+
Opnd::Value(value) if value == unsafe { rb_cHash } => Some(RUBY_T_HASH),
1750+
_ => None
1751+
};
1752+
1753+
if let Some(builtin_type) = builtin_type {
1754+
asm_comment!(asm, "IsA by matching builtin type");
1755+
let ret_label = asm.new_label("is_a_ret");
1756+
let false_label = asm.new_label("is_a_false");
1757+
1758+
let val = match obj {
1759+
Opnd::Reg(_) | Opnd::VReg { .. } => obj,
1760+
_ => asm.load(obj),
1761+
};
1762+
1763+
// Check special constant
1764+
asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64));
1765+
asm.jnz(ret_label.clone());
1766+
1767+
// Check false
1768+
asm.cmp(val, Qfalse.into());
1769+
asm.je(false_label.clone());
1770+
1771+
let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS));
1772+
let obj_builtin_type = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64));
1773+
asm.cmp(obj_builtin_type, Opnd::UImm(builtin_type as u64));
1774+
asm.jmp(ret_label.clone());
1775+
1776+
// If we get here then the value was false, unset the Z flag
1777+
// so that csel_e will select false instead of true
1778+
asm.write_label(false_label);
1779+
asm.test(Opnd::UImm(1), Opnd::UImm(1));
1780+
1781+
asm.write_label(ret_label);
1782+
asm.csel_e(Qtrue.into(), Qfalse.into())
1783+
} else {
1784+
asm_ccall!(asm, rb_obj_is_kind_of, obj, class)
1785+
}
17471786
}
17481787

17491788
/// Compile a new hash instruction

0 commit comments

Comments
 (0)