Skip to content
Open
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
21 changes: 21 additions & 0 deletions cinderx/Jit/hir/guard_removal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ bool guardNeeded(const RegUses& uses, Register* new_reg, Type relaxed_type) {
worklist.emplace(passthrough_output, passthrough_type);
}
}
Register* output = instr->output();
if (output != nullptr && output->isA(TBottom)) {
// This instruction can only fail, and the guard's narrower
// operand type is what proves it. The code after it has already
// been replaced with a trap, so widening the type would let the
// instruction succeed and fall into that trap.
Type relaxed_output =
outputType(*instr, [&](std::size_t operand_index) {
if (operand_index == i) {
return relaxed_type;
}
return instr->getOperand(operand_index)->type();
});
if (!(relaxed_output <= TBottom)) {
TRACE(
"'{}' kept alive by unreachable '{}'",
*new_reg->instr(),
*instr);
return true;
}
}
OperandType expected_type = instr->getOperandType(i);
// TASK(T106726658): We should be able to remove GuardTypes if we ever
// add a matching constraint for non-Primitive types, and our
Expand Down
31 changes: 31 additions & 0 deletions cinderx/PythonLib/test_cinderx/test_jit_specialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,37 @@ def f(a: object) -> str:
self.assertIn("TO_BOOL_NONE", opnames(f))
self.assertEqual(f(None), "n")

@passUnless(sys.version_info >= (3, 14), "TO_BOOL was added in Python 3.13")
def test_to_bool_list_then_none(self) -> None:
# Both TO_BOOL sites read `defaults`, but only calls with a non-empty
# `args` reach the second one, so they specialize on different types.
# The JIT ends up with contradictory guards, proves the code after the
# second one unreachable and traps there. The list guard is the only
# thing left that can send this call back to the interpreter.
def f(defaults: object, args: list[str]) -> list[str]:
first = 0 if defaults else -1
out: list[str] = []
for i, arg in enumerate(args):
if defaults and i >= first:
out.append("d")
out.append(arg)
return out

def warm() -> None:
# An empty `args` keeps the loop body cold, so only the first
# TO_BOOL specializes here.
for _ in range(20):
f([1], [])
f(None, ["a"])

specialize(f, warm)

ops = opnames(f)
self.assertIn("TO_BOOL_LIST", ops)
self.assertIn("TO_BOOL_NONE", ops)
self.assertEqual(f(None, ["a", "b"]), ["a", "b"])
self.assertEqual(f([1], ["a", "b"]), ["d", "a", "d", "b"])

@passUnless(sys.version_info >= (3, 14), "TO_BOOL was added in Python 3.13")
def test_to_bool_str(self) -> None:
def f(a: str) -> str:
Expand Down
Loading