Skip to content

Commit a0ae332

Browse files
committed
Fix vtable pointer with inherited dunder new
1 parent 1f09855 commit a0ae332

File tree

5 files changed

+113
-43
lines changed

5 files changed

+113
-43
lines changed

mypyc/codegen/emitclass.py

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
229229
name = cl.name
230230
name_prefix = cl.name_prefix(emitter.names)
231231

232+
allocate_name = emitter.native_function_name(cl.allocate)
232233
setup_name = emitter.native_function_name(cl.setup)
233234
new_name = f"{name_prefix}_new"
234235
finalize_name = f"{name_prefix}_finalize"
@@ -322,14 +323,15 @@ def emit_line() -> None:
322323
fields["tp_basicsize"] = base_size
323324

324325
if generate_full:
325-
assert cl.setup is not None
326-
emitter.emit_line(native_function_header(cl.setup, emitter) + ";")
327-
assert cl.ctor is not None
328-
emitter.emit_line(native_function_header(cl.ctor, emitter) + ";")
326+
for decl in [cl.allocate, cl.setup, cl.ctor]:
327+
assert decl is not None
328+
emitter.emit_line(native_function_header(decl, emitter) + ";")
329329

330330
emit_line()
331331
init_fn = cl.get_method("__init__")
332-
generate_new_for_class(cl, new_name, vtable_name, setup_name, init_fn, emitter)
332+
generate_new_for_class(
333+
cl, new_name, vtable_name, allocate_name, setup_name, init_fn, emitter
334+
)
333335
emit_line()
334336
generate_traverse_for_class(cl, traverse_name, emitter)
335337
emit_line()
@@ -393,9 +395,13 @@ def emit_line() -> None:
393395

