def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None: emitter.emit_line('static PyMethodDef {}[] = {{'.format(name)) for fn in cl.methods.values(): if fn.decl.is_prop_setter or fn.decl.is_prop_getter: continue emitter.emit_line('{{"{}",'.format(fn.name)) emitter.emit_line(' (PyCFunction){}{},'.format(PREFIX, fn.cname( emitter.names))) if use_fastcall(emitter.capi_version): flags = ['METH_FASTCALL'] else: flags = ['METH_VARARGS'] flags.append('METH_KEYWORDS') if fn.decl.kind == FUNC_STATICMETHOD: flags.append('METH_STATIC') elif fn.decl.kind == FUNC_CLASSMETHOD: flags.append('METH_CLASS') emitter.emit_line(' {}, NULL}},'.format(' | '.join(flags))) # Provide a default __getstate__ and __setstate__ if not cl.has_method('__setstate__') and not cl.has_method('__getstate__'): emitter.emit_lines( '{"__setstate__", (PyCFunction)CPyPickle_SetState, METH_O, NULL},', '{"__getstate__", (PyCFunction)CPyPickle_GetState, METH_NOARGS, NULL},', ) emitter.emit_line('{NULL} /* Sentinel */') emitter.emit_line('};')
def generate_richcompare_wrapper(cl: ClassIR, emitter: Emitter) -> Optional[str]: """Generates a wrapper for richcompare dunder methods.""" # Sort for determinism on Python 3.5 matches = sorted([name for name in RICHCOMPARE_OPS if cl.has_method(name)]) if not matches: return None name = '{}_RichCompare_{}'.format(DUNDER_PREFIX, cl.name_prefix(emitter.names)) emitter.emit_line( 'static PyObject *{name}(PyObject *obj_lhs, PyObject *obj_rhs, int op) {{'.format( name=name) ) emitter.emit_line('switch (op) {') for func in matches: emitter.emit_line('case {}: {{'.format(RICHCOMPARE_OPS[func])) method = cl.get_method(func) assert method is not None generate_wrapper_core(method, emitter, arg_names=['lhs', 'rhs']) emitter.emit_line('}') emitter.emit_line('}') emitter.emit_line('Py_INCREF(Py_NotImplemented);') emitter.emit_line('return Py_NotImplemented;') emitter.emit_line('}') return name
def specialize_parent_vtable(cls: ClassIR, parent: ClassIR) -> VTableEntries: """Generate the part of a vtable corresponding to a parent class or trait""" updated = [] for entry in parent.vtable_entries: if isinstance(entry, VTableMethod): # Find the original method corresponding to this vtable entry. # (This may not be the method in the entry, if it was overridden.) orig_parent_method = entry.cls.get_method(entry.name) assert orig_parent_method method_cls = cls.get_method_and_class(entry.name) if method_cls: child_method, defining_cls = method_cls # TODO: emit a wrapper for __init__ that raises or something if (is_same_method_signature(orig_parent_method.sig, child_method.sig) or orig_parent_method.name == '__init__'): entry = VTableMethod(entry.cls, entry.name, child_method, entry.shadow_method) else: entry = VTableMethod(entry.cls, entry.name, defining_cls.glue_methods[(entry.cls, entry.name)], entry.shadow_method) else: # If it is an attribute from a trait, we need to find out # the real class it got mixed in at and point to that. if parent.is_trait: _, origin_cls = cls.attr_details(entry.name) entry = VTableAttr(origin_cls, entry.name, entry.is_setter) updated.append(entry) return updated
def setup_env_class(builder: IRBuilder) -> ClassIR: """Generate a class representing a function environment. Note that the variables in the function environment are not actually populated here. This is because when the environment class is generated, the function environment has not yet been visited. This behavior is allowed so that when the compiler visits nested functions, it can use the returned ClassIR instance to figure out free variables it needs to access. The remaining attributes of the environment class are populated when the environment registers are loaded. Return a ClassIR representing an environment for a function containing a nested function. """ env_class = ClassIR('{}_env'.format(builder.fn_info.namespaced_name()), builder.module_name, is_generated=True) env_class.attributes[SELF_NAME] = RInstance(env_class) if builder.fn_info.is_nested: # If the function is nested, its environment class must contain an environment # attribute pointing to its encapsulating functions' environment class. env_class.attributes[ENV_ATTR_NAME] = RInstance( builder.fn_infos[-2].env_class) env_class.mro = [env_class] builder.fn_info.env_class = env_class builder.classes.append(env_class) return env_class
def generate_getseter_declarations(cl: ClassIR, emitter: Emitter) -> None: if not cl.is_trait: for attr in cl.attributes: emitter.emit_line('static PyObject *') emitter.emit_line('{}({} *self, void *closure);'.format( getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('static int') emitter.emit_line( '{}({} *self, PyObject *value, void *closure);'.format( setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) for prop in cl.properties: # Generate getter declaration emitter.emit_line('static PyObject *') emitter.emit_line('{}({} *self, void *closure);'.format( getter_name(cl, prop, emitter.names), cl.struct_name(emitter.names))) # Generate property setter declaration if a setter exists if cl.properties[prop][1]: emitter.emit_line('static int') emitter.emit_line( '{}({} *self, PyObject *value, void *closure);'.format( setter_name(cl, prop, emitter.names), cl.struct_name(emitter.names)))
def generate_native_getters_and_setters(cl: ClassIR, emitter: Emitter) -> None: for attr, rtype in cl.attributes.items(): attr_field = emitter.attr(attr) # Native getter emitter.emit_line('{}{}({} *self)'.format( emitter.ctype_spaced(rtype), native_getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('{') if rtype.is_refcounted: emit_undefined_check(rtype, emitter, attr_field, '==') emitter.emit_lines( 'PyErr_SetString(PyExc_AttributeError, "attribute {} of {} undefined");' .format(repr(attr), repr(cl.name)), '} else {') emitter.emit_inc_ref('self->{}'.format(attr_field), rtype) emitter.emit_line('}') emitter.emit_line('return self->{};'.format(attr_field)) emitter.emit_line('}') emitter.emit_line() # Native setter emitter.emit_line('bool {}({} *self, {}value)'.format( native_setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names), emitter.ctype_spaced(rtype))) emitter.emit_line('{') if rtype.is_refcounted: emit_undefined_check(rtype, emitter, attr_field, '!=') emitter.emit_dec_ref('self->{}'.format(attr_field), rtype) emitter.emit_line('}') # This steal the reference to src, so we don't need to increment the arg emitter.emit_lines('self->{} = value;'.format(attr_field), 'return 1;', '}') emitter.emit_line()
def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None: seen_attrs: Set[Tuple[str, RType]] = set() lines: List[str] = [] lines += ['typedef struct {', 'PyObject_HEAD', 'CPyVTableItem *vtable;'] if cl.has_method('__call__') and emitter.use_vectorcall(): lines.append('vectorcallfunc vectorcall;') for base in reversed(cl.base_mro): if not base.is_trait: for attr, rtype in base.attributes.items(): if (attr, rtype) not in seen_attrs: lines.append('{}{};'.format(emitter.ctype_spaced(rtype), emitter.attr(attr))) seen_attrs.add((attr, rtype)) if isinstance(rtype, RTuple): emitter.declare_tuple_struct(rtype) lines.append('}} {};'.format(cl.struct_name(emitter.names))) lines.append('') emitter.context.declarations[cl.struct_name(emitter.names)] = HeaderDeclaration( lines, is_type=True )
def generate_getter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None: attr_field = emitter.attr(attr) emitter.emit_line('static PyObject *') emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('{') attr_expr = f'self->{attr_field}' # HACK: Don't consider refcounted values as always defined, since it's possible to # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted # values is benign. always_defined = cl.is_always_defined(attr) and not rtype.is_refcounted if not always_defined: emitter.emit_undefined_attr_check(rtype, attr_expr, '==', unlikely=True) emitter.emit_line('PyErr_SetString(PyExc_AttributeError,') emitter.emit_line(' "attribute {} of {} undefined");'.format(repr(attr), repr(cl.name))) emitter.emit_line('return NULL;') emitter.emit_line('}') emitter.emit_inc_ref(f'self->{attr_field}', rtype) emitter.emit_box(f'self->{attr_field}', 'retval', rtype, declare_dest=True) emitter.emit_line('return retval;') emitter.emit_line('}')
def setUp(self) -> None: self.env = Environment() self.n = self.env.add_local(Var('n'), int_rprimitive) self.m = self.env.add_local(Var('m'), int_rprimitive) self.k = self.env.add_local(Var('k'), int_rprimitive) self.l = self.env.add_local(Var('l'), list_rprimitive) # noqa self.ll = self.env.add_local(Var('ll'), list_rprimitive) self.o = self.env.add_local(Var('o'), object_rprimitive) self.o2 = self.env.add_local(Var('o2'), object_rprimitive) self.d = self.env.add_local(Var('d'), dict_rprimitive) self.b = self.env.add_local(Var('b'), bool_rprimitive) self.t = self.env.add_local(Var('t'), RTuple([int_rprimitive, bool_rprimitive])) self.tt = self.env.add_local( Var('tt'), RTuple( [RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) ir = ClassIR('A', 'mod') ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) compute_vtable(ir) ir.mro = [ir] self.r = self.env.add_local(Var('r'), RInstance(ir)) self.context = EmitterContext(NameGenerator([['mod']])) self.emitter = Emitter(self.context, self.env) self.declarations = Emitter(self.context, self.env) self.visitor = FunctionEmitterVisitor(self.emitter, self.declarations, 'prog.py', 'prog')
def setUp(self) -> None: self.env = Environment() self.n = self.env.add_local(Var('n'), int_rprimitive) self.m = self.env.add_local(Var('m'), int_rprimitive) self.k = self.env.add_local(Var('k'), int_rprimitive) self.l = self.env.add_local(Var('l'), list_rprimitive) # noqa self.ll = self.env.add_local(Var('ll'), list_rprimitive) self.o = self.env.add_local(Var('o'), object_rprimitive) self.o2 = self.env.add_local(Var('o2'), object_rprimitive) self.d = self.env.add_local(Var('d'), dict_rprimitive) self.b = self.env.add_local(Var('b'), bool_rprimitive) self.s1 = self.env.add_local(Var('s1'), short_int_rprimitive) self.s2 = self.env.add_local(Var('s2'), short_int_rprimitive) self.i32 = self.env.add_local(Var('i32'), int32_rprimitive) self.i32_1 = self.env.add_local(Var('i32_1'), int32_rprimitive) self.i64 = self.env.add_local(Var('i64'), int64_rprimitive) self.i64_1 = self.env.add_local(Var('i64_1'), int64_rprimitive) self.ptr = self.env.add_local(Var('ptr'), pointer_rprimitive) self.t = self.env.add_local(Var('t'), RTuple([int_rprimitive, bool_rprimitive])) self.tt = self.env.add_local( Var('tt'), RTuple( [RTuple([int_rprimitive, bool_rprimitive]), bool_rprimitive])) ir = ClassIR('A', 'mod') ir.attributes = OrderedDict([('x', bool_rprimitive), ('y', int_rprimitive)]) compute_vtable(ir) ir.mro = [ir] self.r = self.env.add_local(Var('r'), RInstance(ir)) self.context = EmitterContext(NameGenerator([['mod']]))
def setup_generator_class(builder: IRBuilder) -> ClassIR: name = '{}_gen'.format(builder.fn_info.namespaced_name()) generator_class_ir = ClassIR(name, builder.module_name, is_generated=True) generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class) generator_class_ir.mro = [generator_class_ir] builder.classes.append(generator_class_ir) builder.fn_info.generator_class = GeneratorClass(generator_class_ir) return generator_class_ir
def setup_callable_class(builder: IRBuilder) -> None: """Generates a callable class representing a nested function or a function within a non-extension class and sets up the 'self' variable for that class. This takes the most recently visited function and returns a ClassIR to represent that function. Each callable class contains an environment attribute with points to another ClassIR representing the environment class where some of its variables can be accessed. Note that its '__call__' method is not yet implemented, and is implemented in the add_call_to_callable_class function. Returns a newly constructed ClassIR representing the callable class for the nested function. """ # Check to see that the name has not already been taken. If so, rename the class. We allow # multiple uses of the same function name because this is valid in if-else blocks. Example: # if True: # def foo(): ----> foo_obj() # return True # else: # def foo(): ----> foo_obj_0() # return False name = base_name = '{}_obj'.format(builder.fn_info.namespaced_name()) count = 0 while name in builder.callable_class_names: name = base_name + '_' + str(count) count += 1 builder.callable_class_names.add(name) # Define the actual callable class ClassIR, and set its environment to point at the # previously defined environment class. callable_class_ir = ClassIR(name, builder.module_name, is_generated=True) # The functools @wraps decorator attempts to call setattr on nested functions, so # we create a dict for these nested functions. # https://github.com/python/cpython/blob/3.7/Lib/functools.py#L58 if builder.fn_info.is_nested: callable_class_ir.has_dict = True # If the enclosing class doesn't contain nested (which will happen if # this is a toplevel lambda), don't set up an environment. if builder.fn_infos[-2].contains_nested: callable_class_ir.attributes[ENV_ATTR_NAME] = RInstance( builder.fn_infos[-2].env_class ) callable_class_ir.mro = [callable_class_ir] builder.fn_info.callable_class = ImplicitClass(callable_class_ir) builder.classes.append(callable_class_ir) # Add a 'self' variable to the callable class' environment, and store that variable in a # register to be accessed later. self_target = add_self_to_env(builder.environment, callable_class_ir) builder.fn_info.callable_class.self_reg = builder.read(self_target, builder.fn_info.fitem.line)
def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None: attr_field = emitter.attr(attr) emitter.emit_line('static int') emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format( setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('{') deletable = cl.is_deletable(attr) if not deletable: emitter.emit_line('if (value == NULL) {') emitter.emit_line('PyErr_SetString(PyExc_AttributeError,') emitter.emit_line(' "{} object attribute {} cannot be deleted");'.format(repr(cl.name), repr(attr))) emitter.emit_line('return -1;') emitter.emit_line('}') # HACK: Don't consider refcounted values as always defined, since it's possible to # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted # values is benign. always_defined = cl.is_always_defined(attr) and not rtype.is_refcounted if rtype.is_refcounted: attr_expr = f'self->{attr_field}' if not always_defined: emitter.emit_undefined_attr_check(rtype, attr_expr, '!=') emitter.emit_dec_ref('self->{}'.format(attr_field), rtype) if not always_defined: emitter.emit_line('}') if deletable: emitter.emit_line('if (value != NULL) {') if rtype.is_unboxed: emitter.emit_unbox('value', 'tmp', rtype, error=ReturnHandler('-1'), declare_dest=True) elif is_same_type(rtype, object_rprimitive): emitter.emit_line('PyObject *tmp = value;') else: emitter.emit_cast('value', 'tmp', rtype, declare_dest=True) emitter.emit_lines('if (!tmp)', ' return -1;') emitter.emit_inc_ref('tmp', rtype) emitter.emit_line(f'self->{attr_field} = tmp;') if deletable: emitter.emit_line('} else') emitter.emit_line(' self->{} = {};'.format(attr_field, emitter.c_undefined_value(rtype))) emitter.emit_line('return 0;') emitter.emit_line('}')
def check_deletable_declaration(builder: IRBuilder, cl: ClassIR, line: int) -> None: for attr in cl.deletable: if attr not in cl.attributes: if not cl.has_attr(attr): builder.error('Attribute "{}" not defined'.format(attr), line) continue for base in cl.mro: if attr in base.property_types: builder.error('Cannot make property "{}" deletable'.format(attr), line) break else: _, base = cl.attr_details(attr) builder.error(('Attribute "{}" not defined in "{}" ' + '(defined in "{}")').format(attr, cl.name, base.name), line)
def declare_native_getters_and_setters(cl: ClassIR, emitter: Emitter) -> None: decls = emitter.context.declarations for attr, rtype in cl.attributes.items(): getter_name = native_getter_name(cl, attr, emitter.names) setter_name = native_setter_name(cl, attr, emitter.names) decls[getter_name] = HeaderDeclaration( '{}{}({} *self);'.format(emitter.ctype_spaced(rtype), getter_name, cl.struct_name(emitter.names)), needs_export=True, ) decls[setter_name] = HeaderDeclaration( 'bool {}({} *self, {}value);'.format( native_setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names), emitter.ctype_spaced(rtype)), needs_export=True, )
def generate_bin_op_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for a native binary dunder method. The same wrapper that handles the forward method (e.g. __add__) also handles the corresponding reverse method (e.g. __radd__), if defined. Both arguments and the return value are PyObject *. """ gen = WrapperGenerator(cl, emitter) gen.set_target(fn) gen.arg_names = ['left', 'right'] wrapper_name = gen.wrapper_name() gen.emit_header() if fn.name not in reverse_op_methods and fn.name in reverse_op_method_names: # There's only a reverse operator method. generate_bin_op_reverse_only_wrapper(emitter, gen) else: rmethod = reverse_op_methods[fn.name] fn_rev = cl.get_method(rmethod) if fn_rev is None: # There's only a forward operator method. generate_bin_op_forward_only_wrapper(fn, emitter, gen) else: # There's both a forward and a reverse operator method. generate_bin_op_both_wrappers(cl, fn, fn_rev, emitter, gen) return wrapper_name
def get_attr_expr(self, obj: str, op: Union[GetAttr, SetAttr], decl_cl: ClassIR) -> str: """Generate attribute accessor for normal (non-property) access. This either has a form like obj->attr_name for attributes defined in non-trait classes, and *(obj + attr_offset) for attributes defined by traits. We also insert all necessary C casts here. """ cast = '({} *)'.format(op.class_type.struct_name(self.emitter.names)) if decl_cl.is_trait and op.class_type.class_ir.is_trait: # For pure trait access find the offset first, offsets # are ordered by attribute position in the cl.attributes dict. # TODO: pre-calculate the mapping to make this faster. trait_attr_index = list(decl_cl.attributes).index(op.attr) # TODO: reuse these names somehow? offset = self.emitter.temp_name() self.declarations.emit_line('size_t {};'.format(offset)) self.emitter.emit_line('{} = {};'.format( offset, 'CPy_FindAttrOffset({}, {}, {})'.format( self.emitter.type_struct_name(decl_cl), '({}{})->vtable'.format(cast, obj), trait_attr_index, ))) attr_cast = '({} *)'.format( self.ctype(op.class_type.attr_type(op.attr))) return '*{}((char *){} + {})'.format(attr_cast, obj, offset) else: # Cast to something non-trait. Note: for this to work, all struct # members for non-trait classes must obey monotonic linear growth. if op.class_type.class_ir.is_trait: assert not decl_cl.is_trait cast = '({} *)'.format(decl_cl.struct_name(self.emitter.names)) return '({}{})->{}'.format(cast, obj, self.emitter.attr(op.attr))
def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None: attr_field = emitter.attr(attr) emitter.emit_line('static int') emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format( setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('{') if rtype.is_refcounted: attr_expr = 'self->{}'.format(attr_field) emitter.emit_undefined_attr_check(rtype, attr_expr, '!=') emitter.emit_dec_ref('self->{}'.format(attr_field), rtype) emitter.emit_line('}') emitter.emit_line('if (value != NULL) {') if rtype.is_unboxed: emitter.emit_unbox('value', 'tmp', rtype, custom_failure='return -1;', declare_dest=True) elif is_same_type(rtype, object_rprimitive): emitter.emit_line('PyObject *tmp = value;') else: emitter.emit_cast('value', 'tmp', rtype, declare_dest=True) emitter.emit_lines('if (!tmp)', ' return -1;') emitter.emit_inc_ref('tmp', rtype) emitter.emit_line('self->{} = tmp;'.format(attr_field)) emitter.emit_line('} else') emitter.emit_line(' self->{} = {};'.format( attr_field, emitter.c_undefined_value(rtype))) emitter.emit_line('return 0;') emitter.emit_line('}')
def deserialize_modules(data: Dict[str, JsonDict], ctx: DeserMaps) -> Dict[str, ModuleIR]: """Deserialize a collection of modules. The modules can contain dependencies on each other. Arguments: data: A dict containing the modules to deserialize. ctx: The deserialization maps to use and to populate. They are populated with information from the deserialized modules and as a precondition must have been populated by deserializing any dependencies of the modules being deserialized (outside of dependencies between the modules themselves). Returns a map containing the deserialized modules. """ for mod in data.values(): # First create ClassIRs for every class so that we can construct types and whatnot for cls in mod['classes']: ir = ClassIR(cls['name'], cls['module_name']) assert ir.fullname not in ctx.classes, "Class %s already in map" % ir.fullname ctx.classes[ir.fullname] = ir for mod in data.values(): # Then deserialize all of the functions so that methods are available # to the class deserialization. for method in mod['functions']: func = FuncIR.deserialize(method, ctx) assert func.decl.fullname not in ctx.functions, ( "Method %s already in map" % func.decl.fullname) ctx.functions[func.decl.fullname] = func return {k: ModuleIR.deserialize(v, ctx) for k, v in data.items()}
def analyze_always_defined_attrs_in_class(cl: ClassIR, seen: Set[ClassIR]) -> None: if cl in seen: return seen.add(cl) if (cl.is_trait or cl.inherits_python or cl.allow_interpreted_subclasses or cl.builtin_base is not None or cl.children is None or cl.is_serializable()): # Give up -- we can't enforce that attributes are always defined. return # First analyze all base classes. Track seen classes to avoid duplicate work. for base in cl.mro[1:]: analyze_always_defined_attrs_in_class(base, seen) m = cl.get_method('__init__') if m is None: cl._always_initialized_attrs = cl.attrs_with_defaults.copy() cl._sometimes_initialized_attrs = cl.attrs_with_defaults.copy() return self_reg = m.arg_regs[0] cfg = get_cfg(m.blocks) dirty = analyze_self_leaks(m.blocks, self_reg, cfg) maybe_defined = analyze_maybe_defined_attrs_in_init( m.blocks, self_reg, cl.attrs_with_defaults, cfg) all_attrs: Set[str] = set() for base in cl.mro: all_attrs.update(base.attributes) maybe_undefined = analyze_maybe_undefined_attrs_in_init( m.blocks, self_reg, initial_undefined=all_attrs - cl.attrs_with_defaults, cfg=cfg) always_defined = find_always_defined_attributes(m.blocks, self_reg, all_attrs, maybe_defined, maybe_undefined, dirty) always_defined = {a for a in always_defined if not cl.is_deletable(a)} cl._always_initialized_attrs = always_defined if dump_always_defined: print(cl.name, sorted(always_defined)) cl._sometimes_initialized_attrs = find_sometimes_defined_attributes( m.blocks, self_reg, maybe_defined, dirty) mark_attr_initialiation_ops(m.blocks, self_reg, maybe_defined, dirty) # Check if __init__ can run unpredictable code (leak 'self'). any_dirty = False for b in m.blocks: for i, op in enumerate(b.ops): if dirty.after[b, i] and not isinstance(op, Return): any_dirty = True break cl.init_self_leak = any_dirty
def generate_setup_for_class(cl: ClassIR, func_name: str, defaults_fn: Optional[FuncIR], vtable_name: str, shadow_vtable_name: Optional[str], emitter: Emitter) -> None: """Generate a native function that allocates an instance of a class.""" emitter.emit_line('static PyObject *') emitter.emit_line('{}(PyTypeObject *type)'.format(func_name)) emitter.emit_line('{') emitter.emit_line('{} *self;'.format(cl.struct_name(emitter.names))) emitter.emit_line('self = ({struct} *)type->tp_alloc(type, 0);'.format( struct=cl.struct_name(emitter.names))) emitter.emit_line('if (self == NULL)') emitter.emit_line(' return NULL;') if shadow_vtable_name: emitter.emit_line('if (type != {}) {{'.format(emitter.type_struct_name(cl))) emitter.emit_line('self->vtable = {};'.format(shadow_vtable_name)) emitter.emit_line('} else {') emitter.emit_line('self->vtable = {};'.format(vtable_name)) emitter.emit_line('}') else: emitter.emit_line('self->vtable = {};'.format(vtable_name)) if cl.has_method('__call__') and emitter.use_vectorcall(): name = cl.method_decl('__call__').cname(emitter.names) emitter.emit_line('self->vectorcall = {}{};'.format(PREFIX, name)) for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): emitter.emit_line('self->{} = {};'.format( emitter.attr(attr), emitter.c_undefined_value(rtype))) # Initialize attributes to default values, if necessary if defaults_fn is not None: emitter.emit_lines( 'if ({}{}((PyObject *)self) == 0) {{'.format( NATIVE_PREFIX, defaults_fn.cname(emitter.names)), 'Py_DECREF(self);', 'return NULL;', '}') emitter.emit_line('return (PyObject *)self;') emitter.emit_line('}')
def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None: attr_field = emitter.attr(attr) emitter.emit_line('static int') emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format( setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names))) emitter.emit_line('{') deletable = cl.is_deletable(attr) if not deletable: emitter.emit_line('if (value == NULL) {') emitter.emit_line('PyErr_SetString(PyExc_AttributeError,') emitter.emit_line( ' "{} object attribute {} cannot be deleted");'.format( repr(cl.name), repr(attr))) emitter.emit_line('return -1;') emitter.emit_line('}') if rtype.is_refcounted: attr_expr = 'self->{}'.format(attr_field) emitter.emit_undefined_attr_check(rtype, attr_expr, '!=') emitter.emit_dec_ref('self->{}'.format(attr_field), rtype) emitter.emit_line('}') if deletable: emitter.emit_line('if (value != NULL) {') if rtype.is_unboxed: emitter.emit_unbox('value', 'tmp', rtype, error=ReturnHandler('-1'), declare_dest=True) elif is_same_type(rtype, object_rprimitive): emitter.emit_line('PyObject *tmp = value;') else: emitter.emit_cast('value', 'tmp', rtype, declare_dest=True) emitter.emit_lines('if (!tmp)', ' return -1;') emitter.emit_inc_ref('tmp', rtype) emitter.emit_line('self->{} = tmp;'.format(attr_field)) if deletable: emitter.emit_line('} else') emitter.emit_line(' self->{} = {};'.format( attr_field, emitter.c_undefined_value(rtype))) emitter.emit_line('return 0;') emitter.emit_line('}')
def generate_slots(cl: ClassIR, table: SlotTable, emitter: Emitter) -> Dict[str, str]: fields = OrderedDict() # type: Dict[str, str] # Sort for determinism on Python 3.5 for name, (slot, generator) in sorted(table.items()): method_cls = cl.get_method_and_class(name) if method_cls and (method_cls[1] == cl or name in ALWAYS_FILL): fields[slot] = generator(cl, method_cls[0], emitter) return fields
def generate_side_table_for_class(cl: ClassIR, name: str, type: str, slots: Dict[str, str], emitter: Emitter) -> Optional[str]: name = '{}_{}'.format(cl.name_prefix(emitter.names), name) emitter.emit_line('static {} {} = {{'.format(type, name)) for field, value in slots.items(): emitter.emit_line(".{} = {},".format(field, value)) emitter.emit_line("};") return name
def generate_dealloc_for_class(cl: ClassIR, dealloc_func_name: str, clear_func_name: str, emitter: Emitter) -> None: emitter.emit_line('static void') emitter.emit_line('{}({} *self)'.format(dealloc_func_name, cl.struct_name(emitter.names))) emitter.emit_line('{') emitter.emit_line('PyObject_GC_UnTrack(self);') emitter.emit_line('{}(self);'.format(clear_func_name)) emitter.emit_line('Py_TYPE(self)->tp_free((PyObject *)self);') emitter.emit_line('}')
def build_type_map(mapper: Mapper, modules: List[MypyFile], graph: Graph, types: Dict[Expression, Type], options: CompilerOptions, errors: Errors) -> None: # Collect all classes defined in everything we are compiling classes = [] for module in modules: module_classes = [ node for node in module.defs if isinstance(node, ClassDef) ] classes.extend([(module, cdef) for cdef in module_classes]) # Collect all class mappings so that we can bind arbitrary class name # references even if there are import cycles. for module, cdef in classes: class_ir = ClassIR(cdef.name, module.fullname, is_trait(cdef), is_abstract=cdef.info.is_abstract) class_ir.is_ext_class = is_extension_class(cdef) if class_ir.is_ext_class: class_ir.deletable = cdef.info.deletable_attributes[:] # If global optimizations are disabled, turn of tracking of class children if not options.global_opts: class_ir.children = None mapper.type_to_ir[cdef.info] = class_ir # Populate structural information in class IR for extension classes. for module, cdef in classes: with catch_errors(module.path, cdef.line): if mapper.type_to_ir[cdef.info].is_ext_class: prepare_class_def(module.path, module.fullname, cdef, errors, mapper) else: prepare_non_ext_class_def(module.path, module.fullname, cdef, errors, mapper) # Collect all the functions also. We collect from the symbol table # so that we can easily pick out the right copy of a function that # is conditionally defined. for module in modules: for func in get_module_func_defs(module): prepare_func_def(module.fullname, None, func, mapper)
def compute_vtable(cls: ClassIR) -> None: """Compute the vtable structure for a class.""" if cls.vtable is not None: return if not cls.is_generated: cls.has_dict = any(x.inherits_python for x in cls.mro) for t in cls.mro[1:]: # Make sure all ancestors are processed first compute_vtable(t) # Merge attributes from traits into the class if not t.is_trait: continue for name, typ in t.attributes.items(): if not cls.is_trait and not any(name in b.attributes for b in cls.base_mro): cls.attributes[name] = typ cls.vtable = {} if cls.base: assert cls.base.vtable is not None cls.vtable.update(cls.base.vtable) cls.vtable_entries = specialize_parent_vtable(cls, cls.base) # Include the vtable from the parent classes, but handle method overrides. entries = cls.vtable_entries # Traits need to have attributes in the vtable, since the # attributes can be at different places in different classes, but # regular classes can just directly get them. if cls.is_trait: # Traits also need to pull in vtable entries for non-trait # parent classes explicitly. for t in cls.mro: for attr in t.attributes: if attr in cls.vtable: continue cls.vtable[attr] = len(entries) entries.append(VTableAttr(t, attr, is_setter=False)) entries.append(VTableAttr(t, attr, is_setter=True)) all_traits = [t for t in cls.mro if t.is_trait] for t in [cls] + cls.traits: for fn in itertools.chain(t.methods.values()): # TODO: don't generate a new entry when we overload without changing the type if fn == cls.get_method(fn.name): cls.vtable[fn.name] = len(entries) # If the class contains a glue method referring to itself, that is a # shadow glue method to support interpreted subclasses. shadow = cls.glue_methods.get((cls, fn.name)) entries.append(VTableMethod(t, fn.name, fn, shadow)) # Compute vtables for all of the traits that the class implements if not cls.is_trait: for trait in all_traits: compute_vtable(trait) cls.trait_vtables[trait] = specialize_parent_vtable(cls, trait)
def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> None: emitter.emit_line('static int') emitter.emit_line('{}({} *self)'.format(func_name, cl.struct_name(emitter.names))) emitter.emit_line('{') for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): emitter.emit_gc_clear('self->{}'.format(emitter.attr(attr)), rtype) if cl.has_dict: struct_name = cl.struct_name(emitter.names) # __dict__ lives right after the struct and __weakref__ lives right after that emitter.emit_gc_clear( '*((PyObject **)((char *)self + sizeof({})))'.format(struct_name), object_rprimitive) emitter.emit_gc_clear( '*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'. format(struct_name), object_rprimitive) emitter.emit_line('return 0;') emitter.emit_line('}')
def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> 'ModuleIR': return ModuleIR( data['fullname'], data['imports'], [ ctx.functions[FuncDecl.get_name_from_json(f['decl'])] for f in data['functions'] ], [ClassIR.deserialize(c, ctx) for c in data['classes']], [(k, deserialize_type(t, ctx)) for k, t in data['final_names']], )
def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> None: """Emit function that performs cycle GC traversal of an instance.""" emitter.emit_line('static int') emitter.emit_line('{}({} *self, visitproc visit, void *arg)'.format( func_name, cl.struct_name(emitter.names))) emitter.emit_line('{') for base in reversed(cl.base_mro): for attr, rtype in base.attributes.items(): emitter.emit_gc_visit('self->{}'.format(emitter.attr(attr)), rtype) if cl.has_dict: struct_name = cl.struct_name(emitter.names) # __dict__ lives right after the struct and __weakref__ lives right after that emitter.emit_gc_visit( '*((PyObject **)((char *)self + sizeof({})))'.format(struct_name), object_rprimitive) emitter.emit_gc_visit( '*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'. format(struct_name), object_rprimitive) emitter.emit_line('return 0;') emitter.emit_line('}')