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
10 changes: 7 additions & 3 deletions python-multiplatform-ksp/src/main/python/typedpython/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,13 @@ def e_Call(self, e: Call, state: _State) -> ir.Expr:
else:
redo = True
if not redo:
for k, (a, p) in enumerate(zip(args, callee.params)):
if type(p) is Param and p.cls is not None and self.exact_of(a) != p.cls:
self.can_deopt = True # the callee's entry guard `type(x) is C` may fail
for p in callee.params:
# The callee's entry guard `type(x) is C` may fail, even for an argument that is a
# just-allocated `New(C)` local: on free-threaded CPython another thread can find
# the object through gc.get_objects() and assign `__class__` (SPEC N-12). So
# exact_of must not exempt the guard; cgen's `_can_deopt` is equally conservative.
if type(p) is Param and p.cls is not None:
self.can_deopt = True
if callee.may_deopt is True and not redo:
self.can_deopt = True
if not redo and not self.covers(callee):
Expand Down
44 changes: 44 additions & 0 deletions python-multiplatform-ksp/src/test/python/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,47 @@ def cb2():
ys[1] = 9.0
mod.fill_then(ys, cb2)
assert ys == [1.0, 9.0, 3.0]


GC_REACHABLE = """\
# typedpython: compiled


class Cell:
a: object
b: object
__slots__ = ("a", "b")

def __init__(self, a: object, b: object) -> None:
self.a = a
self.b = b


def seta(c: Cell, v: object) -> None:
c.a = v


def fresh(a: object, n: int) -> object:
c = Cell(None, None)
seta(c, a)
s: object = None
for i in range(n):
s = c.a
return s
"""


def test_a_fresh_local_does_not_exempt_the_callee_class_guard(tmp_path):
# N-12: on free-threaded CPython another thread can reach even a just-allocated object
# through gc.get_objects() and assign __class__, so the callee's `type(x) is C` entry guard
# can fail. The impure caller must be skipped (verify/deopt-impure), never a CGenError.
path = tmp_path / "gcreach.py"
path.write_text(GC_REACHABLE)
result = pipeline.compile_module(path, tmp_path / "out", cost_rule=False)
assert "fresh" in result.skipped, result.skipped
assert "deopt" in result.skipped["fresh"], result.skipped["fresh"]
assert "seta" in [f.name for f in result.module.functions]
mod = pipeline.load(result)
c = mod.Cell(None, None)
mod.seta(c, 7)
assert c.a == 7