def setup_func_for_recursive_call(builder: IRBuilder, fdef: FuncDef, base: ImplicitClass) -> None: """Enable calling a nested function (with a callable class) recursively. Adds the instance of the callable class representing the given FuncDef to a register in the environment so that the function can be called recursively. Note that this needs to be done only for nested functions. """ # First, set the attribute of the environment class so that GetAttr can be called on it. prev_env = builder.fn_infos[-2].env_class prev_env.attributes[fdef.name] = builder.type_to_rtype(fdef.type) if isinstance(base, GeneratorClass): # If we are dealing with a generator class, then we need to first get the register # holding the current environment class, and load the previous environment class from # there. prev_env_reg = builder.add( GetAttr(base.curr_env_reg, ENV_ATTR_NAME, -1)) else: prev_env_reg = base.prev_env_reg # Obtain the instance of the callable class representing the FuncDef, and add it to the # current environment. val = builder.add(GetAttr(prev_env_reg, fdef.name, -1)) target = builder.environment.add_local_reg(fdef, object_rprimitive) builder.assign(target, val, -1)
def gen_glue_property(builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int, do_pygetattr: bool) -> FuncIR: """Generate glue methods for properties that mediate between different subclass types. Similarly to methods, properties of derived types can be covariantly subtyped. Thus, properties also require glue. However, this only requires the return type to change. Further, instead of a method call, an attribute get is performed. If do_pygetattr is True, then get the attribute using the Python C API instead of a native call. """ builder.enter() rt_arg = RuntimeArg(SELF_NAME, RInstance(cls)) arg = builder.read(add_self_to_env(builder.environment, cls), line) builder.ret_types[-1] = sig.ret_type if do_pygetattr: retval = builder.py_get_attr(arg, target.name, line) else: retval = builder.add(GetAttr(arg, target.name, line)) retbox = builder.coerce(retval, sig.ret_type, line) builder.add(Return(retbox)) blocks, env, return_type, _ = builder.leave() return FuncIR( FuncDecl(target.name + '__' + base.name + '_glue', cls.name, builder.module_name, FuncSignature([rt_arg], return_type)), blocks, env)
def test_get_attr_non_refcounted(self) -> None: self.assert_emit( GetAttr(self.r, 'x', 1), """cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_x; if (unlikely(cpy_r_r0 == 2)) { PyErr_SetString(PyExc_AttributeError, "attribute 'x' of 'A' undefined"); } """)
def gen_method_call( self, base: Value, name: str, arg_values: List[Value], result_type: Optional[RType], line: int, arg_kinds: Optional[List[int]] = None, arg_names: Optional[List[Optional[str]]] = None) -> Value: """Generate either a native or Python method call.""" # If arg_kinds contains values other than arg_pos and arg_named, then fallback to # Python method call. if (arg_kinds is not None and not all(kind in (ARG_POS, ARG_NAMED) for kind in arg_kinds)): return self.py_method_call(base, name, arg_values, base.line, arg_kinds, arg_names) # If the base type is one of ours, do a MethodCall if (isinstance(base.type, RInstance) and base.type.class_ir.is_ext_class and not base.type.class_ir.builtin_base): if base.type.class_ir.has_method(name): decl = base.type.class_ir.method_decl(name) if arg_kinds is None: assert arg_names is None, "arg_kinds not present but arg_names is" arg_kinds = [ARG_POS for _ in arg_values] arg_names = [None for _ in arg_values] else: assert arg_names is not None, "arg_kinds present but arg_names is not" # Normalize args to positionals. assert decl.bound_sig arg_values = self.native_args_to_positional( arg_values, arg_kinds, arg_names, decl.bound_sig, line) return self.add(MethodCall(base, name, arg_values, line)) elif base.type.class_ir.has_attr(name): function = self.add(GetAttr(base, name, line)) return self.py_call(function, arg_values, line, arg_kinds=arg_kinds, arg_names=arg_names) elif isinstance(base.type, RUnion): return self.union_method_call(base, base.type, name, arg_values, result_type, line, arg_kinds, arg_names) # Try to do a special-cased method call if not arg_kinds or arg_kinds == [ARG_POS] * len(arg_values): target = self.translate_special_method_call( base, name, arg_values, result_type, line) if target: return target # Fall back to Python method call return self.py_method_call(base, name, arg_values, line, arg_kinds, arg_names)
def get_attr(self, obj: Value, attr: str, result_type: RType, line: int) -> Value: """Get a native or Python attribute of an object.""" if (isinstance(obj.type, RInstance) and obj.type.class_ir.is_ext_class and obj.type.class_ir.has_attr(attr)): return self.add(GetAttr(obj, attr, line)) elif isinstance(obj.type, RUnion): return self.union_get_attr(obj, obj.type, attr, result_type, line) else: return self.py_get_attr(obj, attr, line)
def test_get_attr(self) -> None: self.assert_emit( GetAttr(self.r, 'y', 1), """cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y; if (unlikely(((mod___AObject *)cpy_r_r)->_y == CPY_INT_TAG)) { PyErr_SetString(PyExc_AttributeError, "attribute 'y' of 'A' undefined"); } else { CPyTagged_IncRef(((mod___AObject *)cpy_r_r)->_y); } """)
def test_get_attr_merged(self) -> None: op = GetAttr(self.r, 'y', 1) branch = Branch(op, BasicBlock(8), BasicBlock(9), Branch.IS_ERROR) branch.traceback_entry = ('foobar', 123) self.assert_emit(op, """\ cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_y; if (unlikely(cpy_r_r0 == CPY_INT_TAG)) { CPy_AttributeError("prog.py", "foobar", "A", "y", 123, CPyStatic_prog___globals); goto CPyL8; } CPyTagged_INCREF(cpy_r_r0); goto CPyL9; """, next_branch=branch)
def get_default() -> Value: assert arg.initializer is not None # If it is constant, don't bother storing it if is_constant(arg.initializer): return builder.accept(arg.initializer) # Because gen_arg_defaults runs before calculate_arg_defaults, we # add the static/attribute to final_names/the class here. elif not builder.fn_info.is_nested: name = fitem.fullname + '.' + arg.variable.name builder.final_names.append((name, target.type)) return builder.add(LoadStatic(target.type, name, builder.module_name)) else: name = arg.variable.name builder.fn_info.callable_class.ir.attributes[name] = target.type return builder.add( GetAttr(builder.fn_info.callable_class.self_reg, name, arg.line))
def read(self, target: Union[Value, AssignmentTarget], line: int = -1) -> Value: if isinstance(target, Value): return target if isinstance(target, AssignmentTargetRegister): return target.register if isinstance(target, AssignmentTargetIndex): reg = self.gen_method_call( target.base, '__getitem__', [target.index], target.type, line) if reg is not None: return reg assert False, target.base.type if isinstance(target, AssignmentTargetAttr): if isinstance(target.obj.type, RInstance) and target.obj.type.class_ir.is_ext_class: return self.add(GetAttr(target.obj, target.attr, line)) else: return self.py_get_attr(target.obj, target.attr, line) assert False, 'Unsupported lvalue: %r' % target
def load_outer_env(builder: IRBuilder, base: Value, outer_env: Environment) -> Value: """Loads the environment class for a given base into a register. Additionally, iterates through all of the SymbolNode and AssignmentTarget instances of the environment at the given index's symtable, and adds those instances to the environment of the current environment. This is done so that the current environment can access outer environment variables without having to reload all of the environment registers. Returns the register where the environment class was loaded. """ env = builder.add(GetAttr(base, ENV_ATTR_NAME, builder.fn_info.fitem.line)) assert isinstance(env.type, RInstance), '{} must be of type RInstance'.format(env) for symbol, target in outer_env.symtable.items(): env.type.class_ir.attributes[symbol.name] = target.type symbol_target = AssignmentTargetAttr(env, symbol.name) builder.environment.add_target(symbol, symbol_target) return env
def test_get_attr(self) -> None: self.assert_emit( GetAttr(self.r, 'y', 1), """cpy_r_r0 = native_A_gety((mod___AObject *)cpy_r_r); /* y */""")