394396
emitter.emit_line()
395397
if generate_full:
396-
generate_setup_for_class(cl, defaults_fn, vtable_name, shadow_vtable_name, emitter)
398+
generate_allocate_for_class(cl, defaults_fn, emitter)
399+
emitter.emit_line()
400+
generate_setup_for_class(cl, vtable_name, shadow_vtable_name, emitter)
397401
emitter.emit_line()
398-
generate_constructor_for_class(cl, cl.ctor, init_fn, setup_name, vtable_name, emitter)
402+
generate_constructor_for_class(
403+
cl, cl.ctor, init_fn, allocate_name, setup_name, vtable_name, emitter
404+
)
399405
emitter.emit_line()
400406
if needs_getseters:
401407
generate_getseters(cl, emitter)
@@ -580,17 +586,11 @@ def generate_vtable(
580586
emitter.emit_line("memcpy({name}, {name}_scratch, sizeof({name}));".format(name=vtable_name))
581587

582588

583-
def generate_setup_for_class(
584-
cl: ClassIR,
585-
defaults_fn: FuncIR | None,
586-
vtable_name: str,
587-
shadow_vtable_name: str | None,
588-
emitter: Emitter,
589-
) -> None:
589+
def generate_allocate_for_class(cl: ClassIR, defaults_fn: FuncIR | None, emitter: Emitter) -> None:
590590
"""Generate a native function that allocates an instance of a class."""
591-
emitter.emit_line(native_function_header(cl.setup, emitter))
591+
emitter.emit_line(native_function_header(cl.allocate, emitter))
592592
emitter.emit_line("{")
593-
type_arg_name = REG_PREFIX + cl.setup.sig.args[0].name
593+
type_arg_name = REG_PREFIX + cl.allocate.sig.args[0].name
594594
emitter.emit_line(f"PyTypeObject *type = (PyTypeObject*){type_arg_name};")
595595
struct_name = cl.struct_name(emitter.names)
596596
emitter.emit_line(f"{struct_name} *self;")
@@ -609,24 +609,8 @@ def generate_setup_for_class(
609609
emitter.emit_line("}")
610610

611611
emitter.emit_line(f"self = ({cl.struct_name(emitter.names)} *)type->tp_alloc(type, 0);")
612-
emitter.emit_line("if (self == NULL)")
613-
emitter.emit_line(" return NULL;")
614-
615-
if shadow_vtable_name:
616-
emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
617-
emitter.emit_line(f"self->vtable = {shadow_vtable_name};")
618-
emitter.emit_line("} else {")
619-
emitter.emit_line(f"self->vtable = {vtable_name};")
620-
emitter.emit_line("}")
621-
else:
622-
emitter.emit_line(f"self->vtable = {vtable_name};")
623-
624612
emit_clear_bitmaps(cl, emitter)
625613

626-
if cl.has_method("__call__"):
627-
name = cl.method_decl("__call__").cname(emitter.names)
628-
emitter.emit_line(f"self->vectorcall = {PREFIX}{name};")
629-
630614
for base in reversed(cl.base_mro):
631615
for attr, rtype in base.attributes.items():
632616
value = emitter.c_undefined_value(rtype)
@@ -644,6 +628,34 @@ def generate_setup_for_class(
644628
emitter.emit_line("}")
645629

646630

631+
def generate_setup_for_class(
632+
cl: ClassIR, vtable_name: str, shadow_vtable_name: str | None, emitter: Emitter
633+
) -> None:
634+
"""Generate a native function that performs setup needed after allocation."""
635+
emitter.emit_line(native_function_header(cl.setup, emitter))
636+
emitter.emit_line("{")
637+
obj_arg_name = REG_PREFIX + cl.setup.sig.args[0].name
638+
emitter.emit_line(f"PyTypeObject *type = Py_TYPE({obj_arg_name});")
639+
struct_name = cl.struct_name(emitter.names)
640+
emitter.emit_line(f"{struct_name} *self = ({struct_name} *){obj_arg_name};")
641+
642+
if shadow_vtable_name:
643+
emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
644+
emitter.emit_line(f"self->vtable = {shadow_vtable_name};")
645+
emitter.emit_line("} else {")
646+
emitter.emit_line(f"self->vtable = {vtable_name};")
647+
emitter.emit_line("}")
648+
else:
649+
emitter.emit_line(f"self->vtable = {vtable_name};")
650+
651+
if cl.has_method("__call__"):
652+
name = cl.method_decl("__call__").cname(emitter.names)
653+
emitter.emit_line(f"self->vectorcall = {PREFIX}{name};")
654+
655+
emitter.emit_line("return (PyObject *)self;")
656+
emitter.emit_line("}")
657+
658+
647659
def emit_clear_bitmaps(cl: ClassIR, emitter: Emitter) -> None:
648660
"""Emit C code to clear bitmaps that track if attributes have an assigned value."""
649661
for i in range(0, len(cl.bitmap_attrs), BITMAP_BITS):
@@ -666,8 +678,9 @@ def emit_attr_defaults_func_call(defaults_fn: FuncIR, self_name: str, emitter: E
666678
)
667679

668680

669-
def emit_setup_or_dunder_new_call(
681+
def emit_allocate_or_dunder_new_call(
670682
cl: ClassIR,
683+
allocate_name: str,
671684
setup_name: str,
672685
type_arg: str,
673686
native_prefix: bool,
@@ -680,8 +693,9 @@ def emit_null_check() -> None:
680693

681694
new_fn = cl.get_method("__new__")
682695
if not new_fn:
683-
emitter.emit_line(f"PyObject *self = {setup_name}({type_arg});")
696+
emitter.emit_line(f"PyObject *self = {allocate_name}({type_arg});")
684697
emit_null_check()
698+
emitter.emit_line(f"self = {setup_name}(self);")
685699
return
686700
prefix = emitter.get_group_prefix(new_fn.decl) + NATIVE_PREFIX if native_prefix else PREFIX
687701
all_args = type_arg
@@ -694,11 +708,14 @@ def emit_null_check() -> None:
694708
emitter.emit_line(f"if (Py_TYPE(self) != {emitter.type_struct_name(cl)})")
695709
emitter.emit_line(" return self;")
696710

711+
emitter.emit_line(f"self = {setup_name}(self);")
712+
697713

698714
def generate_constructor_for_class(
699715
cl: ClassIR,
700716
fn: FuncDecl,
701717
init_fn: FuncIR | None,
718+
allocate_name: str,
702719
setup_name: str,
703720
vtable_name: str,
704721
emitter: Emitter,
@@ -717,7 +734,9 @@ def generate_constructor_for_class(
717734
and fn.sig.args[0].kind == ARG_STAR
718735
and fn.sig.args[1].kind == ARG_STAR2
719736
)
720-
emit_setup_or_dunder_new_call(cl, setup_name, type_arg, not use_wrapper, new_args, emitter)
737+
emit_allocate_or_dunder_new_call(
738+
cl, allocate_name, setup_name, type_arg, not use_wrapper, new_args, emitter
739+
)
721740

722741
args = ", ".join(["self"] + fn_args)
723742
if init_fn is not None:
@@ -779,6 +798,7 @@ def generate_new_for_class(
779798
cl: ClassIR,
780799
func_name: str,
781800
vtable_name: str,
801+
allocate_name: str,
782802
setup_name: str,
783803
init_fn: FuncIR | None,
784804
emitter: Emitter,
@@ -797,7 +817,9 @@ def generate_new_for_class(
797817

798818
type_arg = "(PyObject*)type"
799819
new_args = "args, kwds"
800-
emit_setup_or_dunder_new_call(cl, setup_name, type_arg, False, new_args, emitter)
820+
emit_allocate_or_dunder_new_call(
821+
cl, allocate_name, setup_name, type_arg, False, new_args, emitter
822+
)
801823
if (
802824
not init_fn
803825
or cl.allow_interpreted_subclasses

mypyc/ir/class_ir.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,25 @@ def __init__(
133133
self.builtin_base: str | None = None
134134
# Default empty constructor
135135
self.ctor = FuncDecl(name, None, module_name, FuncSignature([], RInstance(self)))
136-
# Declare setup method that allocates and initializes an object. type is the
136+
# Declare allocate method that allocates and initializes an object. type is the
137137
# type of the class being initialized, which could be another class if there
138138
# is an interpreted subclass.
139139
# TODO: Make it a regular method and generate its body in IR
140+
self.allocate = FuncDecl(
141+
"__mypyc__" + name + "_allocate",
142+
None,
143+
module_name,
144+
FuncSignature([RuntimeArg("type", object_rprimitive)], RInstance(self)),
145+
)
146+
# Declare setup method that performs initialization that needs to happen after
147+
# allocation in case of inherited dunder new methods, eg. setting the vtable pointer.
148+
# The argument is an already allocated object.
149+
# TODO: Make it a regular method and generate its body in IR
140150
self.setup = FuncDecl(
141151
"__mypyc__" + name + "_setup",
142152
None,
143153
module_name,
144-
FuncSignature([RuntimeArg("type", object_rprimitive)], RInstance(self)),
154+
FuncSignature([RuntimeArg("obj", object_rprimitive)], RInstance(self)),
145155
)
146156
# Attributes defined in the class (not inherited)
147157
self.attributes: dict[str, RType] = {}

mypyc/irbuild/specialize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) ->
11031103
method_args = fn.fitem.arg_names
11041104
if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
11051105
subtype = builder.accept(expr.args[0])
1106-
return builder.add(Call(ir.setup, [subtype], expr.line))
1106+
return builder.add(Call(ir.allocate, [subtype], expr.line))
11071107

11081108
return None
11091109

mypyc/test-data/irbuild-classes.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,7 +1715,7 @@ def Test.__new__(cls, val):
17151715
r0, obj :: __main__.Test
17161716
r1 :: bool
17171717
L0:
1718-
r0 = __mypyc__Test_setup(cls)
1718+
r0 = __mypyc__Test_allocate(cls)
17191719
obj = r0
17201720
obj.val = val; r1 = is_error
17211721
return obj
@@ -1732,7 +1732,7 @@ def NewClassMethod.__new__(cls, val):
17321732
r0, obj :: __main__.NewClassMethod
17331733
r1 :: bool
17341734
L0:
1735-
r0 = __mypyc__NewClassMethod_setup(cls)
1735+
r0 = __mypyc__NewClassMethod_allocate(cls)
17361736
obj = r0
17371737
obj.val = val; r1 = is_error
17381738
return obj
@@ -1870,7 +1870,7 @@ def Test.__new__(cls, val):
18701870
r0, obj :: __main__.Test
18711871
r1 :: bool
18721872
L0:
1873-
r0 = __mypyc__Test_setup(cls)
1873+
r0 = __mypyc__Test_allocate(cls)
18741874
obj = r0
18751875
obj.val = val; r1 = is_error
18761876
return obj
@@ -1887,7 +1887,7 @@ def NewClassMethod.__new__(cls, val):
18871887
r0, obj :: __main__.NewClassMethod
18881888
r1 :: bool
18891889
L0:
1890-
r0 = __mypyc__NewClassMethod_setup(cls)
1890+
r0 = __mypyc__NewClassMethod_allocate(cls)
18911891
obj = r0
18921892
obj.val = val; r1 = is_error
18931893
return obj

0 commit comments

Comments
 (0)