Skip to content

Commit 45dbf03

Browse files
committed
Optimize calls to final classes
1 parent 13d6738 commit 45dbf03

File tree

5 files changed

+50
-32
lines changed

5 files changed

+50
-32
lines changed

mypyc/codegen/emitclass.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,9 @@ def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
380380
def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
381381
seen_attrs: set[tuple[str, RType]] = set()
382382
lines: list[str] = []
383-
lines += ["typedef struct {", "PyObject_HEAD", "CPyVTableItem *vtable;"]
383+
lines += ["typedef struct {", "PyObject_HEAD"]
384+
if not cl.is_final_class:
385+
lines.append("CPyVTableItem *vtable;")
384386
if cl.has_method("__call__") and emitter.use_vectorcall():
385387
lines.append("vectorcallfunc vectorcall;")
386388
bitmap_attrs = []
@@ -563,14 +565,16 @@ def generate_setup_for_class(
563565
emitter.emit_line("if (self == NULL)")
564566
emitter.emit_line(" return NULL;")
565567

566-
if shadow_vtable_name:
567-
emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
568-
emitter.emit_line(f"self->vtable = {shadow_vtable_name};")
569-
emitter.emit_line("} else {")
570-
emitter.emit_line(f"self->vtable = {vtable_name};")
571-
emitter.emit_line("}")
572-
else:
573-
emitter.emit_line(f"self->vtable = {vtable_name};")
568+
if not cl.is_final_class:
569+
if shadow_vtable_name:
570+
emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
571+
emitter.emit_line(f"self->vtable = {shadow_vtable_name};")
572+
emitter.emit_line("} else {")
573+
emitter.emit_line(f"self->vtable = {vtable_name};")
574+
emitter.emit_line("}")
575+
else:
576+
emitter.emit_line(f"self->vtable = {vtable_name};")
577+
574578
for i in range(0, len(cl.bitmap_attrs), BITMAP_BITS):
575579
field = emitter.bitmap_field(i)
576580
emitter.emit_line(f"self->{field} = 0;")

mypyc/codegen/emitfunc.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -361,21 +361,24 @@ def visit_get_attr(self, op: GetAttr) -> None:
361361
attr_rtype, decl_cl = cl.attr_details(op.attr)
362362
prefer_method = cl.is_trait and attr_rtype.error_overlap
363363
if cl.get_method(op.attr, prefer_method=prefer_method):
364-
# Properties are essentially methods, so use vtable access for them.
365-
version = "_TRAIT" if cl.is_trait else ""
366-
self.emit_line(
367-
"%s = CPY_GET_ATTR%s(%s, %s, %d, %s, %s); /* %s */"
368-
% (
369-
dest,
370-
version,
371-
obj,
372-
self.emitter.type_struct_name(rtype.class_ir),
373-
rtype.getter_index(op.attr),
374-
rtype.struct_name(self.names),
375-
self.ctype(rtype.attr_type(op.attr)),
376-
op.attr,
364+
if cl.is_method_final(op.attr):
365+
self.emit_method_call(f"{dest} = ", op.obj, op.attr, [])
366+
else:
367+
# Properties are essentially methods, so use vtable access for them.
368+
version = "_TRAIT" if cl.is_trait else ""
369+
self.emit_line(
370+
"%s = CPY_GET_ATTR%s(%s, %s, %d, %s, %s); /* %s */"
371+
% (
372+
dest,
373+
version,
374+
obj,
375+
self.emitter.type_struct_name(rtype.class_ir),
376+
rtype.getter_index(op.attr),
377+
rtype.struct_name(self.names),
378+
self.ctype(rtype.attr_type(op.attr)),
379+
op.attr,
380+
)
377381
)
378-
)
379382
else:
380383
# Otherwise, use direct or offset struct access.
381384
attr_expr = self.get_attr_expr(obj, op, decl_cl)
@@ -529,11 +532,12 @@ def visit_call(self, op: Call) -> None:
529532
def visit_method_call(self, op: MethodCall) -> None:
530533
"""Call native method."""
531534
dest = self.get_dest_assign(op)
532-
obj = self.reg(op.obj)
535+
self.emit_method_call(dest, op.obj, op.method, op.args)
533536

534-
rtype = op.receiver_type
537+
def emit_method_call(self, dest: str, op_obj: Value, name: str, op_args: list[Value]) -> None:
538+
obj = self.reg(op_obj)
539+
rtype = op_obj.type
535540
class_ir = rtype.class_ir
536-
name = op.method
537541
method = rtype.class_ir.get_method(name)
538542
assert method is not None
539543

@@ -547,7 +551,7 @@ def visit_method_call(self, op: MethodCall) -> None:
547551
if method.decl.kind == FUNC_STATICMETHOD
548552
else [f"(PyObject *)Py_TYPE({obj})"] if method.decl.kind == FUNC_CLASSMETHOD else [obj]
549553
)
550-
args = ", ".join(obj_args + [self.reg(arg) for arg in op.args])
554+
args = ", ".join(obj_args + [self.reg(arg) for arg in op_args])
551555
mtype = native_function_type(method, self.emitter)
552556
version = "_TRAIT" if rtype.class_ir.is_trait else ""
553557
if is_direct:
@@ -567,7 +571,7 @@ def visit_method_call(self, op: MethodCall) -> None:
567571
rtype.struct_name(self.names),
568572
mtype,
569573
args,
570-
op.method,
574+
name,
571575
)
572576
)
573577

