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
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
* A `Call` of a compiled callee whose parameter has cls=C (the callee's entry guard) with an
argument not proved exact-C by the forms above can fail that guard in the middle of the caller:
it counts toward `may_deopt` (so it is legal only in a pure function) unless it is a legal redo.
* Purity/deopt: CheckExact can deopt (like Unbox to i64): only in a pure function, and it counts
* Purity/deopt: CheckExact can deopt (like every Unbox): only in a pure function, and it counts
toward `may_deopt` even when redundant. FieldSet is an effect (and makes the function not
*closed*: replacing a slot may drop the last reference to the old value, whose `__del__` is user
code). FieldGet, Is, IsExact, New are not effects.
Expand Down Expand Up @@ -972,8 +972,8 @@ def e_Unbox(self, e: Unbox, state: _State) -> ir.Expr:
self.error(TYPE, f"Unbox of {getattr(ot, 'value', ot)}")
if e.type not in ir.SCALARS:
self.error(TYPE, f"Unbox gives a scalar, node says {e.type.value}")
elif e.type is Type.I64:
self.can_deopt = True
else:
self.can_deopt = True # every Unbox checks its operand's exact type (#271)
return dataclasses.replace(e, operand=operand)

def e_MathCall(self, e: MathCall, state: _State) -> ir.Expr:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,4 @@ def handler(signum, frame):
print(json.dumps({"out": out, "full": full, "elapsed": time.perf_counter() - t0}))
""")
assert r["out"] == "interrupted", r
assert r["elapsed"] < r["full"] * 0.5, r
assert r["elapsed"] < r["full"] * 0.8, r # before #280 the signal waited for the whole loop
7 changes: 4 additions & 3 deletions python-multiplatform-ksp/src/test/python/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ def test_i64_add_in_an_impure_function_is_rejected():
BinOp(F64, BinOpKind.TRUEDIV, Local(I64, "n"), Const(I64, 2)),
UnaryOp(I64, UnaryOpKind.NEG, Local(I64, "n")),
Unbox(I64, Box(OBJ, Local(I64, "n"))),
Unbox(F64, Box(OBJ, Local(I64, "n"))), # #271: every Unbox can deopt, as cgen says
Unbox(BOOL, Box(OBJ, Local(I64, "n"))),
])
def test_every_deopting_node_is_rejected_in_an_impure_function(node):
assert_rejected(DEOPT, fn(params=[Param("n", I64)], body=[ExprStmt(node), Return()]))
Expand All @@ -465,7 +467,6 @@ def test_non_deopting_nodes_are_fine_in_an_impure_function():
body=[
ExprStmt(pycall("print", Box(OBJ, lf("x")))),
ExprStmt(UnaryOp(I64, UnaryOpKind.POS, li("n"))),
ExprStmt(Unbox(F64, Box(OBJ, lf("x")))),
ExprStmt(ObjToFloat(F64, GetAttr(OBJ, lo("o"), "x"))),
ExprStmt(BinOp(OBJ, BinOpKind.MUL, lo("o"), lo("o"))),
Return(BinOp(F64, BinOpKind.FLOORDIV, lf("x"), f(2.0))),
Expand Down Expand Up @@ -627,7 +628,7 @@ def test_box_callobject_unbox_pipeline_is_accepted():
assert_accepted(fn(params=[Param("x", F64), Param("o", OBJ)], locals_={"r": OBJ}, returns=F64,
body=[Assign("r", pycall("f2", Box(OBJ, lf("x")), lo("o"))),
Assign("r", BinOp(OBJ, BinOpKind.ADD, lo("r"), lo("o"))),
Return(Unbox(F64, lo("r")))]))
Return(ObjToFloat(F64, lo("r")))])) # impure: ObjToFloat, not Unbox (#271)


def test_obj_into_a_scalar_slot_without_unbox_is_rejected():
Expand Down Expand Up @@ -1089,7 +1090,7 @@ def _base_functions():
Assign("a", na(F64A, i(4), f(1.0))),
Assign("r", call_obj(GetAttr(OBJ, g_("Modifier"), "padding"),
Box(OBJ, lf("x")), lo("o"), kw=("dp",))),
Assign("y", Unbox(F64, lo("r"))),
Assign("y", ObjToFloat(F64, lo("r"))),
If(Truth(BOOL, lo("r")), (Assign("y", ObjToFloat(F64, lo("o"))),)),
ForRange("k", i(0), Len(I64, "a"), i(1), (
StoreIndex("a", li("k"), BinOp(F64, BinOpKind.MUL,
Expand Down
23 changes: 10 additions & 13 deletions python-multiplatform-ksp/src/test/python/test_verify_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,30 +429,27 @@ def test_a_caller_of_a_check_exact_function_inherits_its_deopt():
callee)


def test_a_call_whose_callee_guards_a_class_param_may_deopt_unless_the_argument_is_proved():
def test_a_call_whose_callee_guards_a_class_param_may_deopt_even_for_a_proved_argument():
"""#275: the callee's guard `type(x) is C` runs again at the call, and on free-threaded 3.15t
another thread can reach even a fresh or already-guarded object (gc.get_objects()) and assign
`__class__`, so no argument proof exempts it; this matches cgen's `_can_deopt`."""
callee = fn("g", params=[pt()], body=[Return(fget(lo("p"), "x"))])
unproved = fn("f", params=[obj()], may_deopt=True, body=[Return(Call(OBJ, "g", (lo("q"),)))])
accepted(unproved, callee)
rejected(FLAG_DEOPT, dataclasses.replace(unproved, may_deopt=False), callee)
rejected(DEOPT, fn("f", params=[obj()], pure=False, body=[IMPURE,
Return(Call(OBJ, "g", (lo("q"),)))]),
callee)
# proved arguments cannot fail the callee's guard: no deopt
for arg, params in ((lo("p"), [pt()]), (new(g_("a"), g_("b")), [])):
accepted(fn("f", params=params, may_deopt=False, body=[Return(Call(OBJ, "g", (arg,)))]),
accepted(fn("f", params=params, may_deopt=True, body=[Return(Call(OBJ, "g", (arg,)))]),
callee)
accepted(fn("f", params=[obj()], may_deopt=True,
body=[Return(Call(OBJ, "g", (check(lo("q")),)))]), callee) # the CheckExact deopts
accepted(fn("f", params=[pt()], pure=False, body=[IMPURE, Return(Call(OBJ, "g", (lo("p"),)))]),
rejected(FLAG_DEOPT, fn("f", params=params, may_deopt=False,
body=[Return(Call(OBJ, "g", (arg,)))]), callee)
rejected(DEOPT, fn("f", params=[pt()], pure=False, body=[IMPURE, Return(Call(OBJ, "g", (lo("p"),)))]),
callee)
# the wrong class proves nothing for this callee
rejected(FLAG_DEOPT, fn("f", params=[pt("p", cls="Other")], may_deopt=False,
body=[Return(Call(OBJ, "g", (lo("p"),)))]), callee)
# an optional class param is satisfied by a proved exact argument too, not by an unproved one
opt = fn("g", params=[pt(optional=True)], body=[Return(g_("a"))])
accepted(fn("f", params=[pt()], may_deopt=False, body=[Return(Call(OBJ, "g", (lo("p"),)))]), opt)
rejected(FLAG_DEOPT, fn("f", params=[obj()], may_deopt=False,
body=[Return(Call(OBJ, "g", (lo("q"),)))]), opt)
rejected(FLAG_DEOPT, fn("f", params=[pt()], may_deopt=False, body=[Return(Call(OBJ, "g", (lo("p"),)))]),
opt)


def test_field_set_is_an_effect():
Expand Down