Jit/jit_rt.h (274 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #pragma once #include "Python.h" #include "classloader.h" #include "frameobject.h" namespace jit { class CodeRuntime; } #ifdef __cplusplus extern "C" { #endif typedef enum { JITRT_CALL_KIND_FUNC = 0, JITRT_CALL_KIND_METHOD_DESCR, JITRT_CALL_KIND_METHOD_LIKE, JITRT_CALL_KIND_WRAPPER_DESCR, JITRT_CALL_KIND_OTHER } JITRT_CallMethodKind; typedef struct { PyTypeObject* type; PyObject* value; JITRT_CallMethodKind call_kind; } JITRT_LoadMethodCacheEntry; // static->static call convention for primitive returns is to return error flag // in rdx (null means error occurred); for C helpers that need to implement this // convention, returning this struct will fill the right registers typedef struct { void* rax; void* rdx; } JITRT_StaticCallReturn; typedef struct { double xmm0; double xmm1; } JITRT_StaticCallFPReturn; #define LOAD_METHOD_CACHE_SIZE 4 typedef struct { JITRT_LoadMethodCacheEntry entries[LOAD_METHOD_CACHE_SIZE]; } JITRT_LoadMethodCache; /* * Allocate a new PyFrameObject and link it into the current thread's * call stack. * * Returns the thread state that the freshly allocated frame was linked to * (accessible via ->frame) on success or NULL on error. */ PyThreadState* JITRT_AllocateAndLinkFrame( PyCodeObject* code, PyObject* globals); /* * Helper function to decref a frame. * * Used by JITRT_UnlinkFrame, and designed to only be used separately if * something else has already unlinked the frame. */ void JITRT_DecrefFrame(PyFrameObject* frame); /* * Helper function to unlink a frame. * * Designed to be used in tandem with JITRT_AllocateAndLinkFrame. This checks * if the frame has escaped (> 1 refcount) and tracks it if so. */ void JITRT_UnlinkFrame(PyThreadState* tstate); /* * Handles a call that includes kw arguments or excess tuple arguments */ PyObject* JITRT_CallWithKeywordArgs( PyFunctionObject* func, PyObject** args, size_t nargsf, PyObject* kwnames); JITRT_StaticCallReturn JITRT_CallWithIncorrectArgcount( PyFunctionObject* func, PyObject** args, size_t nargsf, int argcount); JITRT_StaticCallFPReturn JITRT_CallWithIncorrectArgcountFPReturn( PyFunctionObject* func, PyObject** args, size_t nargsf, int argcount); #define JITRT_CALL_REENTRY_OFFSET (-9) #define JITRT_GET_REENTRY(entry) \ ((vectorcallfunc)(((char*)entry) + JITRT_CALL_REENTRY_OFFSET)) /* Helper function to report an error when the arguments aren't correct for * a static function call. Dispatches to the eval loop to let the normal CHECK_ARGS run and then report the error */ PyObject* JITRT_ReportStaticArgTypecheckErrors( PyObject* func, PyObject** args, size_t nargsf, PyObject* kwnames); /* Variation of JITRT_ReportStaticArgTypecheckErrors but also sets the primitive return value error in addition to returning the normal NULL error indicator */ JITRT_StaticCallReturn JITRT_ReportStaticArgTypecheckErrorsWithPrimitiveReturn( PyObject* func, PyObject** args, size_t nargsf, PyObject* kwnames); /* Variation of JITRT_ReportStaticArgTypecheckErrors but also sets the double return value error in addition to returning the normal NULL error indicator */ JITRT_StaticCallFPReturn JITRT_ReportStaticArgTypecheckErrorsWithDoubleReturn( PyObject* func, PyObject** args, size_t nargsf, PyObject* kwnames); /* * Mimics the behavior of _PyDict_LoadGlobal except that it raises an error when * the name does not exist. */ PyObject* JITRT_LoadGlobal(PyObject* globals, PyObject* builtins, PyObject* name); /* * Perform a positional-only function call * * args[0] is expected to point to the callable and args[1] through args[nargs - * 1] are expected to point to the arguments to the call. */ PyObject* JITRT_CallFunction(PyObject* func, PyObject** args, Py_ssize_t nargs); /* * As JITRT_CallFunction but eagerly starts coroutines. */ PyObject* JITRT_CallFunctionAwaited(PyObject* func, PyObject** args, Py_ssize_t nargs); /* * Perform a combined positional and kwargs function call * * args[0] points to the callable and args[1] - args[nargs - 2] are all argument * values, and args[nargs - 1] is a tuple of strings mapping the last * len(args[nargs - 1]) args to named positions. */ PyObject* JITRT_CallFunctionKWArgs(PyObject* func, PyObject** args, Py_ssize_t nargs); /* * As JITRT_CallFunctionKWArgs but eagerly starts coroutines. */ PyObject* JITRT_CallFunctionKWArgsAwaited( PyObject* func, PyObject** args, Py_ssize_t nargs); /* * Helper to perform a Python call with dynamically determined arguments. * * pargs will be a possibly empty tuple of positional arguments, kwargs will be * null or a dictionary of keyword arguments. */ PyObject* JITRT_CallFunctionEx(PyObject* func, PyObject* pargs, PyObject* kwargs); /* * As JITRT_CallFunctionEx but eagerly starts coroutines. */ PyObject* JITRT_CallFunctionExAwaited(PyObject* func, PyObject* pargs, PyObject* kwargs); /* * Perform a positional-only function call. * * This is designed to be used in tandem with JITRT_GetMethod to optimize * calls that look like instance method calls (e.g. `self.foo()`) to avoid the * creation of bound methods. * * args[0] is expected to point to the receiver of the method lookup (e.g. * `self` in the example above) args[1] through args[nargs - 1] are expected to * point to the arguments to the call. * * call_kind indicates the type of thing being called: * * - JITRT_CALL_KIND_FUNC - We're calling a PyFunctionObject that was * returned instead of creating a bound method. * - JITRT_CALL_KIND_CFUNC - We're calling a C function (PyMethodDef) that was * returned instead of creating a bound method. * - JITRT_CALL_KIND_OTHER - We're calling something else. Bound method * creation was not deferred. */ PyObject* JITRT_CallMethod( PyObject* callable, PyObject** args, Py_ssize_t nargs, PyObject* kwnames, JITRT_CallMethodKind call_kind); /* * As JITRT_CallMethod but eagerly starts coroutines. */ PyObject* JITRT_CallMethodAwaited( PyObject* callable, PyObject** args, Py_ssize_t nargs, PyObject* kwnames, JITRT_CallMethodKind call_kind); /* * Perform an attribute lookup. * * This is used to avoid bound method creation for attribute lookups that * correspond to method calls (e.g. `self.foo()`). * * call_kind indicates whether or not bound method creation was deferred. */ PyObject* JITRT_GetMethod( PyObject* obj, PyObject* name, JITRT_LoadMethodCache* cache, JITRT_CallMethodKind* call_kind); /* * Perform an attribute lookup in a super class * * This is used to avoid bound method creation for attribute lookups that * correspond to method calls (e.g. `self.foo()`). * * call_kind indicates whether or not bound method creation was deferred. */ PyObject* JITRT_GetMethodFromSuper( PyObject* global_super, PyObject* type, PyObject* self, PyObject* name, bool no_args_in_super_call, JITRT_CallMethodKind* call_kind); /* * Perform an attribute lookup in a super class */ PyObject* JITRT_GetAttrFromSuper( PyObject* super_globals, PyObject* type, PyObject* self, PyObject* name, bool no_args_in_super_call); // dealloc a PyObject object void JITRT_Dealloc(PyObject* obj); /* * Mimics the behavior of the UNARY_NOT opcode. * * Checks if value is truthy, and returns Py_False if it is, or Py_True if * it's not. Returns NULL if the object doesn't support truthyness. */ PyObject* JITRT_UnaryNot(PyObject* value); void JITRT_InitLoadMethodCache(JITRT_LoadMethodCache* cache); /* * Invokes a function stored within the method table for the object. * The method table lives off tp_cache in the type object */ PyObject* JITRT_InvokeMethod( Py_ssize_t slot, PyObject** args, Py_ssize_t nargs, PyObject* kwnames); /* * Invokes a function stored within the method table for the object. * The method table lives off tp_cache of self. */ PyObject* JITRT_InvokeClassMethod( Py_ssize_t slot, PyObject** args, Py_ssize_t nargs, PyObject* kwnames); /* * Invokes a function that was compiled statically. */ PyObject* JITRT_InvokeFunction(PyObject* func, PyObject** args, Py_ssize_t nargs); /* * As JITRT_InvokeFunction but eagerly starts coroutines. */ PyObject* JITRT_InvokeFunctionAwaited(PyObject* func, PyObject** args, Py_ssize_t nargs); /* * Loads an indirect function, optionally loading it from the descriptor * if the indirect cache fails. */ PyObject* JITRT_LoadFunctionIndirect(PyObject** func, PyObject* descr); /* * Performs a type check on an object, returning False if the object is * not an instance of the specified type. The type check is a real type * check which doesn't support dynamic behaviors against the type or * proxy behaviors against obj.__class__ */ PyObject* JITRT_TypeCheck(PyObject* obj, PyTypeObject* type); PyObject* JITRT_TypeCheckExact(PyObject* obj, PyTypeObject* type); /* * Performs a type check on an object, returning False if the object is * not an instance of the specified type. The type check is a real type * check which doesn't support dynamic behaviors against the type or * proxy behaviors against obj.__class__ */ PyObject* JITRT_TypeCheckOptional(PyObject* obj, PyTypeObject* type); PyObject* JITRT_TypeCheckOptionalExact(PyObject* obj, PyTypeObject* type); /* * Performs a type check on an object, returning False if the object is * not an instance of the specified type. This case requires extra work * because Python typing pretends int is a subtype of float, so CAST * needs to check two types. */ PyObject* JITRT_TypeCheckFloat(PyObject* obj); /* * Performs a type check on an object, returning False if the object is * not an instance of the specified type. This case requires extra work * because Python typing pretends int is a subtype of float, so CAST * needs to check two types. */ PyObject* JITRT_TypeCheckFloatOptional(PyObject* obj); /* * Performs a type check on an object, raising an error if the object is * not an instance of the specified type. The type check is a real type * check which doesn't support dynamic behaviors against the type or * proxy behaviors against obj.__class__ */ PyObject* JITRT_Cast(PyObject* obj, PyTypeObject* type); /* * JITRT_Cast when target type is float. This case requires extra work * because Python typing pretends int is a subtype of float, so CAST * needs to coerce int to float. */ PyObject* JITRT_CastToFloat(PyObject* obj); /* * JITRT_CastToFloat but with None allowed. */ PyObject* JITRT_CastToFloatOptional(PyObject* obj); /* * Performs a type check on an object, raising an error if the object is * not an instance of the specified type or None. The type check is a * real type check which doesn't support dynamic behaviors against the * type or proxy behaviors against obj.__class__. */ PyObject* JITRT_CastOptional(PyObject* obj, PyTypeObject* type); /* Performs a type check on obj, but does not allow passing a subclass of type. */ PyObject* JITRT_CastExact(PyObject* obj, PyTypeObject* type); PyObject* JITRT_CastOptionalExact(PyObject* obj, PyTypeObject* type); /* Helper methods to implement left shift, which wants its operand in cl */ int64_t JITRT_ShiftLeft64(int64_t x, int64_t y); int32_t JITRT_ShiftLeft32(int32_t x, int32_t y); /* Helper methods to implement right shift, which wants its operand in cl */ int64_t JITRT_ShiftRight64(int64_t x, int64_t y); int32_t JITRT_ShiftRight32(int32_t x, int32_t y); /* Helper methods to implement unsigned right shift, which wants its operand in * cl */ uint64_t JITRT_ShiftRightUnsigned64(uint64_t x, uint64_t y); uint32_t JITRT_ShiftRightUnsigned32(uint32_t x, uint32_t y); /* Helper methods to implement signed modulus */ int64_t JITRT_Mod64(int64_t x, int64_t y); int32_t JITRT_Mod32(int32_t x, int32_t y); /* Helper methods to implement unsigned modulus */ uint64_t JITRT_ModUnsigned64(uint64_t x, uint64_t y); uint32_t JITRT_ModUnsigned32(uint32_t x, uint32_t y); PyObject* JITRT_BoxI32(int32_t i); PyObject* JITRT_BoxU32(uint32_t i); PyObject* JITRT_BoxBool(uint32_t i); PyObject* JITRT_BoxI64(int64_t i); PyObject* JITRT_BoxU64(uint64_t i); PyObject* JITRT_BoxDouble(double_t d); PyObject* JITRT_BoxEnum(int64_t i, uint64_t t); uint64_t JITRT_IsNegativeAndErrOccurred_64(int64_t i); uint64_t JITRT_IsNegativeAndErrOccurred_32(int32_t i); double JITRT_PowerDouble(double x, double y); double JITRT_Power32(int32_t x, int32_t y); double JITRT_PowerUnsigned32(uint32_t x, uint32_t y); double JITRT_Power64(int64_t x, int64_t y); double JITRT_PowerUnsigned64(uint64_t x, uint64_t y); /* Array lookup helpers */ uint64_t JITRT_GetI8_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetU8_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetI16_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetU16_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetI32_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetU32_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetI64_FromArray(char* arr, int64_t idx, ssize_t offset); uint64_t JITRT_GetU64_FromArray(char* arr, int64_t idx, ssize_t offset); PyObject* JITRT_GetObj_FromArray(char* arr, int64_t idx, ssize_t offset); /* Array set helpers */ void JITRT_SetI8_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetU8_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetI16_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetU16_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetI32_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetU32_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetI64_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetU64_InArray(char* arr, uint64_t val, int64_t idx); void JITRT_SetObj_InArray(char* arr, uint64_t val, int64_t idx); uint64_t JITRT_UnboxU64(PyObject* obj); uint32_t JITRT_UnboxU32(PyObject* obj); uint16_t JITRT_UnboxU16(PyObject* obj); uint8_t JITRT_UnboxU8(PyObject* obj); int64_t JITRT_UnboxI64(PyObject* obj); int32_t JITRT_UnboxI32(PyObject* obj); int16_t JITRT_UnboxI16(PyObject* obj); int8_t JITRT_UnboxI8(PyObject* obj); int64_t JITRT_UnboxEnum(PyObject* obj); /* * Calls __builtins__.__import__(), with a fast-path if this hasn't been * overridden. * * This is a near verbatim copy of import_name() from ceval.c with minor * tweaks. We copy rather than expose to avoid making changes to ceval.c. */ PyObject* JITRT_ImportName( PyThreadState* tstate, PyObject* name, PyObject* fromlist, PyObject* level); /* * Wrapper around _Py_DoRaise() which handles the case where we re-raise but no * active exception is set. */ void JITRT_DoRaise(PyThreadState* tstate, PyObject* exc, PyObject* cause); /* * Frees JIT-specific suspend data allocated in JITRT_MakeGenObject(). */ void JITRT_GenJitDataFree(PyGenObject* gen); /* * Formats a f-string value */ PyObject* JITRT_FormatValue( PyThreadState* tstate, PyObject* fmt_spec, PyObject* value, int conversion); /* * Concatenate strings from args */ PyObject* JITRT_BuildString( void* /*unused*/, PyObject** args, size_t nargsf, void* /*unused*/); // Per-function entry point function to resume a JIT generator. Arguments are: // - Generator instance to be resumed. // - A value to send in or NULL to raise the current global error on resume. // - The current thread-state instance. // Returns result of computation which is a "yielded" value unless the state of // the generator is _PyJITGenState_Completed, in which case it is a "return" // value. If the return is NULL, an exception has been raised. typedef PyObject* (*GenResumeFunc)( PyObject* gen, PyObject* send_value, PyThreadState* tstate, uint64_t finish_yield_from); /* * Create generator instance for use during InitialYield in a JIT generator. * There is a variant for each of the different types of generator: iterators, * coroutines, and async generators. */ PyObject* JITRT_MakeGenObject( GenResumeFunc resume_entry, PyThreadState* tstate, size_t spill_words, jit::CodeRuntime* code_rt, PyCodeObject* code); PyObject* JITRT_MakeGenObjectAsyncGen( GenResumeFunc resume_entry, PyThreadState* tstate, size_t spill_words, jit::CodeRuntime* code_rt, PyCodeObject* code); PyObject* JITRT_MakeGenObjectCoro( GenResumeFunc resume_entry, PyThreadState* tstate, size_t spill_words, jit::CodeRuntime* code_rt, PyCodeObject* code); // Set the awaiter of the given awaitable to be the coroutine at the top of // `ts`. void JITRT_SetCurrentAwaiter(PyObject* awaitable, PyThreadState* ts); // Mostly the same implementation as YIELD_FROM in ceval.c with slight tweaks to // make it stand alone. The argument 'v' is stolen. // // The arguments 'gen', 'v', 'tstate', 'finish_yield_from' must match positions // with JIT resume entry function (GenResumeFunc) so registers with their values // pass straight through. struct JITRT_YieldFromRes { PyObject* retval; uint64_t done; }; JITRT_YieldFromRes JITRT_YieldFrom( PyObject* gen, PyObject* v, PyThreadState* tstate, uint64_t finish_yield_from); /* Unpack a sequence as in unpack_iterable(), and save the * results in a tuple. */ PyObject* JITRT_UnpackExToTuple( PyThreadState* tstate, PyObject* iterable, int before, int after); JITRT_StaticCallReturn JITRT_CompileFunction(PyFunctionObject* func, PyObject** args, bool* compiled); JITRT_StaticCallReturn JITRT_CallStaticallyWithPrimitiveSignature( PyFunctionObject* func, PyObject** args, size_t nargsf, PyObject* kwnames, _PyTypedArgsInfo* arg_info); JITRT_StaticCallFPReturn JITRT_CallStaticallyWithPrimitiveSignatureFP( PyFunctionObject* func, PyObject** args, size_t nargsf, PyObject* kwnames, _PyTypedArgsInfo* arg_info); /* Compares if one unicode object is equal to another object. * At least one of the objects has to be exactly a unicode * object. */ int JITRT_UnicodeEquals(PyObject* s1, PyObject* s2, int equals); /* Inverse form of PySequence_Contains for "not in" */ int JITRT_NotContains(PyObject* w, PyObject* v); /* Perform a rich comparison with integer result. This wraps PyObject_RichCompare(), returning -1 for error, 0 for false, 1 for true. Unlike PyObject_RichCompareBool this doesn't perform an object equality check, which is incompatible w/ float comparisons. */ int JITRT_RichCompareBool(PyObject* v, PyObject* w, int op); /* perform a batch decref to the objects in args */ void JITRT_BatchDecref(PyObject** args, int nargs); #ifdef __cplusplus } #endif