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
8 changes: 8 additions & 0 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ def new_tuple(self, items: list[Value], line: int) -> Value:
def debug_print(self, toprint: str | Value) -> None:
return self.builder.debug_print(toprint)

def set_immortal_if_free_threaded(self, v: Value, line: int) -> None:
"""Make an object immortal on free-threaded builds (to avoid contention)."""
self.builder.set_immortal_if_free_threaded(v, line)

# Helpers for IR building

def add_to_non_ext_dict(
Expand All @@ -433,6 +437,10 @@ def add_to_non_ext_dict(
key_unicode = self.load_str(key)
self.primitive_op(dict_set_item_op, [non_ext.dict, key_unicode, val], line)

# It's important that accessing class dictionary items from multiple threads
# doesn't cause contention.
self.builder.set_immortal_if_free_threaded(val, line)

def gen_import(self, id: str, line: int) -> None:
self.imports[id] = None

Expand Down
8 changes: 8 additions & 0 deletions mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ def finalize(self, ir: ClassIR) -> None:
non_ext_class = load_non_ext_class(self.builder, ir, self.non_ext, self.cdef.line)
non_ext_class = load_decorated_class(self.builder, self.cdef, non_ext_class)

# Try to avoid contention when using free threading.
self.builder.set_immortal_if_free_threaded(non_ext_class, self.cdef.line)

# Save the decorated class
self.builder.add(
InitStatic(non_ext_class, self.cdef.name, self.builder.module_name, NAMESPACE_TYPE)
Expand Down Expand Up @@ -449,6 +452,11 @@ def allocate_class(builder: IRBuilder, cdef: ClassDef) -> Value:
)
# Create the class
tp = builder.call_c(pytype_from_template_op, [template, tp_bases, modname], cdef.line)

# Set type object to be immortal if free threaded, as otherwise reference count contention
# can cause a big performance hit.
builder.set_immortal_if_free_threaded(tp, cdef.line)

# Immediately fix up the trait vtables, before doing anything with the class.
ir = builder.mapper.type_to_ir[cdef.info]
if not ir.is_trait and not ir.builtin_base:
Expand Down
8 changes: 8 additions & 0 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import sys
from collections.abc import Sequence
from typing import Callable, Final, Optional

Expand All @@ -16,6 +17,7 @@
from mypyc.common import (
BITMAP_BITS,
FAST_ISINSTANCE_MAX_SUBCLASSES,
IS_FREE_THREADED,
MAX_LITERAL_SHORT_INT,
MAX_SHORT_INT,
MIN_LITERAL_SHORT_INT,
Expand Down Expand Up @@ -164,6 +166,7 @@
fast_isinstance_op,
none_object_op,
not_implemented_op,
set_immortal_op,
var_object_size,
)
from mypyc.primitives.registry import (
Expand Down Expand Up @@ -2322,6 +2325,11 @@ def new_tuple_with_length(self, length: Value, line: int) -> Value:
def int_to_float(self, n: Value, line: int) -> Value:
return self.primitive_op(int_to_float_op, [n], line)

def set_immortal_if_free_threaded(self, v: Value, line: int) -> None:
"""Make an object immortal on free-threaded builds (to avoid contention)."""
if IS_FREE_THREADED and sys.version_info >= (3, 14):
self.primitive_op(set_immortal_op, [v], line)

# Internal helpers

def decompose_union_helper(
Expand Down
4 changes: 4 additions & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,10 @@ PyObject *CPy_GetANext(PyObject *aiter);
void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_value);
void CPyTrace_LogEvent(const char *location, const char *line, const char *op, const char *details);

#if CPY_3_14_FEATURES
void CPy_SetImmortal(PyObject *obj);
#endif

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 11 additions & 1 deletion mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,7 @@ void CPyTrace_LogEvent(const char *location, const char *line, const char *op, c

#endif

#ifdef CPY_3_12_FEATURES
#if CPY_3_12_FEATURES

// Copied from Python 3.12.3, since this struct is internal to CPython. It defines
// the structure of typing.TypeAliasType objects. We need it since compute_value is
Expand Down Expand Up @@ -1088,3 +1088,13 @@ void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_valu
}

#endif

#if CPY_3_14_FEATURES

#include "internal/pycore_object.h"

void CPy_SetImmortal(PyObject *obj) {
_Py_SetImmortal(obj);
}

#endif
3 changes: 2 additions & 1 deletion mypyc/lib-rt/mypyc_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) {
return x << 1;
}

// Are we targeting Python 3.12 or newer?
// Are we targeting Python 3.X or newer?
#define CPY_3_12_FEATURES (PY_VERSION_HEX >= 0x030c0000)
#define CPY_3_14_FEATURES (PY_VERSION_HEX >= 0x030e0000)

#if CPY_3_12_FEATURES

Expand Down
15 changes: 15 additions & 0 deletions mypyc/primitives/misc_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,18 @@
return_type=void_rtype,
error_kind=ERR_NEVER,
)

# Mark object as immortal -- it won't be freed via reference counting, as
# the reference count won't be updated any longer. Immortal objects support
# fast concurrent read-only access from multiple threads when using free
# threading, since this eliminates contention from concurrent reference count
# updates.
#
# Needs at least Python 3.14.
set_immortal_op = custom_primitive_op(
name="set_immmortal",
c_function_name="CPy_SetImmortal",
arg_types=[object_rprimitive],
return_type=void_rtype,
error_kind=ERR_NEVER,
)