def transform_assert_stmt(builder: IRBuilder, a: AssertStmt) -> None: if builder.options.strip_asserts: return cond = builder.accept(a.expr) ok_block, error_block = BasicBlock(), BasicBlock() builder.add_bool_branch(cond, ok_block, error_block) builder.activate_block(error_block) if a.msg is None: # Special case (for simpler generated code) builder.add( RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, None, a.line)) elif isinstance(a.msg, StrExpr): # Another special case builder.add( RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, a.msg.value, a.line)) else: # The general case -- explicitly construct an exception instance message = builder.accept(a.msg) exc_type = builder.load_module_attr_by_fullname( 'builtins.AssertionError', a.line) exc = builder.py_call(exc_type, [message], a.line) builder.call_c(raise_exception_op, [exc], a.line) builder.add(Unreachable()) builder.activate_block(ok_block)
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generate the '__get__' method for a callable class.""" line = fn_info.fitem.line with builder.enter_method(fn_info.callable_class.ir, '__get__', object_rprimitive, fn_info, self_type=object_rprimitive): instance = builder.add_argument('instance', object_rprimitive) builder.add_argument('owner', object_rprimitive) # If accessed through the class, just return the callable # object. If accessed through an object, create a new bound # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op(builder.read(instance), builder.none_object(), 'is', line) builder.add_bool_branch(comparison, class_block, instance_block) builder.activate_block(class_block) builder.add(Return(builder.self())) builder.activate_block(instance_block) builder.add( Return( builder.call_c( method_new_op, [builder.self(), builder.read(instance)], line)))
def setup_non_ext_dict(builder: IRBuilder, cdef: ClassDef, metaclass: Value, bases: Value) -> Value: """Initialize the class dictionary for a non-extension class. This class dictionary is passed to the metaclass constructor. """ # Check if the metaclass defines a __prepare__ method, and if so, call it. has_prepare = builder.call_c(py_hasattr_op, [metaclass, builder.load_str('__prepare__')], cdef.line) non_ext_dict = Register(dict_rprimitive) true_block, false_block, exit_block, = BasicBlock(), BasicBlock(), BasicBlock() builder.add_bool_branch(has_prepare, true_block, false_block) builder.activate_block(true_block) cls_name = builder.load_str(cdef.name) prepare_meth = builder.py_get_attr(metaclass, '__prepare__', cdef.line) prepare_dict = builder.py_call(prepare_meth, [cls_name, bases], cdef.line) builder.assign(non_ext_dict, prepare_dict, cdef.line) builder.goto(exit_block) builder.activate_block(false_block) builder.assign(non_ext_dict, builder.call_c(dict_new_op, [], cdef.line), cdef.line) builder.goto(exit_block) builder.activate_block(exit_block) return non_ext_dict
def faster_min_max(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: if expr.arg_kinds == [ARG_POS, ARG_POS]: x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1]) result = Register(builder.node_type(expr)) # CPython evaluates arguments reversely when calling min(...) or max(...) if callee.fullname == 'builtins.min': comparison = builder.binary_op(y, x, '<', expr.line) else: comparison = builder.binary_op(y, x, '>', expr.line) true_block, false_block, next_block = BasicBlock(), BasicBlock( ), BasicBlock() builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(true_block) builder.assign(result, builder.coerce(y, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(false_block) builder.assign(result, builder.coerce(x, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(next_block) return result return None
def gen_calls_to_correct_impl( builder: IRBuilder, impl_to_use: Value, arg_info: ArgInfo, fitem: FuncDef, line: int, ) -> None: current_func_decl = builder.mapper.func_to_decl[fitem] def gen_native_func_call_and_return(fdef: FuncDef) -> None: func_decl = builder.mapper.func_to_decl[fdef] ret_val = builder.builder.call(func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.add(Return(coerced)) typ, src = builtin_names['builtins.int'] int_type_obj = builder.add(LoadAddress(typ, src, line)) is_int = builder.builder.type_is_op(impl_to_use, int_type_obj, line) native_call, non_native_call = BasicBlock(), BasicBlock() builder.add_bool_branch(is_int, native_call, non_native_call) builder.activate_block(native_call) passed_id = builder.add(Unbox(impl_to_use, int_rprimitive, line)) native_ids = get_native_impl_ids(builder, fitem) for impl, i in native_ids.items(): call_impl, next_impl = BasicBlock(), BasicBlock() current_id = builder.load_int(i) builder.builder.compare_tagged_condition( passed_id, current_id, '==', call_impl, next_impl, line, ) # Call the registered implementation builder.activate_block(call_impl) gen_native_func_call_and_return(impl) builder.activate_block(next_impl) # We've already handled all the possible integer IDs, so we should never get here builder.add(Unreachable()) builder.activate_block(non_native_call) ret_val = builder.py_call(impl_to_use, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.add(Return(coerced))
def populate_switch_for_generator_class(builder: IRBuilder) -> None: cls = builder.fn_info.generator_class line = builder.fn_info.fitem.line builder.activate_block(cls.switch_block) for label, true_block in enumerate(cls.continuation_blocks): false_block = BasicBlock() comparison = builder.binary_op(cls.next_label_reg, builder.add(LoadInt(label)), '==', line) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(false_block) builder.add( RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line)) builder.add(Unreachable())
def add_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int) -> None: """Add error handling blocks to a generator class. Generates blocks to check if error flags are set while calling the helper method for generator functions, and raises an exception if those flags are set. """ cls = builder.fn_info.generator_class assert cls.exc_regs is not None exc_type, exc_val, exc_tb = cls.exc_regs # Check to see if an exception was raised. error_block = BasicBlock() ok_block = BasicBlock() comparison = builder.translate_is_op(exc_type, builder.none_object(), 'is not', line) builder.add_bool_branch(comparison, error_block, ok_block) builder.activate_block(error_block) builder.call_c(raise_exception_with_tb_op, [exc_type, exc_val, exc_tb], line) builder.add(Unreachable()) builder.goto_and_activate(ok_block)
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generate the '__get__' method for a callable class.""" line = fn_info.fitem.line builder.enter(fn_info) vself = builder.read( builder.environment.add_local_reg(Var(SELF_NAME), object_rprimitive, True)) instance = builder.environment.add_local_reg(Var('instance'), object_rprimitive, True) builder.environment.add_local_reg(Var('owner'), object_rprimitive, True) # If accessed through the class, just return the callable # object. If accessed through an object, create a new bound # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op(builder.read(instance), builder.none_object(), 'is', line) builder.add_bool_branch(comparison, class_block, instance_block) builder.activate_block(class_block) builder.add(Return(vself)) builder.activate_block(instance_block) builder.add( Return( builder.call_c(method_new_op, [vself, builder.read(instance)], line))) blocks, env, _, fn_info = builder.leave() sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), RuntimeArg('instance', object_rprimitive), RuntimeArg('owner', object_rprimitive)), object_rprimitive) get_fn_decl = FuncDecl('__get__', fn_info.callable_class.ir.name, builder.module_name, sig) get_fn_ir = FuncIR(get_fn_decl, blocks, env) fn_info.callable_class.ir.methods['__get__'] = get_fn_ir builder.functions.append(get_fn_ir)
def generate_singledispatch_dispatch_function( builder: IRBuilder, main_singledispatch_function_name: str, fitem: FuncDef, ) -> None: line = fitem.line current_func_decl = builder.mapper.func_to_decl[fitem] arg_info = get_args(builder, current_func_decl.sig.args, line) dispatch_func_obj = builder.self() arg_type = builder.builder.get_type_of_obj(arg_info.args[0], line) dispatch_cache = builder.builder.get_attr(dispatch_func_obj, 'dispatch_cache', dict_rprimitive, line) call_find_impl, use_cache, call_func = BasicBlock(), BasicBlock( ), BasicBlock() get_result = builder.call_c(dict_get_method_with_none, [dispatch_cache, arg_type], line) is_not_none = builder.translate_is_op(get_result, builder.none_object(), 'is not', line) impl_to_use = Register(object_rprimitive) builder.add_bool_branch(is_not_none, use_cache, call_find_impl) builder.activate_block(use_cache) builder.assign(impl_to_use, get_result, line) builder.goto(call_func) builder.activate_block(call_find_impl) find_impl = builder.load_module_attr_by_fullname('functools._find_impl', line) registry = load_singledispatch_registry(builder, dispatch_func_obj, line) uncached_impl = builder.py_call(find_impl, [arg_type, registry], line) builder.call_c(dict_set_item_op, [dispatch_cache, arg_type, uncached_impl], line) builder.assign(impl_to_use, uncached_impl, line) builder.goto(call_func) builder.activate_block(call_func) gen_calls_to_correct_impl(builder, impl_to_use, arg_info, fitem, line)
def generate_singledispatch_dispatch_function( builder: IRBuilder, main_singledispatch_function_name: str, fitem: FuncDef, ) -> None: impls = builder.singledispatch_impls[fitem] line = fitem.line current_func_decl = builder.mapper.func_to_decl[fitem] arg_info = get_args(builder, current_func_decl.sig.args, line) def gen_func_call_and_return( func_name: str, fdef: FuncDef, fullname: Optional[str] = None ) -> None: if is_decorated(builder, fdef): func = load_func(builder, func_name, fullname, line) ret_val = builder.builder.py_call( func, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names ) else: func_decl = builder.mapper.func_to_decl[fdef] ret_val = builder.builder.call( func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line ) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.nonlocal_control[-1].gen_return(builder, coerced, line) # Add all necessary imports of other modules that have registered functions in other modules # We're doing this in a separate pass over the implementations because that avoids the # complexity and code size implications of generating this import before every call to a # registered implementation that might need this imported for _, impl in impls: if not is_decorated(builder, impl): continue module_name = impl.fullname.rsplit('.')[0] if module_name not in builder.imports: # We need to generate an import here because the module needs to be imported before we # try loading the function from it builder.gen_import(module_name, line) # Sort the list of implementations so that we check any subclasses before we check the classes # they inherit from, to better match singledispatch's behavior of going through the argument's # MRO, and using the first implementation it finds for dispatch_type, impl in sort_with_subclasses_first(impls): call_impl, next_impl = BasicBlock(), BasicBlock() should_call_impl = check_if_isinstance(builder, arg_info.args[0], dispatch_type, line) builder.add_bool_branch(should_call_impl, call_impl, next_impl) # Call the registered implementation builder.activate_block(call_impl) # The shortname of a function is just '{class}.{func_name}', and we don't support # singledispatchmethod yet, so that is always the same as the function name name = short_id_from_name(impl.name, impl.name, impl.line) gen_func_call_and_return(name, impl, fullname=impl.fullname) builder.activate_block(next_impl) # We don't pass fullname here because getting the fullname of the main generated singledispatch # function isn't easy, and we don't need it because the fullname is only needed for making sure # we load the function from another module instead of the globals dict if it's defined in # another module, which will never be true for the main singledispatch function (it's always # generated in the same module as the dispatch function) gen_func_call_and_return(main_singledispatch_function_name, fitem)