Skip to content

Commit ecbd190

Browse files
authored
[mypyc] Use struct access instead of native getters/setters (#8660)
After some discussions with Sully we agreed that essentially inlining all attribute access may be a good idea. For trait attribute access, this requires setting up offset tables. The offset tables are placed after a trait vtable for every trait base. I didn't add many tests, relying on existing ones, since this PR doesn't add any new functionality.
1 parent fd9ffb8 commit ecbd190

File tree

9 files changed

+338
-199
lines changed

9 files changed

+338
-199
lines changed

mypyc/codegen/emit.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,21 @@ def tuple_c_declaration(self, rtuple: RTuple) -> List[str]:
248248

249249
return result
250250

251+
def emit_undefined_attr_check(self, rtype: RType, attr_expr: str,
252+
compare: str,
253+
unlikely: bool = False) -> None:
254+
if isinstance(rtype, RTuple):
255+
check = '({})'.format(self.tuple_undefined_check_cond(
256+
rtype, attr_expr, self.c_undefined_value, compare)
257+
)
258+
else:
259+
check = '({} {} {})'.format(
260+
attr_expr, compare, self.c_undefined_value(rtype)
261+
)
262+
if unlikely:
263+
check = '(unlikely{})'.format(check)
264+
self.emit_line('if {} {{'.format(check))
265+
251266
def tuple_undefined_check_cond(
252267
self, rtuple: RTuple, tuple_expr_in_c: str,
253268
c_type_compare_val: Callable[[RType], str], compare: str) -> str:

mypyc/codegen/emitclass.py

Lines changed: 65 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66

77
from mypyc.common import PREFIX, NATIVE_PREFIX, REG_PREFIX
88
from mypyc.codegen.emit import Emitter, HeaderDeclaration
9-
from mypyc.codegen.emitfunc import native_function_header, native_getter_name, native_setter_name
9+
from mypyc.codegen.emitfunc import native_function_header
1010
from mypyc.codegen.emitwrapper import (
1111
generate_dunder_wrapper, generate_hash_wrapper, generate_richcompare_wrapper,
1212
generate_bool_wrapper, generate_get_wrapper,
1313
)
1414
from mypyc.ir.rtypes import RType, RTuple, object_rprimitive
1515
from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
16-
from mypyc.ir.class_ir import ClassIR, VTableMethod, VTableEntries
16+
from mypyc.ir.class_ir import ClassIR, VTableEntries
1717
from mypyc.sametype import is_same_type
1818
from mypyc.namegen import NameGenerator
1919

@@ -98,8 +98,6 @@ def generate_class_type_decl(cl: ClassIR, c_emitter: Emitter,
9898
generate_object_struct(cl, external_emitter)
9999
generate_full = not cl.is_trait and not cl.builtin_base
100100
if generate_full:
101-
declare_native_getters_and_setters(cl, emitter)
102-
103101
context.declarations[emitter.native_function_name(cl.ctor)] = HeaderDeclaration(
104102
'{};'.format(native_function_header(cl.ctor, emitter)),
105103
needs_export=True,
@@ -216,7 +214,6 @@ def emit_line() -> None:
216214
emit_line()
217215
generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
218216
emit_line()
219-
generate_native_getters_and_setters(cl, emitter)
220217

221218
if cl.allow_interpreted_subclasses:
222219
shadow_vtable_name = generate_vtables(
@@ -293,64 +290,6 @@ def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
293290
)
294291

295292

296-
def declare_native_getters_and_setters(cl: ClassIR,
297-
emitter: Emitter) -> None:
298-
decls = emitter.context.declarations
299-
for attr, rtype in cl.attributes.items():
300-
getter_name = native_getter_name(cl, attr, emitter.names)
301-
setter_name = native_setter_name(cl, attr, emitter.names)
302-
decls[getter_name] = HeaderDeclaration(
303-
'{}{}({} *self);'.format(emitter.ctype_spaced(rtype),
304-
getter_name,
305-
cl.struct_name(emitter.names)),
306-
needs_export=True,
307-
)
308-
decls[setter_name] = HeaderDeclaration(
309-
'bool {}({} *self, {}value);'.format(native_setter_name(cl, attr, emitter.names),
310-
cl.struct_name(emitter.names),
311-
emitter.ctype_spaced(rtype)),
312-
needs_export=True,
313-
)
314-
315-
316-
def generate_native_getters_and_setters(cl: ClassIR,
317-
emitter: Emitter) -> None:
318-
for attr, rtype in cl.attributes.items():
319-
attr_field = emitter.attr(attr)
320-
321-
# Native getter
322-
emitter.emit_line('{}{}({} *self)'.format(emitter.ctype_spaced(rtype),
323-
native_getter_name(cl, attr, emitter.names),
324-
cl.struct_name(emitter.names)))
325-
emitter.emit_line('{')
326-
if rtype.is_refcounted:
327-
emit_undefined_check(rtype, emitter, attr_field, '==')
328-
emitter.emit_lines(
329-
'PyErr_SetString(PyExc_AttributeError, "attribute {} of {} undefined");'.format(
330-
repr(attr), repr(cl.name)),
331-
'} else {')
332-
emitter.emit_inc_ref('self->{}'.format(attr_field), rtype)
333-
emitter.emit_line('}')
334-
emitter.emit_line('return self->{};'.format(attr_field))
335-
emitter.emit_line('}')
336-
emitter.emit_line()
337-
# Native setter
338-
emitter.emit_line(
339-
'bool {}({} *self, {}value)'.format(native_setter_name(cl, attr, emitter.names),
340-
cl.struct_name(emitter.names),
341-
emitter.ctype_spaced(rtype)))
342-
emitter.emit_line('{')
343-
if rtype.is_refcounted:
344-
emit_undefined_check(rtype, emitter, attr_field, '!=')
345-
emitter.emit_dec_ref('self->{}'.format(attr_field), rtype)
346-
emitter.emit_line('}')
347-
# This steal the reference to src, so we don't need to increment the arg
348-
emitter.emit_lines('self->{} = value;'.format(attr_field),
349-
'return 1;',
350-
'}')
351-
emitter.emit_line()
352-
353-
354293
def generate_vtables(base: ClassIR,
355294
vtable_setup_name: str,
356295
vtable_name: str,
@@ -359,6 +298,18 @@ def generate_vtables(base: ClassIR,
359298
"""Emit the vtables and vtable setup functions for a class.
360299
361300
This includes both the primary vtable and any trait implementation vtables.
301+
The trait vtables go before the main vtable, and have the following layout:
302+
{
303+
CPyType_T1, // pointer to type object
304+
C_T1_trait_vtable, // pointer to array of method pointers
305+
C_T1_offset_table, // pointer to array of attribute offsets
306+
CPyType_T2,
307+
C_T2_trait_vtable,
308+
C_T2_offset_table,
309+
...
310+
}
311+
The method implementations are calculated at the end of IR pass, attribute
312+
offsets are {offsetof(native__C, _x1), offsetof(native__C, _y1), ...}.
362313
363314
To account for both dynamic loading and dynamic class creation,
364315
vtables are populated dynamically at class creation time, so we
@@ -370,22 +321,33 @@ def generate_vtables(base: ClassIR,
370321
371322
Returns the expression to use to refer to the vtable, which might be
372323
different than the name, if there are trait vtables.
373-
374324
"""
375325

376326
def trait_vtable_name(trait: ClassIR) -> str:
377327
return '{}_{}_trait_vtable{}'.format(
378328
base.name_prefix(emitter.names), trait.name_prefix(emitter.names),
379329
'_shadow' if shadow else '')
380330

331+
def trait_offset_table_name(trait: ClassIR) -> str:
332+
return '{}_{}_offset_table'.format(
333+
base.name_prefix(emitter.names), trait.name_prefix(emitter.names)
334+
)
335+
381336
# Emit array definitions with enough space for all the entries
382337
emitter.emit_line('static CPyVTableItem {}[{}];'.format(
383338
vtable_name,
384-
max(1, len(base.vtable_entries) + 2 * len(base.trait_vtables))))
339+
max(1, len(base.vtable_entries) + 3 * len(base.trait_vtables))))
340+
385341
for trait, vtable in base.trait_vtables.items():
342+
# Trait methods entry (vtable index -> method implementation).
386343
emitter.emit_line('static CPyVTableItem {}[{}];'.format(
387344
trait_vtable_name(trait),
388345
max(1, len(vtable))))
346+
# Trait attributes entry (attribute number in trait -> offset in actual struct).
347+
emitter.emit_line('static size_t {}[{}];'.format(
348+
trait_offset_table_name(trait),
349+
max(1, len(trait.attributes)))
350+
)
389351

390352
# Emit vtable setup function
391353
emitter.emit_line('static bool')
@@ -398,43 +360,59 @@ def trait_vtable_name(trait: ClassIR) -> str:
398360
subtables = []
399361
for trait, vtable in base.trait_vtables.items():
400362
name = trait_vtable_name(trait)
363+
offset_name = trait_offset_table_name(trait)
401364
generate_vtable(vtable, name, emitter, [], shadow)
402-
subtables.append((trait, name))
365+
generate_offset_table(offset_name, emitter, trait, base)
366+
subtables.append((trait, name, offset_name))
403367

404368
generate_vtable(base.vtable_entries, vtable_name, emitter, subtables, shadow)
405369

406370
emitter.emit_line('return 1;')
407371
emitter.emit_line('}')
408372

409-
return vtable_name if not subtables else "{} + {}".format(vtable_name, len(subtables) * 2)
373+
return vtable_name if not subtables else "{} + {}".format(vtable_name, len(subtables) * 3)
374+
375+
376+
def generate_offset_table(trait_offset_table_name: str,
377+
emitter: Emitter,
378+
trait: ClassIR,
379+
cl: ClassIR) -> None:
380+
"""Generate attribute offset row of a trait vtable."""
381+
emitter.emit_line('size_t {}_scratch[] = {{'.format(trait_offset_table_name))
382+
for attr in trait.attributes:
383+
emitter.emit_line('offsetof({}, {}),'.format(
384+
cl.struct_name(emitter.names), emitter.attr(attr)
385+
))
386+
if not trait.attributes:
387+
# This is for msvc.
388+
emitter.emit_line('0')
389+
emitter.emit_line('};')
390+
emitter.emit_line('memcpy({name}, {name}_scratch, sizeof({name}));'.format(
391+
name=trait_offset_table_name)
392+
)
410393

411394

412395
def generate_vtable(entries: VTableEntries,
413396
vtable_name: str,
414397
emitter: Emitter,
415-
subtables: List[Tuple[ClassIR, str]],
398+
subtables: List[Tuple[ClassIR, str, str]],
416399
shadow: bool) -> None:
417400
emitter.emit_line('CPyVTableItem {}_scratch[] = {{'.format(vtable_name))
418401
if subtables:
419402
emitter.emit_line('/* Array of trait vtables */')
420-
for trait, table in subtables:
421-
emitter.emit_line('(CPyVTableItem){}, (CPyVTableItem){},'.format(
422-
emitter.type_struct_name(trait), table))
403+
for trait, table, offset_table in subtables:
404+
emitter.emit_line(
405+
'(CPyVTableItem){}, (CPyVTableItem){}, (CPyVTableItem){},'.format(
406+
emitter.type_struct_name(trait), table, offset_table))
423407
emitter.emit_line('/* Start of real vtable */')
424408

425409
for entry in entries:
426-
if isinstance(entry, VTableMethod):
427-
method = entry.shadow_method if shadow and entry.shadow_method else entry.method
428-
emitter.emit_line('(CPyVTableItem){}{}{},'.format(
429-
emitter.get_group_prefix(entry.method.decl),
430-
NATIVE_PREFIX,
431-
method.cname(emitter.names)))
432-
else:
433-
cl, attr, is_setter = entry
434-
namer = native_setter_name if is_setter else native_getter_name
435-
emitter.emit_line('(CPyVTableItem){}{},'.format(
436-
emitter.get_group_prefix(cl),
437-
namer(cl, attr, emitter.names)))
410+
method = entry.shadow_method if shadow and entry.shadow_method else entry.method
411+
emitter.emit_line('(CPyVTableItem){}{}{},'.format(
412+
emitter.get_group_prefix(entry.method.decl),
413+
NATIVE_PREFIX,
414+
method.cname(emitter.names)))
415+
438416
# msvc doesn't allow empty arrays; maybe allowing them at all is an extension?
439417
if not entries:
440418
emitter.emit_line('NULL')
@@ -747,7 +725,8 @@ def generate_getter(cl: ClassIR,
747725
emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names),
748726
cl.struct_name(emitter.names)))
749727
emitter.emit_line('{')
750-
emit_undefined_check(rtype, emitter, attr_field, '==')
728+
attr_expr = 'self->{}'.format(attr_field)
729+
emitter.emit_undefined_attr_check(rtype, attr_expr, '==', unlikely=True)
751730
emitter.emit_line('PyErr_SetString(PyExc_AttributeError,')
752731
emitter.emit_line(' "attribute {} of {} undefined");'.format(repr(attr),
753732
repr(cl.name)))
@@ -770,7 +749,8 @@ def generate_setter(cl: ClassIR,
770749
cl.struct_name(emitter.names)))
771750
emitter.emit_line('{')
772751
if rtype.is_refcounted:
773-
emit_undefined_check(rtype, emitter, attr_field, '!=')
752+
attr_expr = 'self->{}'.format(attr_field)
753+
emitter.emit_undefined_attr_check(rtype, attr_expr, '!=')
774754
emitter.emit_dec_ref('self->{}'.format(attr_field), rtype)
775755
emitter.emit_line('}')
776756
emitter.emit_line('if (value != NULL) {')
@@ -833,15 +813,3 @@ def generate_property_setter(cl: ClassIR,
833813
func_ir.cname(emitter.names)))
834814
emitter.emit_line('return 0;')
835815
emitter.emit_line('}')
836-
837-
838-
def emit_undefined_check(rtype: RType, emitter: Emitter, attr: str, compare: str) -> None:
839-
if isinstance(rtype, RTuple):
840-
attr_expr = 'self->{}'.format(attr)
841-
emitter.emit_line(
842-
'if ({}) {{'.format(
843-
emitter.tuple_undefined_check_cond(
844-
rtype, attr_expr, emitter.c_undefined_value, compare)))
845-
else:
846-
emitter.emit_line(
847-
'if (self->{} {} {}) {{'.format(attr, compare, emitter.c_undefined_value(rtype)))

0 commit comments

Comments
 (0)