Jit/hir/preload.cpp (327 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "Jit/hir/preload.h" #include "Python.h" #include "classloader.h" #include "opcode.h" #include "Jit/bytecode.h" #include "Jit/codegen/gen_asm.h" #include "Jit/hir/optimization.h" #include "Jit/ref.h" #include "Jit/util.h" #include <utility> namespace jit { namespace hir { Type prim_type_to_type(int prim_type) { switch (prim_type) { case TYPED_BOOL: return TCBool; case TYPED_CHAR: case TYPED_INT8: return TCInt8; case TYPED_INT16: return TCInt16; case TYPED_INT32: return TCInt32; case TYPED_INT64: return TCInt64; case TYPED_UINT8: return TCUInt8; case TYPED_UINT16: return TCUInt16; case TYPED_UINT32: return TCUInt32; case TYPED_UINT64: return TCUInt64; case TYPED_OBJECT: return TOptObject; case TYPED_DOUBLE: return TCDouble; case TYPED_ERROR: return TCInt32; default: JIT_CHECK( false, "non-primitive or unsupported Python type: %d", prim_type); break; } } static Type to_jit_type(const PyTypeOpt& pytype_opt) { auto& [pytype, opt, exact] = pytype_opt; if (_PyClassLoader_IsEnum(pytype)) { JIT_CHECK(!opt, "static enums cannot be optional"); return Type::fromEnum(pytype); } int prim_type = _PyClassLoader_GetTypeCode(pytype); if (prim_type == TYPED_OBJECT) { Type type = exact ? Type::fromTypeExact(pytype) : Type::fromType(pytype); if (opt) { type |= TNoneType; } return type; } JIT_CHECK(!opt, "primitive types cannot be optional"); return prim_type_to_type(prim_type); } static PyTypeOpt resolve_type_descr(BorrowedRef<> descr) { int optional, exact; auto type = Ref<PyTypeObject>::steal( _PyClassLoader_ResolveType(descr, &optional, &exact)); return {std::move(type), optional, exact}; } static FieldInfo resolve_field_descr(BorrowedRef<PyTupleObject> descr) { int field_type; Py_ssize_t offset = _PyClassLoader_ResolveFieldOffset(descr, &field_type); JIT_CHECK(offset != -1, "failed to resolve field %s", repr(descr)); return { offset, prim_type_to_type(field_type), PyTuple_GET_ITEM(descr, PyTuple_GET_SIZE(descr) - 1)}; } static void fill_primitive_arg_types_func( BorrowedRef<PyFunctionObject> func, ArgToType& map) { auto prim_args_info = Ref<_PyTypedArgsInfo>::steal(_PyClassLoader_GetTypedArgsInfo( reinterpret_cast<PyCodeObject*>(func->func_code), 1)); for (Py_ssize_t i = 0; i < Py_SIZE(prim_args_info.get()); i++) { BorrowedRef<PyTypeObject> type = prim_args_info->tai_args[i].tai_type; map.emplace( prim_args_info->tai_args[i].tai_argnum, _PyClassLoader_IsEnum(type) ? Type::fromEnum(type) : prim_type_to_type( prim_args_info->tai_args[i].tai_primitive_type)); } } static void fill_primitive_arg_types_builtin( BorrowedRef<> callable, ArgToType& map) { _PyTypedMethodDef* def = _PyClassLoader_GetTypedMethodDef(callable); JIT_CHECK(def != NULL, "expected typed method def"); for (Py_ssize_t i = 0; def->tmd_sig[i] != NULL; i++) { const _Py_SigElement* elem = def->tmd_sig[i]; int code = _Py_SIG_TYPE_MASK(elem->se_argtype); Type typ = prim_type_to_type(code); if (typ <= TPrimitive) { map.emplace(i, typ); } } } static std::unique_ptr<InvokeTarget> resolve_target_descr( BorrowedRef<> descr, int opcode) { auto target = std::make_unique<InvokeTarget>(); PyObject* container; auto callable = Ref<>::steal(_PyClassLoader_ResolveFunction(descr, &container)); JIT_CHECK(callable != nullptr, "unknown invoke target %s", repr(descr)); int coroutine, optional, exact, classmethod; auto return_pytype = Ref<PyTypeObject>::steal(_PyClassLoader_ResolveReturnType( callable, &optional, &exact, &coroutine, &classmethod)); target->container_is_immutable = _PyClassLoader_IsImmutable(container); if (return_pytype != NULL) { if (coroutine) { // TODO properly handle coroutine returns awaitable type target->return_type = TObject; } else { target->return_type = to_jit_type({std::move(return_pytype), optional, exact}); } } target->is_statically_typed = _PyClassLoader_IsStaticCallable(callable); PyMethodDef* def; _PyTypedMethodDef* tmd; if (PyFunction_Check(callable)) { target->is_function = true; } else if ((def = _PyClassLoader_GetMethodDef(callable)) != nullptr) { target->is_builtin = true; target->builtin_c_func = reinterpret_cast<void*>(def->ml_meth); if (def->ml_flags == METH_NOARGS) { target->builtin_expected_nargs = 1; } else if (def->ml_flags == METH_O) { target->builtin_expected_nargs = 2; } else if ((tmd = _PyClassLoader_GetTypedMethodDef(callable))) { target->builtin_returns_error_code = (tmd->tmd_ret == _Py_SIG_ERROR); target->builtin_returns_void = (tmd->tmd_ret == _Py_SIG_VOID); target->builtin_c_func = tmd->tmd_meth; } } target->callable = std::move(callable); if (opcode == INVOKE_METHOD) { target->slot = _PyClassLoader_ResolveMethod(descr); JIT_CHECK(target->slot != -1, "method lookup failed: %s", repr(descr)); } else { // the rest of this only used by INVOKE_FUNCTION currently target->uses_runtime_func = target->is_function && usesRuntimeFunc(target->func()->func_code); if (!target->container_is_immutable) { target->indirect_ptr = _PyClassLoader_GetIndirectPtr(descr, target->callable, container); JIT_CHECK( target->indirect_ptr != NULL, "%s indirect_ptr is null", repr(descr)); } } if (target->is_statically_typed) { if (target->is_function) { fill_primitive_arg_types_func( target->func(), target->primitive_arg_types); } else { fill_primitive_arg_types_builtin( target->callable, target->primitive_arg_types); } } return target; } BorrowedRef<PyFunctionObject> InvokeTarget::func() const { JIT_CHECK(is_function, "not a PyFunctionObject"); return reinterpret_cast<PyFunctionObject*>(callable.get()); } Type Preloader::type(BorrowedRef<> descr) const { return to_jit_type(pyTypeOpt(descr)); } int Preloader::primitiveTypecode(BorrowedRef<> descr) const { return _PyClassLoader_GetTypeCode(pyType(descr)); } BorrowedRef<PyTypeObject> Preloader::pyType(BorrowedRef<> descr) const { auto& [pytype, opt, exact] = pyTypeOpt(descr); JIT_CHECK(!opt, "unexpected optional type"); return pytype; } const PyTypeOpt& Preloader::pyTypeOpt(BorrowedRef<> descr) const { return map_get(types_, descr); } const FieldInfo& Preloader::fieldInfo(BorrowedRef<> descr) const { return map_get(fields_, descr); } const InvokeTarget& Preloader::invokeFunctionTarget(BorrowedRef<> descr) const { return *(map_get(func_targets_, descr)); } const InvokeTarget& Preloader::invokeMethodTarget(BorrowedRef<> descr) const { return *(map_get(meth_targets_, descr)); } Type Preloader::checkArgType(long local_idx) const { return map_get(check_arg_types_, local_idx, TObject); } GlobalCache Preloader::getGlobalCache(BorrowedRef<> name) const { return jit::codegen::NativeGeneratorFactory::runtime()->findGlobalCache( globals_, name); } BorrowedRef<> Preloader::global(int name_idx) const { BorrowedRef<> name = map_get(global_names_, name_idx, nullptr); if (name != nullptr) { GlobalCache cache = getGlobalCache(name); return *(cache.valuePtr()); } return nullptr; } std::unique_ptr<Function> Preloader::makeFunction() const { // We touch refcounts of Python objects here, so must serialize ThreadedCompileSerialize guard; auto irfunc = std::make_unique<Function>(); irfunc->fullname = fullname_; irfunc->setCode(code_); irfunc->globals.reset(globals_); irfunc->prim_args_info.reset(prim_args_info_); irfunc->return_type = return_type_; irfunc->has_primitive_args = has_primitive_args_; irfunc->has_primitive_first_arg = has_primitive_first_arg_; for (auto& [local, pytype_opt] : check_arg_pytypes_) { irfunc->typed_args.emplace_back( local, std::get<0>(pytype_opt), std::get<1>(pytype_opt), std::get<2>(pytype_opt), to_jit_type(pytype_opt)); } return irfunc; } BorrowedRef<> Preloader::constArg(BytecodeInstruction& bc_instr) const { return PyTuple_GET_ITEM(code_->co_consts, bc_instr.oparg()); } void Preloader::preload() { if (code_->co_flags & CO_STATICALLY_COMPILED) { return_type_ = to_jit_type( resolve_type_descr(_PyClassLoader_GetCodeReturnTypeDescr(code_))); } jit::BytecodeInstructionBlock bc_instrs{code_}; for (auto bc_instr : bc_instrs) { switch (bc_instr.opcode()) { case LOAD_GLOBAL: { if (_PyDict_CanWatch(builtins_) && _PyDict_CanWatch(globals_)) { int name_idx = bc_instr.oparg(); BorrowedRef<> name = PyTuple_GET_ITEM(code_->co_names, name_idx); // We can't keep hold of a reference to this cache, it could get // invalidated and freed; we just do this here for the side effect to // make sure the cached value has been loaded and any side effects of // loading it have been exercised. JIT_CHECK(name != nullptr, "name cannot be null"); getGlobalCache(name); global_names_.emplace(name_idx, name); } break; } case CHECK_ARGS: { BorrowedRef<PyTupleObject> checks = reinterpret_cast<PyTupleObject*>(constArg(bc_instr).get()); for (int i = 0; i < PyTuple_GET_SIZE(checks); i += 2) { long local = PyLong_AsLong(PyTuple_GET_ITEM(checks, i)); if (local < 0) { // A negative value for local indicates that it's a cell JIT_CHECK( code_->co_cell2arg != nullptr, "no cell2arg but negative local %ld", local); long arg = code_->co_cell2arg[-1 * (local + 1)]; JIT_CHECK( arg != CO_CELL_NOT_AN_ARG, "cell not an arg for local %ld", local); local = arg; } PyTypeOpt pytype_opt = resolve_type_descr(PyTuple_GET_ITEM(checks, i + 1)); JIT_CHECK( std::get<0>(pytype_opt) != reinterpret_cast<PyTypeObject*>(&PyObject_Type), "shouldn't generate type checks for object"); Type type = to_jit_type(pytype_opt); check_arg_types_.emplace(local, type); check_arg_pytypes_.emplace(local, std::move(pytype_opt)); if (type <= TPrimitive) { has_primitive_args_ = true; if (local == 0) { has_primitive_first_arg_ = true; } } } break; } case CAST: case BUILD_CHECKED_LIST: case BUILD_CHECKED_MAP: { BorrowedRef<> descr = PyTuple_GetItem(constArg(bc_instr), 0); types_.emplace(descr, resolve_type_descr(descr)); break; } case PRIMITIVE_BOX: case PRIMITIVE_UNBOX: case REFINE_TYPE: case TP_ALLOC: { BorrowedRef<> descr = constArg(bc_instr); types_.emplace(descr, resolve_type_descr(descr)); break; } case LOAD_FIELD: case STORE_FIELD: { BorrowedRef<PyTupleObject> descr(constArg(bc_instr)); fields_.emplace(descr, resolve_field_descr(descr)); break; } case INVOKE_FUNCTION: case INVOKE_METHOD: { BorrowedRef<PyObject> descr = PyTuple_GetItem(constArg(bc_instr), 0); auto& map = bc_instr.opcode() == INVOKE_FUNCTION ? func_targets_ : meth_targets_; map.emplace(descr, resolve_target_descr(descr, bc_instr.opcode())); break; } } } if (has_primitive_args_) { prim_args_info_ = Ref<_PyTypedArgsInfo>::steal( _PyClassLoader_GetTypedArgsInfo(code_, true)); } } } // namespace hir } // namespace jit