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
14 changes: 12 additions & 2 deletions mypyc/irbuild/ast_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
UnaryExpr,
Var,
)
from mypyc.common import IS_FREE_THREADED
from mypyc.ir.ops import BasicBlock
from mypyc.ir.rtypes import is_fixed_width_rtype, is_tagged
from mypyc.ir.rtypes import RVec, is_fixed_width_rtype, is_tagged
from mypyc.irbuild.builder import IRBuilder
from mypyc.irbuild.constant_fold import constant_fold_expr

Expand Down Expand Up @@ -119,5 +120,14 @@ def is_borrow_friendly_expr(self: IRBuilder, expr: Expression) -> bool:
# Local variable reference can be borrowed
return True
if isinstance(expr, MemberExpr) and self.is_native_attr_ref(expr):
return True
# Borrowing a native attribute read is unsafe on free-threaded builds, since another
# thread could concurrently reassign the attribute and free the old value (this is the
# same rule transform_member_expr applies). We still borrow in two cases:
# - Native Final attributes are read-only at runtime, so they can never be reassigned.
# - Vec-typed attributes require manual synchronization, so we borrow them liberally.
return (
not IS_FREE_THREADED
or isinstance(self.node_type(expr), RVec)
or self.is_final_native_attr_ref(expr)
)
return False
30 changes: 29 additions & 1 deletion mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ L0:
self.y = 60
return 1

[case testAttrLvalue]
[case testAttrLvalue_withgil]
class O(object):
def __init__(self) -> None:
self.x = 1
Expand All @@ -180,6 +180,34 @@ L0:
o.x = r1; r2 = is_error
return o

[case testAttrLvalue_nogil]
# On free-threaded builds an operator assignment such as 'o.x += 1' must not borrow
# the native attribute read: a concurrent store could free the old value between the
# borrowed load and its use. Contrast testAttrLvalue_withgil, where the read is
# borrowed. is_borrow_friendly_expr applies the same rule as transform_member_expr.
class O(object):
def __init__(self) -> None:
self.x = 1

def increment(o: O) -> O:
o.x += 1
return o
[out]
def O.__init__(self):
self :: __main__.O
L0:
self.x = 2
return 1
def increment(o):
o :: __main__.O
r0, r1 :: int
r2 :: bool
L0:
r0 = o.x
r1 = CPyTagged_Add(r0, 2)
o.x = r1; r2 = is_error
return o

[case testSubclass_withgil_toplevel]
from typing import TypeVar, Generic
from mypy_extensions import trait
Expand Down
Loading