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
16 changes: 16 additions & 0 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,22 @@ def is_native_attr_ref(self, expr: MemberExpr) -> bool:
and any(expr.name in ir.attributes for ir in obj_rtype.class_ir.mro)
)

def is_final_native_attr_ref(self, expr: MemberExpr) -> bool:
"""Is expr a direct reference to a Final native (struct) attribute of an instance?

A Final attribute is read-only at runtime (it has no setter), so it can never be
reassigned after construction. This makes it safe to borrow even on free-threaded
builds, since no concurrent store can invalidate the borrowed reference.
"""
obj_rtype = self.node_type(expr.expr)
if not (isinstance(obj_rtype, RInstance) and obj_rtype.class_ir.is_ext_class):
return False
# Find the class that defines the attribute and check whether it's Final there.
for ir in obj_rtype.class_ir.mro:
if expr.name in ir.attributes:
return expr.name in ir.final_attributes
return False

def mark_block_unreachable(self) -> None:
"""Mark statements in the innermost block being processed as unreachable.

Expand Down
11 changes: 9 additions & 2 deletions mypyc/irbuild/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,16 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports:
return builder.load_module(expr.node.fullname)

can_borrow = builder.is_native_attr_ref(expr)
obj = builder.accept(expr.expr, can_borrow=can_borrow)
rtype = builder.node_type(expr)
# Borrowing a native attribute read is unsafe on free-threaded builds, since another
# thread could concurrently reassign the attribute and free the old value. 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.
can_borrow = builder.is_native_attr_ref(expr) and (
not IS_FREE_THREADED or isinstance(rtype, RVec) or builder.is_final_native_attr_ref(expr)
)
obj = builder.accept(expr.expr, can_borrow=can_borrow)

if (
is_object_rprimitive(obj.type)
Expand Down
28 changes: 27 additions & 1 deletion mypyc/test-data/exceptions.test
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ L3:
r3 = <error> :: i64
return r3

[case testExceptionWithNativeAttributeGetAndSet]
[case testExceptionWithNativeAttributeGetAndSet_withgil]
class C:
def __init__(self, x: int) -> None:
self.x = x
Expand All @@ -578,6 +578,32 @@ L0:
c.x = r1
return 1

[case testExceptionWithNativeAttributeGetAndSet_nogil]
class C:
def __init__(self, x: int) -> None:
self.x = x

def foo(c: C, x: int) -> None:
c.x = x - c.x
[out]
def C.__init__(self, x):
self :: __main__.C
x :: int
L0:
inc_ref x :: int
self.x = x
return 1
def foo(c, x):
c :: __main__.C
x, r0, r1 :: int
r2 :: bool
L0:
r0 = c.x
r1 = CPyTagged_Subtract(x, r0)
dec_ref r0 :: int
c.x = r1
return 1

[case testExceptionWithOverlappingFloatErrorValue]
def f() -> float:
return 0.0
Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -2063,7 +2063,7 @@ L7:
L8:
return r10

[case testProperty]
[case testProperty_withgil]
class PropertyHolder:
@property
def value(self) -> int:
Expand Down
24 changes: 21 additions & 3 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ L0:
a.x = 2; r0 = is_error
return 1

[case testUserClassInList]
[case testUserClassInList_withgil]
class C:
x: int

Expand Down Expand Up @@ -103,7 +103,7 @@ L0:
r0 = a.n
return r0

[case testOptionalMember]
[case testOptionalMember_withgil]
from typing import Optional
class Node:
next: Optional[Node]
Expand Down Expand Up @@ -1239,7 +1239,7 @@ L0:
__mypyc_self__.s = r0
return 1

[case testBorrowAttribute]
[case testBorrowAttribute_withgil]
def f(d: D) -> int:
return d.c.x

Expand All @@ -1258,6 +1258,24 @@ L0:
keep_alive d
return r1

[case testCannotBorrowAttribute_nogil]
def f(d: D) -> int:
return d.c.x

class C:
x: int
class D:
c: C
[out]
def f(d):
d :: __main__.D
r0 :: __main__.C
r1 :: int
L0:
r0 = d.c
r1 = r0.x
return r1

[case testNoBorrowOverPropertyAccess]
class C:
d: D
Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ L0:
y = r3
return 1

[case testGenericAttrAndTypeApplication]
[case testGenericAttrAndTypeApplication_withgil]
from typing import TypeVar, Generic
T = TypeVar('T')
class C(Generic[T]):
Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-glue-methods.test
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ L0:
r0 = x.foo(y)
return r0

[case testPropertyDerivedGen]
[case testPropertyDerivedGen_withgil]
from typing import Callable
class BaseProperty:
@property
Expand Down
6 changes: 3 additions & 3 deletions mypyc/test-data/irbuild-i64.test
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ L4:
y = r3
return y

[case testBorrowOverI64Arithmetic]
[case testBorrowOverI64Arithmetic_withgil]
from mypy_extensions import i64

def add_simple(c: C) -> i64:
Expand Down Expand Up @@ -1084,7 +1084,7 @@ L0:
keep_alive d, d
return r4

[case testBorrowOverI64Bitwise]
[case testBorrowOverI64Bitwise_withgil]
from mypy_extensions import i64

def bitwise_simple(c: C) -> i64:
Expand Down Expand Up @@ -2070,7 +2070,7 @@ L4:
r6 = CPyFloat_FromTagged(r3)
return r6

[case testI64IsinstanceNarrowing]
[case testI64IsinstanceNarrowing_withgil]
from typing import Union
from mypy_extensions import i64

Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ L2:
L3:
return r1

[case testBorrowSpecialCaseWithIsinstance]
[case testBorrowSpecialCaseWithIsinstance_withgil]
class C:
s: str

Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/irbuild-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ L3:
L4:
return 1

[case testUnionType]
[case testUnionType_withgil]
from typing import Union

class A:
Expand Down
12 changes: 11 additions & 1 deletion mypyc/test-data/irbuild-vec-t.test
Original file line number Diff line number Diff line change
Expand Up @@ -512,15 +512,25 @@ L0:
return r2

[case testVecTBorrowGetItem_64bit]
from typing import Final

from librt.vecs import vec
from mypy_extensions import i64

class A:
x: str
def __init__(self, x: str) -> None:
# Final to allow borrowing on free-threaded builds
self.x: Final = x

def f(v: vec[A], n: i64) -> int:
return len(v[n].x)
[out]
def A.__init__(self, x):
self :: __main__.A
x :: str
L0:
self.x = x
return 1
def f(v, n):
v :: vec[__main__.A]
n :: i64
Expand Down
2 changes: 1 addition & 1 deletion mypyc/test-data/opt-copy-propagation.test
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ L1:
L2:
return 2

[case testIRTransformRegisterOps1]
[case testIRTransformRegisterOps1_withgil]
from __future__ import annotations
from typing import cast

Expand Down
Loading
Loading