Skip to content

Commit e34da02

Browse files
authored
Merge pull request #279 from thisisthepy/feat/tp-cls-guard
Fix: impure caller of a class-guarded callee is skipped, not a cgen crash (#275)
2 parents 2ff918f + abd2e56 commit e34da02

2 files changed

Lines changed: 51 additions & 3 deletions

File tree

‎python-multiplatform-ksp/src/main/python/typedpython/verify.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,9 +1009,13 @@ def e_Call(self, e: Call, state: _State) -> ir.Expr:
10091009
else:
10101010
redo = True
10111011
if not redo:
1012-
for k, (a, p) in enumerate(zip(args, callee.params)):
1013-
if type(p) is Param and p.cls is not None and self.exact_of(a) != p.cls:
1014-
self.can_deopt = True # the callee's entry guard `type(x) is C` may fail
1012+
for p in callee.params:
1013+
# The callee's entry guard `type(x) is C` may fail, even for an argument that is a
1014+
# just-allocated `New(C)` local: on free-threaded CPython another thread can find
1015+
# the object through gc.get_objects() and assign `__class__` (SPEC N-12). So
1016+
# exact_of must not exempt the guard; cgen's `_can_deopt` is equally conservative.
1017+
if type(p) is Param and p.cls is not None:
1018+
self.can_deopt = True
10151019
if callee.may_deopt is True and not redo:
10161020
self.can_deopt = True
10171021
if not redo and not self.covers(callee):

‎python-multiplatform-ksp/src/test/python/test_pipeline.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,47 @@ def cb2():
135135
ys[1] = 9.0
136136
mod.fill_then(ys, cb2)
137137
assert ys == [1.0, 9.0, 3.0]
138+
139+
140+
GC_REACHABLE = """\
141+
# typedpython: compiled
142+
143+
144+
class Cell:
145+
a: object
146+
b: object
147+
__slots__ = ("a", "b")
148+
149+
def __init__(self, a: object, b: object) -> None:
150+
self.a = a
151+
self.b = b
152+
153+
154+
def seta(c: Cell, v: object) -> None:
155+
c.a = v
156+
157+
158+
def fresh(a: object, n: int) -> object:
159+
c = Cell(None, None)
160+
seta(c, a)
161+
s: object = None
162+
for i in range(n):
163+
s = c.a
164+
return s
165+
"""
166+
167+
168+
def test_a_fresh_local_does_not_exempt_the_callee_class_guard(tmp_path):
169+
# N-12: on free-threaded CPython another thread can reach even a just-allocated object
170+
# through gc.get_objects() and assign __class__, so the callee's `type(x) is C` entry guard
171+
# can fail. The impure caller must be skipped (verify/deopt-impure), never a CGenError.
172+
path = tmp_path / "gcreach.py"
173+
path.write_text(GC_REACHABLE)
174+
result = pipeline.compile_module(path, tmp_path / "out", cost_rule=False)
175+
assert "fresh" in result.skipped, result.skipped
176+
assert "deopt" in result.skipped["fresh"], result.skipped["fresh"]
177+
assert "seta" in [f.name for f in result.module.functions]
178+
mod = pipeline.load(result)
179+
c = mod.Cell(None, None)
180+
mod.seta(c, 7)
181+
assert c.a == 7

0 commit comments

Comments
 (0)