Skip to content

Commit 5e77ea6

Browse files
committed
ZJIT: Don't fold StringEqual through an always-failing guard
1 parent 0a3580c commit 5e77ea6

3 files changed

Lines changed: 98 additions & 5 deletions

File tree

‎zjit/src/codegen_tests.rs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3616,6 +3616,29 @@ fn test_opt_eq_string_distinct_objects() {
36163616
assert_contains_opcode("test", YARVINSN_opt_eq);
36173617
}
36183618

3619+
#[test]
3620+
fn test_opt_eq_string_guard_fails_after_inlining() {
3621+
eval(r#"
3622+
# frozen_string_literal: true
3623+
class Foo
3624+
def self.bar(l, r) = l == r
3625+
end
3626+
def test(flag)
3627+
foo = Foo
3628+
if flag
3629+
foo.bar("a", "b")
3630+
else
3631+
foo.bar("a", :sym)
3632+
end
3633+
end
3634+
"#);
3635+
assert_snapshot!(inspect(r#"
3636+
test(true) # profile opt_eq in bar
3637+
test(true) # compile test, inlining bar with a Symbol argument on the untaken branch
3638+
[test(true), test(false)]
3639+
"#), @"[false, false]");
3640+
}
3641+
36193642
#[test]
36203643
fn test_opt_eqq_string_same_operand() {
36213644
assert_snapshot!(inspect(r#"

‎zjit/src/hir.rs‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6770,20 +6770,24 @@ impl Function {
67706770
}
67716771
}
67726772
&Insn::StringEqual { left, right } => {
6773-
let left = self.chase_insn(left);
6774-
let right = self.chase_insn(right);
67756773
// If both operands resolve to the same SSA value,
67766774
// String#== is guaranteed to be true.
6777-
if left == right {
6775+
if self.chase_insn(left) == self.chase_insn(right) {
67786776
self.new_insn(Insn::Const { val: Const::Value(Qtrue) })
67796777
} else {
6778+
// Look at the operand types as StringEqual sees them, without chasing through guards.
6779+
// After inlining, a known non-String can flow into a `GuardType String` that always
6780+
// fails at run-time, so we must not fold using the pre-guard value.
67806781
let left_type = self.type_of(left);
67816782
let right_type = self.type_of(right);
67826783
match (left_type.ruby_object(), right_type.ruby_object()) {
67836784
(Some(left_obj), Some(right_obj))
6784-
if left_obj.is_frozen() && right_obj.is_frozen() =>
6785+
if left_type.is_subtype(types::String)
6786+
&& right_type.is_subtype(types::String)
6787+
&& left_obj.is_frozen()
6788+
&& right_obj.is_frozen() =>
67856789
{
6786-
// For known frozen objects, evaluate String#== at compile time.
6790+
// For known frozen Strings, evaluate String#== at compile time.
67876791
let val = unsafe { rb_yarv_str_eql_internal(left_obj, right_obj) };
67886792
self.new_insn(Insn::Const { val: Const::Value(val) })
67896793
}

‎zjit/src/hir/opt_tests.rs‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16461,6 +16461,72 @@ mod hir_opt_tests {
1646116461
");
1646216462
}
1646316463

16464+
#[test]
16465+
fn test_not_fold_string_equal_through_failing_guard() {
16466+
eval(r#"
16467+
# frozen_string_literal: true
16468+
class Foo
16469+
def self.bar(l, r) = l == r
16470+
end
16471+
def test(flag)
16472+
foo = Foo
16473+
if flag
16474+
foo.bar("a", "b")
16475+
else
16476+
foo.bar("a", :sym)
16477+
end
16478+
end
16479+
test(true)
16480+
test(true)
16481+
"#);
16482+
assert_snapshot!(hir_string("test"), @"
16483+
fn test@<compiled>:7:
16484+
bb1():
16485+
EntryPoint interpreter
16486+
v1:BasicObject = LoadSelf
16487+
v2:CPtr = LoadSP
16488+
v3:BasicObject = LoadField v2, :flag@0x1000
16489+
Jump bb3(v1, v3)
16490+
bb2():
16491+
EntryPoint JIT(0)
16492+
v7:BasicObject = LoadArg :self@0
16493+
v8:BasicObject = LoadArg :flag@1
16494+
Jump bb3(v7, v8)
16495+
bb3(v11:BasicObject, v12:BasicObject):
16496+
v101:NilClass = Const Value(nil)
16497+
PatchPoint StableConstantNames(0x1008, Foo)
16498+
v18:ClassSubclass[Foo@0x1010] = Const Value(VALUE(0x1010))
16499+
PatchPoint NoEPEscape(test)
16500+
v25:CBool = Test v12
16501+
v26:Falsy = RefineType v12, Falsy
16502+
CondBranch v25, bb5(), bb4()
16503+
bb5():
16504+
v28:Truthy = RefineType v12, Truthy
16505+
v32:StringExact[VALUE(0x1018)] = Const Value(VALUE(0x1018))
16506+
v34:StringExact[VALUE(0x1020)] = Const Value(VALUE(0x1020))
16507+
PatchPoint MethodRedefined(Class@0x1028, bar@0x1030, cme:0x1038)
16508+
PushInlineFrame :bar, v18 (0x1060), num_args=2
16509+
PatchPoint NoSingletonClass(String@0x1080)
16510+
PatchPoint MethodRedefined(String@0x1080, ==@0x1088, cme:0x1090)
16511+
v113:FalseClass = Const Value(false)
16512+
CheckInterrupts
16513+
PopInlineFrame
16514+
Return v113
16515+
bb4():
16516+
v48:StringExact[VALUE(0x1018)] = Const Value(VALUE(0x1018))
16517+
v50:StaticSymbol[:sym] = Const Value(VALUE(0x10b8))
16518+
PatchPoint MethodRedefined(Class@0x1028, bar@0x1030, cme:0x1038)
16519+
PushInlineFrame :bar, v18 (0x1060), num_args=2
16520+
PatchPoint NoSingletonClass(String@0x1080)
16521+
PatchPoint MethodRedefined(String@0x1080, ==@0x1088, cme:0x1090)
16522+
v111 = GuardType v50, String
16523+
v112:BoolExact = StringEqual v48, v111
16524+
CheckInterrupts
16525+
PopInlineFrame
16526+
Return v112
16527+
");
16528+
}
16529+
1646416530
#[test]
1646516531
fn opt_neq_string_nil_falls_back_to_basic_object_neq() {
1646616532
eval(r#"

0 commit comments

Comments
 (0)