mypyc/ir/class_ir.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ def __init__(
9393
is_generated: bool = False,
9494
is_abstract: bool = False,
9595
is_ext_class: bool = True,
96+
is_final_class: bool = False,
9697
) -> None:
9798
self.name = name
9899
self.module_name = module_name
99100
self.is_trait = is_trait
100101
self.is_generated = is_generated
101102
self.is_abstract = is_abstract
102103
self.is_ext_class = is_ext_class
104+
self.is_final_class = is_final_class
103105
# An augmented class has additional methods separate from what mypyc generates.
104106
# Right now the only one is dataclasses.
105107
self.is_augmented = False
@@ -248,8 +250,7 @@ def has_method(self, name: str) -> bool:
248250
def is_method_final(self, name: str) -> bool:
249251
subs = self.subclasses()
250252
if subs is None:
251-
# TODO: Look at the final attribute!
252-
return False
253+
return self.is_final_class
253254

254255
if self.has_method(name):
255256
method_decl = self.method_decl(name)

mypyc/irbuild/classdef.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
handle_non_ext_method,
6464
load_type,
6565
)
66-
from mypyc.irbuild.util import dataclass_type, get_func_def, is_constant, is_dataclass_decorator
66+
from mypyc.irbuild.util import dataclass_type, get_func_def, is_constant, is_dataclass_decorator, is_final_class
6767
from mypyc.primitives.dict_ops import dict_new_op, dict_set_item_op
6868
from mypyc.primitives.generic_ops import (
6969
iter_op,
@@ -294,6 +294,7 @@ def add_attr(self, lvalue: NameExpr, stmt: AssignmentStmt) -> None:
294294
self.builder.init_final_static(lvalue, value, self.cdef.name)
295295

296296
def finalize(self, ir: ClassIR) -> None:
297+
ir.is_final_class = is_final_class(self.cdef)
297298
attrs_with_defaults, default_assignments = find_attr_initializers(
298299
self.builder, self.cdef, self.skip_attr_default
299300
)

mypyc/irbuild/util.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@
3131
DATACLASS_DECORATORS = {"dataclasses.dataclass", "attr.s", "attr.attrs"}
3232

3333

34+
def is_final_decorator(d: Expression) -> bool:
35+
return isinstance(d, RefExpr) and d.fullname == "typing.final"
36+
37+
38+
def is_final_class(cdef: ClassDef) -> bool:
39+
return any(is_final_decorator(d) for d in cdef.decorators)
40+
41+
3442
def is_trait_decorator(d: Expression) -> bool:
3543
return isinstance(d, RefExpr) and d.fullname == "mypy_extensions.trait"
3644

@@ -119,7 +127,7 @@ def get_mypyc_attrs(stmt: ClassDef | Decorator) -> dict[str, Any]:
119127

120128
def is_extension_class(cdef: ClassDef) -> bool:
121129
if any(
122-
not is_trait_decorator(d) and not is_dataclass_decorator(d) and not get_mypyc_attr_call(d)
130+
not is_trait_decorator(d) and not is_dataclass_decorator(d) and not get_mypyc_attr_call(d) and not is_final_decorator(d)
123131
for d in cdef.decorators
124132
):
125133
return False

0 commit comments

Comments
 (0)