This commit is contained in:
Waylon Walker 2022-03-31 20:20:07 -05:00
commit 38355d2442
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
9083 changed files with 1225834 additions and 0 deletions

View file

@ -0,0 +1,593 @@
// Mypyc C API
#ifndef CPY_CPY_H
#define CPY_CPY_H
#include <stdbool.h>
#include <Python.h>
#include <frameobject.h>
#include <structmember.h>
#include <assert.h>
#include <stdint.h>
#include "pythonsupport.h"
#include "mypyc_util.h"
#ifdef __cplusplus
extern "C" {
#endif
#if 0
} // why isn't emacs smart enough to not indent this
#endif
#define CPYTHON_LARGE_INT_ERRMSG "Python int too large to convert to C ssize_t"
// Naming conventions:
//
// Tagged: tagged int
// Long: tagged long int (pointer)
// Short: tagged short int (unboxed)
// Ssize_t: A Py_ssize_t, which ought to be the same width as pointers
// Object: CPython object (PyObject *)
// Tuple type definitions needed for API functions
#ifndef MYPYC_DECLARED_tuple_T3OOO
#define MYPYC_DECLARED_tuple_T3OOO
typedef struct tuple_T3OOO {
PyObject *f0;
PyObject *f1;
PyObject *f2;
} tuple_T3OOO;
static tuple_T3OOO tuple_undefined_T3OOO = { NULL, NULL, NULL };
#endif
// Our return tuple wrapper for dictionary iteration helper.
#ifndef MYPYC_DECLARED_tuple_T3CIO
#define MYPYC_DECLARED_tuple_T3CIO
typedef struct tuple_T3CIO {
char f0; // Should continue?
CPyTagged f1; // Last dict offset
PyObject *f2; // Next dictionary key or value
} tuple_T3CIO;
static tuple_T3CIO tuple_undefined_T3CIO = { 2, CPY_INT_TAG, NULL };
#endif
// Same as above but for both key and value.
#ifndef MYPYC_DECLARED_tuple_T4CIOO
#define MYPYC_DECLARED_tuple_T4CIOO
typedef struct tuple_T4CIOO {
char f0; // Should continue?
CPyTagged f1; // Last dict offset
PyObject *f2; // Next dictionary key
PyObject *f3; // Next dictionary value
} tuple_T4CIOO;
static tuple_T4CIOO tuple_undefined_T4CIOO = { 2, CPY_INT_TAG, NULL, NULL };
#endif
// Native object operations
// Search backwards through the trait part of a vtable (which sits *before*
// the start of the vtable proper) looking for the subvtable describing a trait
// implementation. We don't do any bounds checking so we'd better be pretty sure
// we know that it is there.
static inline CPyVTableItem *CPy_FindTraitVtable(PyTypeObject *trait, CPyVTableItem *vtable) {
int i;
for (i = -3; ; i -= 3) {
if ((PyTypeObject *)vtable[i] == trait) {
return (CPyVTableItem *)vtable[i + 1];
}
}
}
// Use the same logic for offset table.
static inline size_t CPy_FindAttrOffset(PyTypeObject *trait, CPyVTableItem *vtable, size_t index) {
int i;
for (i = -3; ; i -= 3) {
if ((PyTypeObject *)vtable[i] == trait) {
return ((size_t *)vtable[i + 2])[index];
}
}
}
// Get attribute value using vtable (may return an undefined value)
#define CPY_GET_ATTR(obj, type, vtable_index, object_type, attr_type) \
((attr_type (*)(object_type *))((object_type *)obj)->vtable[vtable_index])((object_type *)obj)
#define CPY_GET_ATTR_TRAIT(obj, trait, vtable_index, object_type, attr_type) \
((attr_type (*)(object_type *))(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable))[vtable_index])((object_type *)obj)
// Set attribute value using vtable
#define CPY_SET_ATTR(obj, type, vtable_index, value, object_type, attr_type) \
((bool (*)(object_type *, attr_type))((object_type *)obj)->vtable[vtable_index])( \
(object_type *)obj, value)
#define CPY_SET_ATTR_TRAIT(obj, trait, vtable_index, value, object_type, attr_type) \
((bool (*)(object_type *, attr_type))(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable))[vtable_index])( \
(object_type *)obj, value)
#define CPY_GET_METHOD(obj, type, vtable_index, object_type, method_type) \
((method_type)(((object_type *)obj)->vtable[vtable_index]))
#define CPY_GET_METHOD_TRAIT(obj, trait, vtable_index, object_type, method_type) \
((method_type)(CPy_FindTraitVtable(trait, ((object_type *)obj)->vtable)[vtable_index]))
// Int operations
CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value);
CPyTagged CPyTagged_FromObject(PyObject *object);
CPyTagged CPyTagged_StealFromObject(PyObject *object);
CPyTagged CPyTagged_BorrowFromObject(PyObject *object);
PyObject *CPyTagged_AsObject(CPyTagged x);
PyObject *CPyTagged_StealAsObject(CPyTagged x);
Py_ssize_t CPyTagged_AsSsize_t(CPyTagged x);
void CPyTagged_IncRef(CPyTagged x);
void CPyTagged_DecRef(CPyTagged x);
void CPyTagged_XDecRef(CPyTagged x);
CPyTagged CPyTagged_Negate(CPyTagged num);
CPyTagged CPyTagged_Invert(CPyTagged num);
CPyTagged CPyTagged_Add(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Subtract(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Multiply(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_FloorDivide(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Remainder(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_And(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Or(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Xor(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Rshift(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Lshift(CPyTagged left, CPyTagged right);
bool CPyTagged_IsEq_(CPyTagged left, CPyTagged right);
bool CPyTagged_IsLt_(CPyTagged left, CPyTagged right);
PyObject *CPyTagged_Str(CPyTagged n);
PyObject *CPyLong_FromStrWithBase(PyObject *o, CPyTagged base);
PyObject *CPyLong_FromStr(PyObject *o);
PyObject *CPyLong_FromFloat(PyObject *o);
PyObject *CPyBool_Str(bool b);
static inline int CPyTagged_CheckLong(CPyTagged x) {
return x & CPY_INT_TAG;
}
static inline int CPyTagged_CheckShort(CPyTagged x) {
return !CPyTagged_CheckLong(x);
}
static inline void CPyTagged_INCREF(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
CPyTagged_IncRef(x);
}
}
static inline void CPyTagged_DECREF(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
CPyTagged_DecRef(x);
}
}
static inline void CPyTagged_XDECREF(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
CPyTagged_XDecRef(x);
}
}
static inline Py_ssize_t CPyTagged_ShortAsSsize_t(CPyTagged x) {
// NOTE: Assume that we sign extend.
return (Py_ssize_t)x >> 1;
}
static inline PyObject *CPyTagged_LongAsObject(CPyTagged x) {
// NOTE: Assume target is not a short int.
return (PyObject *)(x & ~CPY_INT_TAG);
}
static inline bool CPyTagged_TooBig(Py_ssize_t value) {
// Micro-optimized for the common case where it fits.
return (size_t)value > CPY_TAGGED_MAX
&& (value >= 0 || value < CPY_TAGGED_MIN);
}
static inline bool CPyTagged_IsAddOverflow(CPyTagged sum, CPyTagged left, CPyTagged right) {
// This check was copied from some of my old code I believe that it works :-)
return (Py_ssize_t)(sum ^ left) < 0 && (Py_ssize_t)(sum ^ right) < 0;
}
static inline bool CPyTagged_IsSubtractOverflow(CPyTagged diff, CPyTagged left, CPyTagged right) {
// This check was copied from some of my old code I believe that it works :-)
return (Py_ssize_t)(diff ^ left) < 0 && (Py_ssize_t)(diff ^ right) >= 0;
}
static inline bool CPyTagged_IsMultiplyOverflow(CPyTagged left, CPyTagged right) {
// This is conservative -- return false only in a small number of all non-overflow cases
return left >= (1U << (CPY_INT_BITS/2 - 1)) || right >= (1U << (CPY_INT_BITS/2 - 1));
}
static inline bool CPyTagged_MaybeFloorDivideFault(CPyTagged left, CPyTagged right) {
return right == 0 || left == -((size_t)1 << (CPY_INT_BITS-1));
}
static inline bool CPyTagged_MaybeRemainderFault(CPyTagged left, CPyTagged right) {
// Division/modulus can fault when dividing INT_MIN by -1, but we
// do our mods on still-tagged integers with the low-bit clear, so
// -1 is actually represented as -2 and can't overflow.
// Mod by 0 can still fault though.
return right == 0;
}
static inline bool CPyTagged_IsEq(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left)) {
return left == right;
} else {
return CPyTagged_IsEq_(left, right);
}
}
static inline bool CPyTagged_IsNe(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left)) {
return left != right;
} else {
return !CPyTagged_IsEq_(left, right);
}
}
static inline bool CPyTagged_IsLt(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) {
return (Py_ssize_t)left < (Py_ssize_t)right;
} else {
return CPyTagged_IsLt_(left, right);
}
}
static inline bool CPyTagged_IsGe(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) {
return (Py_ssize_t)left >= (Py_ssize_t)right;
} else {
return !CPyTagged_IsLt_(left, right);
}
}
static inline bool CPyTagged_IsGt(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) {
return (Py_ssize_t)left > (Py_ssize_t)right;
} else {
return CPyTagged_IsLt_(right, left);
}
}
static inline bool CPyTagged_IsLe(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) {
return (Py_ssize_t)left <= (Py_ssize_t)right;
} else {
return !CPyTagged_IsLt_(right, left);
}
}
// Generic operations (that work with arbitrary types)
/* We use intentionally non-inlined decrefs in rarely executed code
* paths since it pretty substantially speeds up compile time. We have
* our own copies both to avoid the null check in Py_DecRef and to avoid
* making an indirect PIC call. */
CPy_NOINLINE
static void CPy_DecRef(PyObject *p) {
CPy_DECREF(p);
}
CPy_NOINLINE
static void CPy_XDecRef(PyObject *p) {
CPy_XDECREF(p);
}
static inline CPyTagged CPyObject_Size(PyObject *obj) {
Py_ssize_t s = PyObject_Size(obj);
if (s < 0) {
return CPY_INT_TAG;
} else {
// Technically __len__ could return a really big number, so we
// should allow this to produce a boxed int. In practice it
// shouldn't ever if the data structure actually contains all
// the elements, but...
return CPyTagged_FromSsize_t(s);
}
}
#ifdef MYPYC_LOG_GETATTR
static void CPy_LogGetAttr(const char *method, PyObject *obj, PyObject *attr) {
PyObject *module = PyImport_ImportModule("getattr_hook");
if (module) {
PyObject *res = PyObject_CallMethodObjArgs(module, method, obj, attr, NULL);
Py_XDECREF(res);
Py_DECREF(module);
}
PyErr_Clear();
}
#else
#define CPy_LogGetAttr(method, obj, attr) (void)0
#endif
// Intercept a method call and log it. This needs to be a macro
// because there is no API that accepts va_args for making a
// call. Worse, it needs to use the comma operator to return the right
// value.
#define CPyObject_CallMethodObjArgs(obj, attr, ...) \
(CPy_LogGetAttr("log_method", (obj), (attr)), \
PyObject_CallMethodObjArgs((obj), (attr), __VA_ARGS__))
// This one is a macro for consistency with the above, I guess.
#define CPyObject_GetAttr(obj, attr) \
(CPy_LogGetAttr("log", (obj), (attr)), \
PyObject_GetAttr((obj), (attr)))
CPyTagged CPyObject_Hash(PyObject *o);
PyObject *CPyObject_GetAttr3(PyObject *v, PyObject *name, PyObject *defl);
PyObject *CPyIter_Next(PyObject *iter);
PyObject *CPyNumber_Power(PyObject *base, PyObject *index);
PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
// List operations
PyObject *CPyList_Build(Py_ssize_t len, ...);
PyObject *CPyList_GetItem(PyObject *list, CPyTagged index);
PyObject *CPyList_GetItemUnsafe(PyObject *list, CPyTagged index);
PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index);
bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value);
bool CPyList_SetItemUnsafe(PyObject *list, CPyTagged index, PyObject *value);
PyObject *CPyList_PopLast(PyObject *obj);
PyObject *CPyList_Pop(PyObject *obj, CPyTagged index);
CPyTagged CPyList_Count(PyObject *obj, PyObject *value);
int CPyList_Insert(PyObject *list, CPyTagged index, PyObject *value);
PyObject *CPyList_Extend(PyObject *o1, PyObject *o2);
int CPyList_Remove(PyObject *list, PyObject *obj);
CPyTagged CPyList_Index(PyObject *list, PyObject *obj);
PyObject *CPySequence_Multiply(PyObject *seq, CPyTagged t_size);
PyObject *CPySequence_RMultiply(CPyTagged t_size, PyObject *seq);
PyObject *CPyList_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
// Dict operations
PyObject *CPyDict_GetItem(PyObject *dict, PyObject *key);
int CPyDict_SetItem(PyObject *dict, PyObject *key, PyObject *value);
PyObject *CPyDict_Get(PyObject *dict, PyObject *key, PyObject *fallback);
PyObject *CPyDict_GetWithNone(PyObject *dict, PyObject *key);
PyObject *CPyDict_SetDefault(PyObject *dict, PyObject *key, PyObject *value);
PyObject *CPyDict_SetDefaultWithNone(PyObject *dict, PyObject *key);
PyObject *CPyDict_SetDefaultWithEmptyDatatype(PyObject *dict, PyObject *key, int data_type);
PyObject *CPyDict_Build(Py_ssize_t size, ...);
int CPyDict_Update(PyObject *dict, PyObject *stuff);
int CPyDict_UpdateInDisplay(PyObject *dict, PyObject *stuff);
int CPyDict_UpdateFromAny(PyObject *dict, PyObject *stuff);
PyObject *CPyDict_FromAny(PyObject *obj);
PyObject *CPyDict_KeysView(PyObject *dict);
PyObject *CPyDict_ValuesView(PyObject *dict);
PyObject *CPyDict_ItemsView(PyObject *dict);
PyObject *CPyDict_Keys(PyObject *dict);
PyObject *CPyDict_Values(PyObject *dict);
PyObject *CPyDict_Items(PyObject *dict);
char CPyDict_Clear(PyObject *dict);
PyObject *CPyDict_Copy(PyObject *dict);
PyObject *CPyDict_GetKeysIter(PyObject *dict);
PyObject *CPyDict_GetItemsIter(PyObject *dict);
PyObject *CPyDict_GetValuesIter(PyObject *dict);
tuple_T3CIO CPyDict_NextKey(PyObject *dict_or_iter, CPyTagged offset);
tuple_T3CIO CPyDict_NextValue(PyObject *dict_or_iter, CPyTagged offset);
tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset);
// Check that dictionary didn't change size during iteration.
static inline char CPyDict_CheckSize(PyObject *dict, CPyTagged size) {
if (!PyDict_CheckExact(dict)) {
// Dict subclasses will be checked by Python runtime.
return 1;
}
Py_ssize_t py_size = CPyTagged_AsSsize_t(size);
Py_ssize_t dict_size = PyDict_Size(dict);
if (py_size != dict_size) {
PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration");
return 0;
}
return 1;
}
// Str operations
PyObject *CPyStr_Build(Py_ssize_t len, ...);
PyObject *CPyStr_GetItem(PyObject *str, CPyTagged index);
PyObject *CPyStr_Split(PyObject *str, PyObject *sep, CPyTagged max_split);
PyObject *CPyStr_Replace(PyObject *str, PyObject *old_substr, PyObject *new_substr, CPyTagged max_replace);
PyObject *CPyStr_Append(PyObject *o1, PyObject *o2);
PyObject *CPyStr_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
bool CPyStr_Startswith(PyObject *self, PyObject *subobj);
bool CPyStr_Endswith(PyObject *self, PyObject *subobj);
bool CPyStr_IsTrue(PyObject *obj);
Py_ssize_t CPyStr_Size_size_t(PyObject *str);
PyObject *CPy_Decode(PyObject *obj, PyObject *encoding, PyObject *errors);
PyObject *CPy_Encode(PyObject *obj, PyObject *encoding, PyObject *errors);
// Bytes operations
PyObject *CPyBytes_Build(Py_ssize_t len, ...);
PyObject *CPyBytes_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
CPyTagged CPyBytes_GetItem(PyObject *o, CPyTagged index);
PyObject *CPyBytes_Concat(PyObject *a, PyObject *b);
PyObject *CPyBytes_Join(PyObject *sep, PyObject *iter);
int CPyBytes_Compare(PyObject *left, PyObject *right);
// Set operations
bool CPySet_Remove(PyObject *set, PyObject *key);
// Tuple operations
PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index);
PyObject *CPySequenceTuple_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
bool CPySequenceTuple_SetItemUnsafe(PyObject *tuple, CPyTagged index, PyObject *value);
// Exception operations
// mypyc is not very good at dealing with refcount management of
// pointers that might be NULL. As a workaround for this, the
// exception APIs that might want to return NULL pointers instead
// return properly refcounted pointers to this dummy object.
struct ExcDummyStruct { PyObject_HEAD };
extern struct ExcDummyStruct _CPy_ExcDummyStruct;
extern PyObject *_CPy_ExcDummy;
static inline void _CPy_ToDummy(PyObject **p) {
if (*p == NULL) {
Py_INCREF(_CPy_ExcDummy);
*p = _CPy_ExcDummy;
}
}
static inline PyObject *_CPy_FromDummy(PyObject *p) {
if (p == _CPy_ExcDummy) return NULL;
Py_INCREF(p);
return p;
}
static int CPy_NoErrOccured(void) {
return PyErr_Occurred() == NULL;
}
static inline bool CPy_KeepPropagating(void) {
return 0;
}
// We want to avoid the public PyErr_GetExcInfo API for these because
// it requires a bunch of spurious refcount traffic on the parts of
// the triple we don't care about. Unfortunately the layout of the
// data structure changed in 3.7 so we need to handle that.
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 7
#define CPy_ExcState() PyThreadState_GET()->exc_info
#else
#define CPy_ExcState() PyThreadState_GET()
#endif
void CPy_Raise(PyObject *exc);
void CPy_Reraise(void);
void CPyErr_SetObjectAndTraceback(PyObject *type, PyObject *value, PyObject *traceback);
tuple_T3OOO CPy_CatchError(void);
void CPy_RestoreExcInfo(tuple_T3OOO info);
bool CPy_ExceptionMatches(PyObject *type);
PyObject *CPy_GetExcValue(void);
tuple_T3OOO CPy_GetExcInfo(void);
void _CPy_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback);
void CPyError_OutOfMemory(void);
void CPy_TypeError(const char *expected, PyObject *value);
void CPy_AddTraceback(const char *filename, const char *funcname, int line, PyObject *globals);
// Misc operations
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 8
#define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_BEGIN(op, dealloc)
#define CPy_TRASHCAN_END(op) Py_TRASHCAN_END
#else
#define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_SAFE_BEGIN(op)
#define CPy_TRASHCAN_END(op) Py_TRASHCAN_SAFE_END(op)
#endif
// Tweaked version of _PyArg_Parser in CPython
typedef struct CPyArg_Parser {
const char *format;
const char * const *keywords;
const char *fname;
const char *custom_msg;
int pos; /* number of positional-only arguments */
int min; /* minimal number of arguments */
int max; /* maximal number of positional arguments */
int has_required_kws; /* are there any keyword-only arguments? */
int required_kwonly_start;
int varargs; /* does the function accept *args or **kwargs? */
PyObject *kwtuple; /* tuple of keyword parameter names */
struct CPyArg_Parser *next;
} CPyArg_Parser;
// mypy lets ints silently coerce to floats, so a mypyc runtime float
// might be an int also
static inline bool CPyFloat_Check(PyObject *o) {
return PyFloat_Check(o) || PyLong_Check(o);
}
// TODO: find an unified way to avoid inline functions in non-C back ends that can not
// use inline functions
static inline bool CPy_TypeCheck(PyObject *o, PyObject *type) {
return PyObject_TypeCheck(o, (PyTypeObject *)type);
}
static inline PyObject *CPy_CalculateMetaclass(PyObject *type, PyObject *o) {
return (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)type, o);
}
PyObject *CPy_GetCoro(PyObject *obj);
PyObject *CPyIter_Send(PyObject *iter, PyObject *val);
int CPy_YieldFromErrorHandle(PyObject *iter, PyObject **outp);
PyObject *CPy_FetchStopIterationValue(void);
PyObject *CPyType_FromTemplate(PyObject *template_,
PyObject *orig_bases,
PyObject *modname);
PyObject *CPyType_FromTemplateWarpper(PyObject *template_,
PyObject *orig_bases,
PyObject *modname);
int CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
PyObject *dict, PyObject *annotations);
PyObject *CPyPickle_SetState(PyObject *obj, PyObject *state);
PyObject *CPyPickle_GetState(PyObject *obj);
CPyTagged CPyTagged_Id(PyObject *o);
void CPyDebug_Print(const char *msg);
void CPy_Init(void);
int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, const char *, const char * const *, ...);
int CPyArg_ParseStackAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...);
int CPyArg_ParseStackAndKeywordsNoArgs(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...);
int CPyArg_ParseStackAndKeywordsOneArg(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...);
int CPyArg_ParseStackAndKeywordsSimple(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...);
int CPySequence_CheckUnpackCount(PyObject *sequence, Py_ssize_t expected);
int CPyStatics_Initialize(PyObject **statics,
const char * const *strings,
const char * const *bytestrings,
const char * const *ints,
const double *floats,
const double *complex_numbers,
const int *tuples);
PyObject *CPy_Super(PyObject *builtins, PyObject *self);
PyObject *CPy_CallReverseOpMethod(PyObject *left, PyObject *right, const char *op,
_Py_Identifier *method);
PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name,
PyObject *import_name, PyObject *as_name);
PyObject *CPySingledispatch_RegisterFunction(PyObject *singledispatch_func, PyObject *cls,
PyObject *func);
#ifdef __cplusplus
}
#endif
#endif // CPY_CPY_H

View file

@ -0,0 +1,143 @@
// Bytes primitive operations
//
// These are registered in mypyc.primitives.bytes_ops.
#include <Python.h>
#include "CPy.h"
// Returns -1 on error, 0 on inequality, 1 on equality.
//
// Falls back to PyObject_RichCompareBool.
int CPyBytes_Compare(PyObject *left, PyObject *right) {
if (PyBytes_CheckExact(left) && PyBytes_CheckExact(right)) {
if (left == right) {
return 1;
}
// Adapted from cpython internal implementation of bytes_compare.
Py_ssize_t len = Py_SIZE(left);
if (Py_SIZE(right) != len) {
return 0;
}
PyBytesObject *left_b = (PyBytesObject *)left;
PyBytesObject *right_b = (PyBytesObject *)right;
if (left_b->ob_sval[0] != right_b->ob_sval[0]) {
return 0;
}
return memcmp(left_b->ob_sval, right_b->ob_sval, len) == 0;
}
return PyObject_RichCompareBool(left, right, Py_EQ);
}
CPyTagged CPyBytes_GetItem(PyObject *o, CPyTagged index) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = ((PyVarObject *)o)->ob_size;
if (n < 0)
n += size;
if (n < 0 || n >= size) {
PyErr_SetString(PyExc_IndexError, "index out of range");
return CPY_INT_TAG;
}
unsigned char num = PyBytes_Check(o) ? ((PyBytesObject *)o)->ob_sval[n]
: ((PyByteArrayObject *)o)->ob_bytes[n];
return num << 1;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return CPY_INT_TAG;
}
}
PyObject *CPyBytes_Concat(PyObject *a, PyObject *b) {
if (PyBytes_Check(a) && PyBytes_Check(b)) {
Py_ssize_t a_len = ((PyVarObject *)a)->ob_size;
Py_ssize_t b_len = ((PyVarObject *)b)->ob_size;
PyBytesObject *ret = (PyBytesObject *)PyBytes_FromStringAndSize(NULL, a_len + b_len);
if (ret != NULL) {
memcpy(ret->ob_sval, ((PyBytesObject *)a)->ob_sval, a_len);
memcpy(ret->ob_sval + a_len, ((PyBytesObject *)b)->ob_sval, b_len);
}
return (PyObject *)ret;
} else if (PyByteArray_Check(a)) {
return PyByteArray_Concat(a, b);
} else {
PyBytes_Concat(&a, b);
return a;
}
}
static inline Py_ssize_t Clamp(Py_ssize_t a, Py_ssize_t b, Py_ssize_t c) {
return a < b ? b : (a >= c ? c : a);
}
PyObject *CPyBytes_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
if ((PyBytes_Check(obj) || PyByteArray_Check(obj))
&& CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end)) {
Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start);
Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end);
Py_ssize_t len = ((PyVarObject *)obj)->ob_size;
if (startn < 0) {
startn += len;
}
if (endn < 0) {
endn += len;
}
startn = Clamp(startn, 0, len);
endn = Clamp(endn, 0, len);
Py_ssize_t slice_len = endn - startn;
if (PyBytes_Check(obj)) {
return PyBytes_FromStringAndSize(PyBytes_AS_STRING(obj) + startn, slice_len);
} else {
return PyByteArray_FromStringAndSize(PyByteArray_AS_STRING(obj) + startn, slice_len);
}
}
return CPyObject_GetSlice(obj, start, end);
}
// Like _PyBytes_Join but fallback to dynamic call if 'sep' is not bytes
// (mostly commonly, for bytearrays)
PyObject *CPyBytes_Join(PyObject *sep, PyObject *iter) {
if (PyBytes_CheckExact(sep)) {
return _PyBytes_Join(sep, iter);
} else {
_Py_IDENTIFIER(join);
return _PyObject_CallMethodIdOneArg(sep, &PyId_join, iter);
}
}
PyObject *CPyBytes_Build(Py_ssize_t len, ...) {
Py_ssize_t i;
Py_ssize_t sz = 0;
va_list args;
va_start(args, len);
for (i = 0; i < len; i++) {
PyObject *item = va_arg(args, PyObject *);
size_t add_sz = ((PyVarObject *)item)->ob_size;
// Using size_t to avoid overflow during arithmetic calculation
if (add_sz > (size_t)(PY_SSIZE_T_MAX - sz)) {
PyErr_SetString(PyExc_OverflowError,
"join() result is too long for a Python bytes");
return NULL;
}
sz += add_sz;
}
va_end(args);
PyBytesObject *ret = (PyBytesObject *)PyBytes_FromStringAndSize(NULL, sz);
if (ret != NULL) {
char *res_data = ret->ob_sval;
va_start(args, len);
for (i = 0; i < len; i++) {
PyObject *item = va_arg(args, PyObject *);
Py_ssize_t item_sz = ((PyVarObject *)item)->ob_size;
memcpy(res_data, ((PyBytesObject *)item)->ob_sval, item_sz);
res_data += item_sz;
}
va_end(args);
assert(res_data == ret->ob_sval + ((PyVarObject *)ret)->ob_size);
}
return (PyObject *)ret;
}

View file

@ -0,0 +1,438 @@
// Dict primitive operations
//
// These are registered in mypyc.primitives.dict_ops.
#include <Python.h>
#include "CPy.h"
// Dict subclasses like defaultdict override things in interesting
// ways, so we don't want to just directly use the dict methods. Not
// sure if it is actually worth doing all this stuff, but it saves
// some indirections.
PyObject *CPyDict_GetItem(PyObject *dict, PyObject *key) {
if (PyDict_CheckExact(dict)) {
PyObject *res = PyDict_GetItemWithError(dict, key);
if (!res) {
if (!PyErr_Occurred()) {
PyErr_SetObject(PyExc_KeyError, key);
}
} else {
Py_INCREF(res);
}
return res;
} else {
return PyObject_GetItem(dict, key);
}
}
PyObject *CPyDict_Build(Py_ssize_t size, ...) {
Py_ssize_t i;
PyObject *res = _PyDict_NewPresized(size);
if (res == NULL) {
return NULL;
}
va_list args;
va_start(args, size);
for (i = 0; i < size; i++) {
PyObject *key = va_arg(args, PyObject *);
PyObject *value = va_arg(args, PyObject *);
if (PyDict_SetItem(res, key, value)) {
Py_DECREF(res);
return NULL;
}
}
va_end(args);
return res;
}
PyObject *CPyDict_Get(PyObject *dict, PyObject *key, PyObject *fallback) {
// We are dodgily assuming that get on a subclass doesn't have
// different behavior.
PyObject *res = PyDict_GetItemWithError(dict, key);
if (!res) {
if (PyErr_Occurred()) {
return NULL;
}
res = fallback;
}
Py_INCREF(res);
return res;
}
PyObject *CPyDict_GetWithNone(PyObject *dict, PyObject *key) {
return CPyDict_Get(dict, key, Py_None);
}
PyObject *CPyDict_SetDefault(PyObject *dict, PyObject *key, PyObject *value) {
if (PyDict_CheckExact(dict)) {
PyObject* ret = PyDict_SetDefault(dict, key, value);
Py_XINCREF(ret);
return ret;
}
_Py_IDENTIFIER(setdefault);
return _PyObject_CallMethodIdObjArgs(dict, &PyId_setdefault, key, value, NULL);
}
PyObject *CPyDict_SetDefaultWithNone(PyObject *dict, PyObject *key) {
return CPyDict_SetDefault(dict, key, Py_None);
}
PyObject *CPyDict_SetDefaultWithEmptyDatatype(PyObject *dict, PyObject *key,
int data_type) {
PyObject *res = CPyDict_GetItem(dict, key);
if (!res) {
// CPyDict_GetItem() would generates an PyExc_KeyError
// when key is not found.
PyErr_Clear();
PyObject *new_obj;
if (data_type == 1) {
new_obj = PyList_New(0);
} else if (data_type == 2) {
new_obj = PyDict_New();
} else if (data_type == 3) {
new_obj = PySet_New(NULL);
} else {
return NULL;
}
if (CPyDict_SetItem(dict, key, new_obj) == -1) {
return NULL;
} else {
return new_obj;
}
} else {
return res;
}
}
int CPyDict_SetItem(PyObject *dict, PyObject *key, PyObject *value) {
if (PyDict_CheckExact(dict)) {
return PyDict_SetItem(dict, key, value);
} else {
return PyObject_SetItem(dict, key, value);
}
}
static inline int CPy_ObjectToStatus(PyObject *obj) {
if (obj) {
Py_DECREF(obj);
return 0;
} else {
return -1;
}
}
static int CPyDict_UpdateGeneral(PyObject *dict, PyObject *stuff) {
_Py_IDENTIFIER(update);
PyObject *res = _PyObject_CallMethodIdOneArg(dict, &PyId_update, stuff);
return CPy_ObjectToStatus(res);
}
int CPyDict_UpdateInDisplay(PyObject *dict, PyObject *stuff) {
// from https://github.com/python/cpython/blob/55d035113dfb1bd90495c8571758f504ae8d4802/Python/ceval.c#L2710
int ret = PyDict_Update(dict, stuff);
if (ret < 0) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object is not a mapping",
Py_TYPE(stuff)->tp_name);
}
}
return ret;
}
int CPyDict_Update(PyObject *dict, PyObject *stuff) {
if (PyDict_CheckExact(dict)) {
return PyDict_Update(dict, stuff);
} else {
return CPyDict_UpdateGeneral(dict, stuff);
}
}
int CPyDict_UpdateFromAny(PyObject *dict, PyObject *stuff) {
if (PyDict_CheckExact(dict)) {
// Argh this sucks
_Py_IDENTIFIER(keys);
if (PyDict_Check(stuff) || _CPyObject_HasAttrId(stuff, &PyId_keys)) {
return PyDict_Update(dict, stuff);
} else {
return PyDict_MergeFromSeq2(dict, stuff, 1);
}
} else {
return CPyDict_UpdateGeneral(dict, stuff);
}
}
PyObject *CPyDict_FromAny(PyObject *obj) {
if (PyDict_Check(obj)) {
return PyDict_Copy(obj);
} else {
int res;
PyObject *dict = PyDict_New();
if (!dict) {
return NULL;
}
_Py_IDENTIFIER(keys);
if (_CPyObject_HasAttrId(obj, &PyId_keys)) {
res = PyDict_Update(dict, obj);
} else {
res = PyDict_MergeFromSeq2(dict, obj, 1);
}
if (res < 0) {
Py_DECREF(dict);
return NULL;
}
return dict;
}
}
PyObject *CPyDict_KeysView(PyObject *dict) {
if (PyDict_CheckExact(dict)){
return _CPyDictView_New(dict, &PyDictKeys_Type);
}
_Py_IDENTIFIER(keys);
return _PyObject_CallMethodIdNoArgs(dict, &PyId_keys);
}
PyObject *CPyDict_ValuesView(PyObject *dict) {
if (PyDict_CheckExact(dict)){
return _CPyDictView_New(dict, &PyDictValues_Type);
}
_Py_IDENTIFIER(values);
return _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
}
PyObject *CPyDict_ItemsView(PyObject *dict) {
if (PyDict_CheckExact(dict)){
return _CPyDictView_New(dict, &PyDictItems_Type);
}
_Py_IDENTIFIER(items);
return _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
}
PyObject *CPyDict_Keys(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
return PyDict_Keys(dict);
}
// Inline generic fallback logic to also return a list.
PyObject *list = PyList_New(0);
_Py_IDENTIFIER(keys);
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_keys);
if (view == NULL) {
return NULL;
}
PyObject *res = _PyList_Extend((PyListObject *)list, view);
Py_DECREF(view);
if (res == NULL) {
return NULL;
}
Py_DECREF(res);
return list;
}
PyObject *CPyDict_Values(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
return PyDict_Values(dict);
}
// Inline generic fallback logic to also return a list.
PyObject *list = PyList_New(0);
_Py_IDENTIFIER(values);
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
if (view == NULL) {
return NULL;
}
PyObject *res = _PyList_Extend((PyListObject *)list, view);
Py_DECREF(view);
if (res == NULL) {
return NULL;
}
Py_DECREF(res);
return list;
}
PyObject *CPyDict_Items(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
return PyDict_Items(dict);
}
// Inline generic fallback logic to also return a list.
PyObject *list = PyList_New(0);
_Py_IDENTIFIER(items);
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
if (view == NULL) {
return NULL;
}
PyObject *res = _PyList_Extend((PyListObject *)list, view);
Py_DECREF(view);
if (res == NULL) {
return NULL;
}
Py_DECREF(res);
return list;
}
char CPyDict_Clear(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
PyDict_Clear(dict);
} else {
_Py_IDENTIFIER(clear);
PyObject *res = _PyObject_CallMethodIdNoArgs(dict, &PyId_clear);
if (res == NULL) {
return 0;
}
}
return 1;
}
PyObject *CPyDict_Copy(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
return PyDict_Copy(dict);
}
_Py_IDENTIFIER(copy);
return _PyObject_CallMethodIdNoArgs(dict, &PyId_copy);
}
PyObject *CPyDict_GetKeysIter(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
// Return dict itself to indicate we can use fast path instead.
Py_INCREF(dict);
return dict;
}
return PyObject_GetIter(dict);
}
PyObject *CPyDict_GetItemsIter(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
// Return dict itself to indicate we can use fast path instead.
Py_INCREF(dict);
return dict;
}
_Py_IDENTIFIER(items);
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_items);
if (view == NULL) {
return NULL;
}
PyObject *iter = PyObject_GetIter(view);
Py_DECREF(view);
return iter;
}
PyObject *CPyDict_GetValuesIter(PyObject *dict) {
if (PyDict_CheckExact(dict)) {
// Return dict itself to indicate we can use fast path instead.
Py_INCREF(dict);
return dict;
}
_Py_IDENTIFIER(values);
PyObject *view = _PyObject_CallMethodIdNoArgs(dict, &PyId_values);
if (view == NULL) {
return NULL;
}
PyObject *iter = PyObject_GetIter(view);
Py_DECREF(view);
return iter;
}
static void _CPyDict_FromNext(tuple_T3CIO *ret, PyObject *dict_iter) {
// Get next item from iterator and set "should continue" flag.
ret->f2 = PyIter_Next(dict_iter);
if (ret->f2 == NULL) {
ret->f0 = 0;
Py_INCREF(Py_None);
ret->f2 = Py_None;
} else {
ret->f0 = 1;
}
}
// Helpers for fast dictionary iteration, return a single tuple
// instead of writing to multiple registers, for exact dicts use
// the fast path, and fall back to generic iterator logic for subclasses.
tuple_T3CIO CPyDict_NextKey(PyObject *dict_or_iter, CPyTagged offset) {
tuple_T3CIO ret;
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
PyObject *dummy;
if (PyDict_CheckExact(dict_or_iter)) {
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &dummy);
if (ret.f0) {
ret.f1 = CPyTagged_FromSsize_t(py_offset);
} else {
// Set key to None, so mypyc can manage refcounts.
ret.f1 = 0;
ret.f2 = Py_None;
}
// PyDict_Next() returns borrowed references.
Py_INCREF(ret.f2);
} else {
// offset is dummy in this case, just use the old value.
ret.f1 = offset;
_CPyDict_FromNext(&ret, dict_or_iter);
}
return ret;
}
tuple_T3CIO CPyDict_NextValue(PyObject *dict_or_iter, CPyTagged offset) {
tuple_T3CIO ret;
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
PyObject *dummy;
if (PyDict_CheckExact(dict_or_iter)) {
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &dummy, &ret.f2);
if (ret.f0) {
ret.f1 = CPyTagged_FromSsize_t(py_offset);
} else {
// Set value to None, so mypyc can manage refcounts.
ret.f1 = 0;
ret.f2 = Py_None;
}
// PyDict_Next() returns borrowed references.
Py_INCREF(ret.f2);
} else {
// offset is dummy in this case, just use the old value.
ret.f1 = offset;
_CPyDict_FromNext(&ret, dict_or_iter);
}
return ret;
}
tuple_T4CIOO CPyDict_NextItem(PyObject *dict_or_iter, CPyTagged offset) {
tuple_T4CIOO ret;
Py_ssize_t py_offset = CPyTagged_AsSsize_t(offset);
if (PyDict_CheckExact(dict_or_iter)) {
ret.f0 = PyDict_Next(dict_or_iter, &py_offset, &ret.f2, &ret.f3);
if (ret.f0) {
ret.f1 = CPyTagged_FromSsize_t(py_offset);
} else {
// Set key and value to None, so mypyc can manage refcounts.
ret.f1 = 0;
ret.f2 = Py_None;
ret.f3 = Py_None;
}
} else {
ret.f1 = offset;
PyObject *item = PyIter_Next(dict_or_iter);
if (item == NULL || !PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
if (item != NULL) {
PyErr_SetString(PyExc_TypeError, "a tuple of length 2 expected");
}
ret.f0 = 0;
ret.f2 = Py_None;
ret.f3 = Py_None;
} else {
ret.f0 = 1;
ret.f2 = PyTuple_GET_ITEM(item, 0);
ret.f3 = PyTuple_GET_ITEM(item, 1);
Py_DECREF(item);
}
}
// PyDict_Next() returns borrowed references.
Py_INCREF(ret.f2);
Py_INCREF(ret.f3);
return ret;
}

View file

@ -0,0 +1,227 @@
// Exception related primitive operations
//
// These are registered in mypyc.primitives.exc_ops.
#include <Python.h>
#include "CPy.h"
void CPy_Raise(PyObject *exc) {
if (PyObject_IsInstance(exc, (PyObject *)&PyType_Type)) {
PyObject *obj = PyObject_CallFunctionObjArgs(exc, NULL);
if (!obj)
return;
PyErr_SetObject(exc, obj);
Py_DECREF(obj);
} else {
PyErr_SetObject((PyObject *)Py_TYPE(exc), exc);
}
}
void CPy_Reraise(void) {
PyObject *p_type, *p_value, *p_traceback;
PyErr_GetExcInfo(&p_type, &p_value, &p_traceback);
PyErr_Restore(p_type, p_value, p_traceback);
}
void CPyErr_SetObjectAndTraceback(PyObject *type, PyObject *value, PyObject *traceback) {
// Set the value and traceback of an error. Because calling
// PyErr_Restore takes away a reference to each object passed in
// as an argument, we manually increase the reference count of
// each argument before calling it.
Py_INCREF(type);
Py_INCREF(value);
Py_INCREF(traceback);
PyErr_Restore(type, value, traceback);
}
tuple_T3OOO CPy_CatchError(void) {
// We need to return the existing sys.exc_info() information, so
// that it can be restored when we finish handling the error we
// are catching now. Grab that triple and convert NULL values to
// the ExcDummy object in order to simplify refcount handling in
// generated code.
tuple_T3OOO ret;
PyErr_GetExcInfo(&ret.f0, &ret.f1, &ret.f2);
_CPy_ToDummy(&ret.f0);
_CPy_ToDummy(&ret.f1);
_CPy_ToDummy(&ret.f2);
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_RuntimeError, "CPy_CatchError called with no error!");
}
// Retrieve the error info and normalize it so that it looks like
// what python code needs it to be.
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
// Could we avoid always normalizing?
PyErr_NormalizeException(&type, &value, &traceback);
if (traceback != NULL) {
PyException_SetTraceback(value, traceback);
}
// Indicate that we are now handling this exception by stashing it
// in sys.exc_info(). mypyc routines that need access to the
// exception will read it out of there.
PyErr_SetExcInfo(type, value, traceback);
// Clear the error indicator, since the exception isn't
// propagating anymore.
PyErr_Clear();
return ret;
}
void CPy_RestoreExcInfo(tuple_T3OOO info) {
PyErr_SetExcInfo(_CPy_FromDummy(info.f0), _CPy_FromDummy(info.f1), _CPy_FromDummy(info.f2));
}
bool CPy_ExceptionMatches(PyObject *type) {
return PyErr_GivenExceptionMatches(CPy_ExcState()->exc_type, type);
}
PyObject *CPy_GetExcValue(void) {
PyObject *exc = CPy_ExcState()->exc_value;
Py_INCREF(exc);
return exc;
}
static inline void _CPy_ToNone(PyObject **p) {
if (*p == NULL) {
Py_INCREF(Py_None);
*p = Py_None;
}
}
void _CPy_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) {
PyErr_GetExcInfo(p_type, p_value, p_traceback);
_CPy_ToNone(p_type);
_CPy_ToNone(p_value);
_CPy_ToNone(p_traceback);
}
tuple_T3OOO CPy_GetExcInfo(void) {
tuple_T3OOO ret;
_CPy_GetExcInfo(&ret.f0, &ret.f1, &ret.f2);
return ret;
}
void CPyError_OutOfMemory(void) {
fprintf(stderr, "fatal: out of memory\n");
fflush(stderr);
abort();
}
// Construct a nicely formatted type name based on __module__ and __name__.
static PyObject *CPy_GetTypeName(PyObject *type) {
PyObject *module = NULL, *name = NULL;
PyObject *full = NULL;
module = PyObject_GetAttrString(type, "__module__");
if (!module || !PyUnicode_Check(module)) {
goto out;
}
name = PyObject_GetAttrString(type, "__qualname__");
if (!name || !PyUnicode_Check(name)) {
goto out;
}
if (PyUnicode_CompareWithASCIIString(module, "builtins") == 0) {
Py_INCREF(name);
full = name;
} else {
full = PyUnicode_FromFormat("%U.%U", module, name);
}
out:
Py_XDECREF(module);
Py_XDECREF(name);
return full;
}
// Get the type of a value as a string, expanding tuples to include
// all the element types.
static PyObject *CPy_FormatTypeName(PyObject *value) {
if (Py_IsNone(value)) {
return PyUnicode_FromString("None");
}
if (!PyTuple_CheckExact(value)) {
return CPy_GetTypeName((PyObject *)Py_TYPE(value));
}
if (PyTuple_GET_SIZE(value) > 10) {
return PyUnicode_FromFormat("tuple[<%d items>]", PyTuple_GET_SIZE(value));
}
// Most of the logic is all for tuples, which is the only interesting case
PyObject *output = PyUnicode_FromString("tuple[");
if (!output) {
return NULL;
}
/* This is quadratic but if that ever matters something is really weird. */
int i;
for (i = 0; i < PyTuple_GET_SIZE(value); i++) {
PyObject *s = CPy_FormatTypeName(PyTuple_GET_ITEM(value, i));
if (!s) {
Py_DECREF(output);
return NULL;
}
PyObject *next = PyUnicode_FromFormat("%U%U%s", output, s,
i + 1 == PyTuple_GET_SIZE(value) ? "]" : ", ");
Py_DECREF(output);
Py_DECREF(s);
if (!next) {
return NULL;
}
output = next;
}
return output;
}
CPy_NOINLINE
void CPy_TypeError(const char *expected, PyObject *value) {
PyObject *out = CPy_FormatTypeName(value);
if (out) {
PyErr_Format(PyExc_TypeError, "%s object expected; got %U", expected, out);
Py_DECREF(out);
} else {
PyErr_Format(PyExc_TypeError, "%s object expected; and errored formatting real type!",
expected);
}
}
// This function is basically exactly the same with _PyTraceback_Add
// which is available in all the versions we support.
// We're continuing to use this because we'll probably optimize this later.
void CPy_AddTraceback(const char *filename, const char *funcname, int line, PyObject *globals) {
PyObject *exc, *val, *tb;
PyThreadState *thread_state = PyThreadState_GET();
PyFrameObject *frame_obj;
// We need to save off the exception state because in 3.8,
// PyFrame_New fails if there is an error set and it fails to look
// up builtins in the globals. (_PyTraceback_Add documents that it
// needs to do it because it decodes the filename according to the
// FS encoding, which could have a decoder in Python. We don't do
// that so *that* doesn't apply to us.)
PyErr_Fetch(&exc, &val, &tb);
PyCodeObject *code_obj = PyCode_NewEmpty(filename, funcname, line);
if (code_obj == NULL) {
goto error;
}
frame_obj = PyFrame_New(thread_state, code_obj, globals, 0);
if (frame_obj == NULL) {
Py_DECREF(code_obj);
goto error;
}
frame_obj->f_lineno = line;
PyErr_Restore(exc, val, tb);
PyTraceBack_Here(frame_obj);
Py_DECREF(code_obj);
Py_DECREF(frame_obj);
return;
error:
_PyErr_ChainExceptions(exc, val, tb);
}

View file

@ -0,0 +1,59 @@
// Generic primitive operations
//
// These are registered in mypyc.primitives.generic_ops.
#include <Python.h>
#include "CPy.h"
CPyTagged CPyObject_Hash(PyObject *o) {
Py_hash_t h = PyObject_Hash(o);
if (h == -1) {
return CPY_INT_TAG;
} else {
// This is tragically annoying. The range of hash values in
// 64-bit python covers 64-bits, and our short integers only
// cover 63. This means that half the time we are boxing the
// result for basically no good reason. To add insult to
// injury it is probably about to be immediately unboxed by a
// tp_hash wrapper.
return CPyTagged_FromSsize_t(h);
}
}
PyObject *CPyObject_GetAttr3(PyObject *v, PyObject *name, PyObject *defl)
{
PyObject *result = PyObject_GetAttr(v, name);
if (!result && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
Py_INCREF(defl);
result = defl;
}
return result;
}
PyObject *CPyIter_Next(PyObject *iter)
{
return (*Py_TYPE(iter)->tp_iternext)(iter);
}
PyObject *CPyNumber_Power(PyObject *base, PyObject *index)
{
return PyNumber_Power(base, index, Py_None);
}
PyObject *CPyObject_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
PyObject *start_obj = CPyTagged_AsObject(start);
PyObject *end_obj = CPyTagged_AsObject(end);
if (unlikely(start_obj == NULL || end_obj == NULL)) {
return NULL;
}
PyObject *slice = PySlice_New(start_obj, end_obj, NULL);
Py_DECREF(start_obj);
Py_DECREF(end_obj);
if (unlikely(slice == NULL)) {
return NULL;
}
PyObject *result = PyObject_GetItem(obj, slice);
Py_DECREF(slice);
return result;
}

View file

@ -0,0 +1,450 @@
/* getargs implementation copied from Python 3.8 and stripped down to only include
* the functions we need.
* We also add support for required kwonly args and accepting *args / **kwargs.
* A good idea would be to also vendor in the Fast versions and get our stuff
* working with *that*.
* Another probably good idea is to strip out all the formatting stuff we don't need
* and then add in custom stuff that we do need.
*
* DOCUMENTATION OF THE EXTENSIONS:
* - Arguments given after a @ format specify are required keyword-only arguments.
* The | and $ specifiers must both appear before @.
* - If the first character of a format string is %, then the function can support
* *args and **kwargs. On seeing a %, the parser will consume two arguments,
* which should be pointers to variables to store the *args and **kwargs, respectively.
* Either pointer can be NULL, in which case the function doesn't take that
* variety of vararg.
* Unlike most format specifiers, the caller takes ownership of these objects
* and is responsible for decrefing them.
* - All arguments must use the 'O' format.
* - There's minimal error checking of format strings. They are generated
* programmatically and can be assumed valid.
*/
// These macro definitions are copied from pyport.h in Python 3.9 and later
// https://bugs.python.org/issue19569
#if defined(__clang__)
#define _Py_COMP_DIAG_PUSH _Pragma("clang diagnostic push")
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
#define _Py_COMP_DIAG_POP _Pragma("clang diagnostic pop")
#elif defined(__GNUC__) \
&& ((__GNUC__ >= 5) || (__GNUC__ == 4) && (__GNUC_MINOR__ >= 6))
#define _Py_COMP_DIAG_PUSH _Pragma("GCC diagnostic push")
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#define _Py_COMP_DIAG_POP _Pragma("GCC diagnostic pop")
#elif defined(_MSC_VER)
#define _Py_COMP_DIAG_PUSH __pragma(warning(push))
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS __pragma(warning(disable: 4996))
#define _Py_COMP_DIAG_POP __pragma(warning(pop))
#else
#define _Py_COMP_DIAG_PUSH
#define _Py_COMP_DIAG_IGNORE_DEPR_DECLS
#define _Py_COMP_DIAG_POP
#endif
#include "Python.h"
#include "pythonsupport.h"
#include <ctype.h>
#include <float.h>
#ifndef PyDict_GET_SIZE
#define PyDict_GET_SIZE(d) PyDict_Size(d)
#endif
#ifdef __cplusplus
extern "C" {
#endif
int CPyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
const char *, const char *, const char * const *, ...);
/* Forward */
static int vgetargskeywords(PyObject *, PyObject *,
const char *, const char *, const char * const *, va_list *);
static void skipitem(const char **, va_list *);
/* Support for keyword arguments donated by
Geoff Philbrick <philbric@delphi.hks.com> */
/* Return false (0) for error, else true. */
int
CPyArg_ParseTupleAndKeywords(PyObject *args,
PyObject *keywords,
const char *format,
const char *fname,
const char * const *kwlist, ...)
{
int retval;
va_list va;
va_start(va, kwlist);
retval = vgetargskeywords(args, keywords, format, fname, kwlist, &va);
va_end(va);
return retval;
}
#define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':')
static int
vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format,
const char *fname, const char * const *kwlist, va_list *p_va)
{
int min = INT_MAX;
int max = INT_MAX;
int required_kwonly_start = INT_MAX;
int has_required_kws = 0;
int i, pos, len;
int skip = 0;
Py_ssize_t nargs, nkwargs;
PyObject *current_arg;
int bound_pos_args;
PyObject **p_args = NULL, **p_kwargs = NULL;
assert(args != NULL && PyTuple_Check(args));
assert(kwargs == NULL || PyDict_Check(kwargs));
assert(format != NULL);
assert(kwlist != NULL);
assert(p_va != NULL);
/* scan kwlist and count the number of positional-only parameters */
for (pos = 0; kwlist[pos] && !*kwlist[pos]; pos++) {
}
/* scan kwlist and get greatest possible nbr of args */
for (len = pos; kwlist[len]; len++) {
#ifdef DEBUG
if (!*kwlist[len]) {
PyErr_SetString(PyExc_SystemError,
"Empty keyword parameter name");
return 0;
}
#endif
}
if (*format == '%') {
p_args = va_arg(*p_va, PyObject **);
p_kwargs = va_arg(*p_va, PyObject **);
format++;
}
nargs = PyTuple_GET_SIZE(args);
nkwargs = (kwargs == NULL) ? 0 : PyDict_GET_SIZE(kwargs);
if (unlikely(nargs + nkwargs > len && !p_args && !p_kwargs)) {
/* Adding "keyword" (when nargs == 0) prevents producing wrong error
messages in some special cases (see bpo-31229). */
PyErr_Format(PyExc_TypeError,
"%.200s%s takes at most %d %sargument%s (%zd given)",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
len,
(nargs == 0) ? "keyword " : "",
(len == 1) ? "" : "s",
nargs + nkwargs);
return 0;
}
/* convert tuple args and keyword args in same loop, using kwlist to drive process */
for (i = 0; i < len; i++) {
if (*format == '|') {
#ifdef DEBUG
if (min != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string (| specified twice)");
return 0;
}
#endif
min = i;
format++;
#ifdef DEBUG
if (max != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string ($ before |)");
return 0;
}
#endif
/* If there are optional args, figure out whether we have
* required keyword arguments so that we don't bail without
* enforcing them. */
has_required_kws = strchr(format, '@') != NULL;
}
if (*format == '$') {
#ifdef DEBUG
if (max != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string ($ specified twice)");
return 0;
}
#endif
max = i;
format++;
#ifdef DEBUG
if (max < pos) {
PyErr_SetString(PyExc_SystemError,
"Empty parameter name after $");
return 0;
}
#endif
if (skip) {
/* Now we know the minimal and the maximal numbers of
* positional arguments and can raise an exception with
* informative message (see below). */
break;
}
if (unlikely(max < nargs && !p_args)) {
if (max == 0) {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes no positional arguments",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()");
}
else {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes %s %d positional argument%s"
" (%zd given)",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
(min < max) ? "at most" : "exactly",
max,
max == 1 ? "" : "s",
nargs);
}
return 0;
}
}
if (*format == '@') {
#ifdef DEBUG
if (min == INT_MAX && max == INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string "
"(@ without preceding | and $)");
return 0;
}
if (required_kwonly_start != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string (@ specified twice)");
return 0;
}
#endif
required_kwonly_start = i;
format++;
}
#ifdef DEBUG
if (IS_END_OF_FORMAT(*format)) {
PyErr_Format(PyExc_SystemError,
"More keyword list entries (%d) than "
"format specifiers (%d)", len, i);
return 0;
}
#endif
if (!skip) {
if (i < nargs && i < max) {
current_arg = PyTuple_GET_ITEM(args, i);
}
else if (nkwargs && i >= pos) {
current_arg = _PyDict_GetItemStringWithError(kwargs, kwlist[i]);
if (current_arg) {
--nkwargs;
}
else if (PyErr_Occurred()) {
return 0;
}
}
else {
current_arg = NULL;
}
if (current_arg) {
PyObject **p = va_arg(*p_va, PyObject **);
*p = current_arg;
format++;
continue;
}
if (i < min || i >= required_kwonly_start) {
if (likely(i < pos)) {
assert (min == INT_MAX);
assert (max == INT_MAX);
skip = 1;
/* At that moment we still don't know the minimal and
* the maximal numbers of positional arguments. Raising
* an exception is deferred until we encounter | and $
* or the end of the format. */
}
else {
if (i >= max) {
PyErr_Format(PyExc_TypeError,
"%.200s%s missing required "
"keyword-only argument '%s'",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
kwlist[i]);
}
else {
PyErr_Format(PyExc_TypeError,
"%.200s%s missing required "
"argument '%s' (pos %d)",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
kwlist[i], i+1);
}
return 0;
}
}
/* current code reports success when all required args
* fulfilled and no keyword args left, with no further
* validation. XXX Maybe skip this in debug build ?
*/
if (!nkwargs && !skip && !has_required_kws &&
!p_args && !p_kwargs)
{
return 1;
}
}
/* We are into optional args, skip through to any remaining
* keyword args */
skipitem(&format, p_va);
}
if (unlikely(skip)) {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes %s %d positional argument%s"
" (%zd given)",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
(Py_MIN(pos, min) < i) ? "at least" : "exactly",
Py_MIN(pos, min),
Py_MIN(pos, min) == 1 ? "" : "s",
nargs);
return 0;
}
#ifdef DEBUG
if (!IS_END_OF_FORMAT(*format) &&
(*format != '|') && (*format != '$') && (*format != '@'))
{
PyErr_Format(PyExc_SystemError,
"more argument specifiers than keyword list entries "
"(remaining format:'%s')", format);
return 0;
}
#endif
bound_pos_args = Py_MIN(nargs, Py_MIN(max, len));
if (p_args) {
*p_args = PyTuple_GetSlice(args, bound_pos_args, nargs);
if (!*p_args) {
return 0;
}
}
if (p_kwargs) {
/* This unfortunately needs to be special cased because if len is 0 then we
* never go through the main loop. */
if (unlikely(nargs > 0 && len == 0 && !p_args)) {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes no positional arguments",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()");
return 0;
}
*p_kwargs = PyDict_New();
if (!*p_kwargs) {
goto latefail;
}
}
if (nkwargs > 0) {
PyObject *key, *value;
Py_ssize_t j;
/* make sure there are no arguments given by name and position */
for (i = pos; i < bound_pos_args && i < len; i++) {
current_arg = _PyDict_GetItemStringWithError(kwargs, kwlist[i]);
if (unlikely(current_arg != NULL)) {
/* arg present in tuple and in dict */
PyErr_Format(PyExc_TypeError,
"argument for %.200s%s given by name ('%s') "
"and position (%d)",
(fname == NULL) ? "function" : fname,
(fname == NULL) ? "" : "()",
kwlist[i], i+1);
goto latefail;
}
else if (unlikely(PyErr_Occurred() != NULL)) {
goto latefail;
}
}
/* make sure there are no extraneous keyword arguments */
j = 0;
while (PyDict_Next(kwargs, &j, &key, &value)) {
int match = 0;
if (unlikely(!PyUnicode_Check(key))) {
PyErr_SetString(PyExc_TypeError,
"keywords must be strings");
goto latefail;
}
for (i = pos; i < len; i++) {
if (CPyUnicode_EqualToASCIIString(key, kwlist[i])) {
match = 1;
break;
}
}
if (!match) {
if (unlikely(!p_kwargs)) {
PyErr_Format(PyExc_TypeError,
"'%U' is an invalid keyword "
"argument for %.200s%s",
key,
(fname == NULL) ? "this function" : fname,
(fname == NULL) ? "" : "()");
goto latefail;
} else {
if (PyDict_SetItem(*p_kwargs, key, value) < 0) {
goto latefail;
}
}
}
}
}
return 1;
/* Handle failures that have happened after we have tried to
* create *args and **kwargs, if they exist. */
latefail:
if (p_args) {
Py_XDECREF(*p_args);
}
if (p_kwargs) {
Py_XDECREF(*p_kwargs);
}
return 0;
}
static void
skipitem(const char **p_format, va_list *p_va)
{
const char *format = *p_format;
char c = *format++;
if (p_va != NULL) {
(void) va_arg(*p_va, PyObject **);
}
*p_format = format;
}
#ifdef __cplusplus
};
#endif

View file

@ -0,0 +1,574 @@
/* getargskeywordsfast implementation copied from Python 3.9 and stripped down to
* only include the functionality we need.
*
* We also add support for required kwonly args and accepting *args / **kwargs.
*
* DOCUMENTATION OF THE EXTENSIONS:
* - Arguments given after a @ format specify required keyword-only arguments.
* The | and $ specifiers must both appear before @.
* - If the first character of a format string is %, then the function can support
* *args and/or **kwargs. In this case the parser will consume two arguments,
* which should be pointers to variables to store the *args and **kwargs, respectively.
* Either pointer can be NULL, in which case the function doesn't take that
* variety of vararg.
* Unlike most format specifiers, the caller takes ownership of these objects
* and is responsible for decrefing them.
*/
#include <Python.h>
#include "CPy.h"
/* None of this is supported on Python 3.6 or earlier */
#if PY_VERSION_HEX >= 0x03070000
#define PARSER_INITED(parser) ((parser)->kwtuple != NULL)
/* Forward */
static int
vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
PyObject *kwargs, PyObject *kwnames,
CPyArg_Parser *parser,
va_list *p_va);
static void skipitem_fast(const char **, va_list *);
/* Parse args for an arbitrary signature */
int
CPyArg_ParseStackAndKeywords(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...)
{
int retval;
va_list va;
va_start(va, parser);
retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va);
va_end(va);
return retval;
}
/* Parse args for a function that takes no args */
int
CPyArg_ParseStackAndKeywordsNoArgs(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...)
{
int retval;
va_list va;
va_start(va, parser);
if (nargs == 0 && kwnames == NULL) {
// Fast path: no arguments
retval = 1;
} else {
retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va);
}
va_end(va);
return retval;
}
/* Parse args for a function that takes one arg */
int
CPyArg_ParseStackAndKeywordsOneArg(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...)
{
int retval;
va_list va;
va_start(va, parser);
if (kwnames == NULL && nargs == 1) {
// Fast path: one positional argument
PyObject **p;
p = va_arg(va, PyObject **);
*p = args[0];
retval = 1;
} else {
retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va);
}
va_end(va);
return retval;
}
/* Parse args for a function that takes no keyword-only args, *args or **kwargs */
int
CPyArg_ParseStackAndKeywordsSimple(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames,
CPyArg_Parser *parser, ...)
{
int retval;
va_list va;
va_start(va, parser);
if (kwnames == NULL && PARSER_INITED(parser) &&
nargs >= parser->min && nargs <= parser->max) {
// Fast path: correct number of positional arguments only
PyObject **p;
Py_ssize_t i;
for (i = 0; i < nargs; i++) {
p = va_arg(va, PyObject **);
*p = args[i];
}
retval = 1;
} else {
retval = vgetargskeywordsfast_impl(args, nargs, NULL, kwnames, parser, &va);
}
va_end(va);
return retval;
}
#define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':')
/* List of static parsers. */
static struct CPyArg_Parser *static_arg_parsers = NULL;
static int
parser_init(CPyArg_Parser *parser)
{
const char * const *keywords;
const char *format, *msg;
int i, len, min, max, nkw;
PyObject *kwtuple;
assert(parser->keywords != NULL);
if (PARSER_INITED(parser)) {
return 1;
}
keywords = parser->keywords;
/* scan keywords and count the number of positional-only parameters */
for (i = 0; keywords[i] && !*keywords[i]; i++) {
}
parser->pos = i;
/* scan keywords and get greatest possible nbr of args */
for (; keywords[i]; i++) {
if (!*keywords[i]) {
PyErr_SetString(PyExc_SystemError,
"Empty keyword parameter name");
return 0;
}
}
len = i;
parser->required_kwonly_start = INT_MAX;
if (*parser->format == '%') {
parser->format++;
parser->varargs = 1;
}
format = parser->format;
if (format) {
/* grab the function name or custom error msg first (mutually exclusive) */
parser->fname = strchr(parser->format, ':');
if (parser->fname) {
parser->fname++;
parser->custom_msg = NULL;
}
else {
parser->custom_msg = strchr(parser->format,';');
if (parser->custom_msg)
parser->custom_msg++;
}
min = max = INT_MAX;
for (i = 0; i < len; i++) {
if (*format == '|') {
if (min != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string (| specified twice)");
return 0;
}
if (max != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string ($ before |)");
return 0;
}
min = i;
format++;
}
if (*format == '$') {
if (max != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string ($ specified twice)");
return 0;
}
if (i < parser->pos) {
PyErr_SetString(PyExc_SystemError,
"Empty parameter name after $");
return 0;
}
max = i;
format++;
}
if (*format == '@') {
if (parser->required_kwonly_start != INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string (@ specified twice)");
return 0;
}
if (min == INT_MAX && max == INT_MAX) {
PyErr_SetString(PyExc_SystemError,
"Invalid format string "
"(@ without preceding | and $)");
return 0;
}
format++;
parser->has_required_kws = 1;
parser->required_kwonly_start = i;
}
if (IS_END_OF_FORMAT(*format)) {
PyErr_Format(PyExc_SystemError,
"More keyword list entries (%d) than "
"format specifiers (%d)", len, i);
return 0;
}
skipitem_fast(&format, NULL);
}
parser->min = Py_MIN(min, len);
parser->max = Py_MIN(max, len);
if (!IS_END_OF_FORMAT(*format) && (*format != '|') && (*format != '$')) {
PyErr_Format(PyExc_SystemError,
"more argument specifiers than keyword list entries "
"(remaining format:'%s')", format);
return 0;
}
}
nkw = len - parser->pos;
kwtuple = PyTuple_New(nkw);
if (kwtuple == NULL) {
return 0;
}
keywords = parser->keywords + parser->pos;
for (i = 0; i < nkw; i++) {
PyObject *str = PyUnicode_FromString(keywords[i]);
if (str == NULL) {
Py_DECREF(kwtuple);
return 0;
}
PyUnicode_InternInPlace(&str);
PyTuple_SET_ITEM(kwtuple, i, str);
}
parser->kwtuple = kwtuple;
assert(parser->next == NULL);
parser->next = static_arg_parsers;
static_arg_parsers = parser;
return 1;
}
static PyObject*
find_keyword(PyObject *kwnames, PyObject *const *kwstack, PyObject *key)
{
Py_ssize_t i, nkwargs;
nkwargs = PyTuple_GET_SIZE(kwnames);
for (i = 0; i < nkwargs; i++) {
PyObject *kwname = PyTuple_GET_ITEM(kwnames, i);
/* kwname == key will normally find a match in since keyword keys
should be interned strings; if not retry below in a new loop. */
if (kwname == key) {
return kwstack[i];
}
}
for (i = 0; i < nkwargs; i++) {
PyObject *kwname = PyTuple_GET_ITEM(kwnames, i);
assert(PyUnicode_Check(kwname));
if (_PyUnicode_EQ(kwname, key)) {
return kwstack[i];
}
}
return NULL;
}
static int
vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs,
PyObject *kwargs, PyObject *kwnames,
CPyArg_Parser *parser,
va_list *p_va)
{
PyObject *kwtuple;
const char *format;
PyObject *keyword;
int i, pos, len;
Py_ssize_t nkwargs;
PyObject *current_arg;
PyObject *const *kwstack = NULL;
int bound_pos_args;
PyObject **p_args = NULL, **p_kwargs = NULL;
assert(kwargs == NULL || PyDict_Check(kwargs));
assert(kwargs == NULL || kwnames == NULL);
assert(p_va != NULL);
if (!parser_init(parser)) {
return 0;
}
kwtuple = parser->kwtuple;
pos = parser->pos;
len = pos + (int)PyTuple_GET_SIZE(kwtuple);
if (parser->varargs) {
p_args = va_arg(*p_va, PyObject **);
p_kwargs = va_arg(*p_va, PyObject **);
}
if (kwargs != NULL) {
nkwargs = PyDict_GET_SIZE(kwargs);
}
else if (kwnames != NULL) {
nkwargs = PyTuple_GET_SIZE(kwnames);
kwstack = args + nargs;
}
else {
nkwargs = 0;
}
if (nargs + nkwargs > len && !p_args && !p_kwargs) {
/* Adding "keyword" (when nargs == 0) prevents producing wrong error
messages in some special cases (see bpo-31229). */
PyErr_Format(PyExc_TypeError,
"%.200s%s takes at most %d %sargument%s (%zd given)",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
len,
(nargs == 0) ? "keyword " : "",
(len == 1) ? "" : "s",
nargs + nkwargs);
return 0;
}
if (parser->max < nargs && !p_args) {
if (parser->max == 0) {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes no positional arguments",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
}
else {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes %s %d positional argument%s (%zd given)",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
(parser->min < parser->max) ? "at most" : "exactly",
parser->max,
parser->max == 1 ? "" : "s",
nargs);
}
return 0;
}
format = parser->format;
/* convert tuple args and keyword args in same loop, using kwtuple to drive process */
for (i = 0; i < len; i++) {
if (*format == '|') {
format++;
}
if (*format == '$') {
format++;
}
if (*format == '@') {
format++;
}
assert(!IS_END_OF_FORMAT(*format));
if (i < nargs && i < parser->max) {
current_arg = args[i];
}
else if (nkwargs && i >= pos) {
keyword = PyTuple_GET_ITEM(kwtuple, i - pos);
if (kwargs != NULL) {
current_arg = PyDict_GetItemWithError(kwargs, keyword);
if (!current_arg && PyErr_Occurred()) {
return 0;
}
}
else {
current_arg = find_keyword(kwnames, kwstack, keyword);
}
if (current_arg) {
--nkwargs;
}
}
else {
current_arg = NULL;
}
if (current_arg) {
PyObject **p = va_arg(*p_va, PyObject **);
*p = current_arg;
format++;
continue;
}
if (i < parser->min || i >= parser->required_kwonly_start) {
/* Less arguments than required */
if (i < pos) {
Py_ssize_t min = Py_MIN(pos, parser->min);
PyErr_Format(PyExc_TypeError,
"%.200s%s takes %s %d positional argument%s"
" (%zd given)",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
min < parser->max ? "at least" : "exactly",
min,
min == 1 ? "" : "s",
nargs);
}
else {
keyword = PyTuple_GET_ITEM(kwtuple, i - pos);
if (i >= parser->max) {
PyErr_Format(PyExc_TypeError, "%.200s%s missing required "
"keyword-only argument '%U'",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
keyword);
}
else {
PyErr_Format(PyExc_TypeError, "%.200s%s missing required "
"argument '%U' (pos %d)",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
keyword, i+1);
}
}
return 0;
}
/* current code reports success when all required args
* fulfilled and no keyword args left, with no further
* validation. XXX Maybe skip this in debug build ?
*/
if (!nkwargs && !parser->has_required_kws && !p_args && !p_kwargs) {
return 1;
}
/* We are into optional args, skip through to any remaining
* keyword args */
skipitem_fast(&format, p_va);
}
assert(IS_END_OF_FORMAT(*format) || (*format == '|') || (*format == '$'));
bound_pos_args = Py_MIN(nargs, Py_MIN(parser->max, len));
if (p_args) {
*p_args = PyTuple_New(nargs - bound_pos_args);
if (!*p_args) {
return 0;
}
for (i = bound_pos_args; i < nargs; i++) {
PyObject *arg = args[i];
Py_INCREF(arg);
PyTuple_SET_ITEM(*p_args, i - bound_pos_args, arg);
}
}
if (p_kwargs) {
/* This unfortunately needs to be special cased because if len is 0 then we
* never go through the main loop. */
if (nargs > 0 && len == 0 && !p_args) {
PyErr_Format(PyExc_TypeError,
"%.200s%s takes no positional arguments",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
return 0;
}
*p_kwargs = PyDict_New();
if (!*p_kwargs) {
goto latefail;
}
}
if (nkwargs > 0) {
Py_ssize_t j;
PyObject *value;
/* make sure there are no arguments given by name and position */
for (i = pos; i < bound_pos_args; i++) {
keyword = PyTuple_GET_ITEM(kwtuple, i - pos);
if (kwargs != NULL) {
current_arg = PyDict_GetItemWithError(kwargs, keyword);
if (!current_arg && PyErr_Occurred()) {
goto latefail;
}
}
else {
current_arg = find_keyword(kwnames, kwstack, keyword);
}
if (current_arg) {
/* arg present in tuple and in dict */
PyErr_Format(PyExc_TypeError,
"argument for %.200s%s given by name ('%U') "
"and position (%d)",
(parser->fname == NULL) ? "function" : parser->fname,
(parser->fname == NULL) ? "" : "()",
keyword, i+1);
goto latefail;
}
}
/* make sure there are no extraneous keyword arguments */
j = 0;
while (1) {
int match;
if (kwargs != NULL) {
if (!PyDict_Next(kwargs, &j, &keyword, &value))
break;
}
else {
if (j >= PyTuple_GET_SIZE(kwnames))
break;
keyword = PyTuple_GET_ITEM(kwnames, j);
value = kwstack[j];
j++;
}
match = PySequence_Contains(kwtuple, keyword);
if (match <= 0) {
if (!match) {
if (!p_kwargs) {
PyErr_Format(PyExc_TypeError,
"'%S' is an invalid keyword "
"argument for %.200s%s",
keyword,
(parser->fname == NULL) ? "this function" : parser->fname,
(parser->fname == NULL) ? "" : "()");
goto latefail;
} else {
if (PyDict_SetItem(*p_kwargs, keyword, value) < 0) {
goto latefail;
}
}
} else {
goto latefail;
}
}
}
}
return 1;
/* Handle failures that have happened after we have tried to
* create *args and **kwargs, if they exist. */
latefail:
if (p_args) {
Py_XDECREF(*p_args);
}
if (p_kwargs) {
Py_XDECREF(*p_kwargs);
}
return 0;
}
static void
skipitem_fast(const char **p_format, va_list *p_va)
{
const char *format = *p_format;
char c = *format++;
if (p_va != NULL) {
(void) va_arg(*p_va, PyObject **);
}
*p_format = format;
}
#endif

View file

@ -0,0 +1,13 @@
#include <Python.h>
#include "CPy.h"
struct ExcDummyStruct _CPy_ExcDummyStruct = { PyObject_HEAD_INIT(NULL) };
PyObject *_CPy_ExcDummy = (PyObject *)&_CPy_ExcDummyStruct;
// Because its dynamic linker is more restricted than linux/OS X,
// Windows doesn't allow initializing globals with values from
// other dynamic libraries. This means we need to initialize
// things at load time.
void CPy_Init(void) {
_CPy_ExcDummyStruct.ob_base.ob_type = &PyBaseObject_Type;
}

View file

@ -0,0 +1,494 @@
// Int primitive operations (tagged arbitrary-precision integers)
//
// These are registered in mypyc.primitives.int_ops.
#include <Python.h>
#include "CPy.h"
#ifndef _WIN32
// On 64-bit Linux and macOS, ssize_t and long are both 64 bits, and
// PyLong_FromLong is faster than PyLong_FromSsize_t, so use the faster one
#define CPyLong_FromSsize_t PyLong_FromLong
#else
// On 64-bit Windows, ssize_t is 64 bits but long is 32 bits, so we
// can't use the above trick
#define CPyLong_FromSsize_t PyLong_FromSsize_t
#endif
CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) {
// We use a Python object if the value shifted left by 1 is too
// large for Py_ssize_t
if (unlikely(CPyTagged_TooBig(value))) {
PyObject *object = PyLong_FromSsize_t(value);
return ((CPyTagged)object) | CPY_INT_TAG;
} else {
return value << 1;
}
}
CPyTagged CPyTagged_FromObject(PyObject *object) {
int overflow;
// The overflow check knows about CPyTagged's width
Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow);
if (unlikely(overflow != 0)) {
Py_INCREF(object);
return ((CPyTagged)object) | CPY_INT_TAG;
} else {
return value << 1;
}
}
CPyTagged CPyTagged_StealFromObject(PyObject *object) {
int overflow;
// The overflow check knows about CPyTagged's width
Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow);
if (unlikely(overflow != 0)) {
return ((CPyTagged)object) | CPY_INT_TAG;
} else {
Py_DECREF(object);
return value << 1;
}
}
CPyTagged CPyTagged_BorrowFromObject(PyObject *object) {
int overflow;
// The overflow check knows about CPyTagged's width
Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow);
if (unlikely(overflow != 0)) {
return ((CPyTagged)object) | CPY_INT_TAG;
} else {
return value << 1;
}
}
PyObject *CPyTagged_AsObject(CPyTagged x) {
PyObject *value;
if (unlikely(CPyTagged_CheckLong(x))) {
value = CPyTagged_LongAsObject(x);
Py_INCREF(value);
} else {
value = CPyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x));
if (value == NULL) {
CPyError_OutOfMemory();
}
}
return value;
}
PyObject *CPyTagged_StealAsObject(CPyTagged x) {
PyObject *value;
if (unlikely(CPyTagged_CheckLong(x))) {
value = CPyTagged_LongAsObject(x);
} else {
value = CPyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x));
if (value == NULL) {
CPyError_OutOfMemory();
}
}
return value;
}
Py_ssize_t CPyTagged_AsSsize_t(CPyTagged x) {
if (likely(CPyTagged_CheckShort(x))) {
return CPyTagged_ShortAsSsize_t(x);
} else {
return PyLong_AsSsize_t(CPyTagged_LongAsObject(x));
}
}
CPy_NOINLINE
void CPyTagged_IncRef(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
Py_INCREF(CPyTagged_LongAsObject(x));
}
}
CPy_NOINLINE
void CPyTagged_DecRef(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
Py_DECREF(CPyTagged_LongAsObject(x));
}
}
CPy_NOINLINE
void CPyTagged_XDecRef(CPyTagged x) {
if (unlikely(CPyTagged_CheckLong(x))) {
Py_XDECREF(CPyTagged_LongAsObject(x));
}
}
CPyTagged CPyTagged_Negate(CPyTagged num) {
if (CPyTagged_CheckShort(num)
&& num != (CPyTagged) ((Py_ssize_t)1 << (CPY_INT_BITS - 1))) {
// The only possibility of an overflow error happening when negating a short is if we
// attempt to negate the most negative number.
return -num;
}
PyObject *num_obj = CPyTagged_AsObject(num);
PyObject *result = PyNumber_Negative(num_obj);
if (result == NULL) {
CPyError_OutOfMemory();
}
Py_DECREF(num_obj);
return CPyTagged_StealFromObject(result);
}
CPyTagged CPyTagged_Add(CPyTagged left, CPyTagged right) {
// TODO: Use clang/gcc extension __builtin_saddll_overflow instead.
if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) {
CPyTagged sum = left + right;
if (likely(!CPyTagged_IsAddOverflow(sum, left, right))) {
return sum;
}
}
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Add(left_obj, right_obj);
if (result == NULL) {
CPyError_OutOfMemory();
}
Py_DECREF(left_obj);
Py_DECREF(right_obj);
return CPyTagged_StealFromObject(result);
}
CPyTagged CPyTagged_Subtract(CPyTagged left, CPyTagged right) {
// TODO: Use clang/gcc extension __builtin_saddll_overflow instead.
if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) {
CPyTagged diff = left - right;
if (likely(!CPyTagged_IsSubtractOverflow(diff, left, right))) {
return diff;
}
}
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Subtract(left_obj, right_obj);
if (result == NULL) {
CPyError_OutOfMemory();
}
Py_DECREF(left_obj);
Py_DECREF(right_obj);
return CPyTagged_StealFromObject(result);
}
CPyTagged CPyTagged_Multiply(CPyTagged left, CPyTagged right) {
// TODO: Consider using some clang/gcc extension
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) {
if (!CPyTagged_IsMultiplyOverflow(left, right)) {
return left * CPyTagged_ShortAsSsize_t(right);
}
}
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Multiply(left_obj, right_obj);
if (result == NULL) {
CPyError_OutOfMemory();
}
Py_DECREF(left_obj);
Py_DECREF(right_obj);
return CPyTagged_StealFromObject(result);
}
CPyTagged CPyTagged_FloorDivide(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left)
&& CPyTagged_CheckShort(right)
&& !CPyTagged_MaybeFloorDivideFault(left, right)) {
Py_ssize_t result = CPyTagged_ShortAsSsize_t(left) / CPyTagged_ShortAsSsize_t(right);
if (((Py_ssize_t)left < 0) != (((Py_ssize_t)right) < 0)) {
if (result * right != left) {
// Round down
result--;
}
}
return result << 1;
}
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_FloorDivide(left_obj, right_obj);
Py_DECREF(left_obj);
Py_DECREF(right_obj);
// Handle exceptions honestly because it could be ZeroDivisionError
if (result == NULL) {
return CPY_INT_TAG;
} else {
return CPyTagged_StealFromObject(result);
}
}
CPyTagged CPyTagged_Remainder(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)
&& !CPyTagged_MaybeRemainderFault(left, right)) {
Py_ssize_t result = (Py_ssize_t)left % (Py_ssize_t)right;
if (((Py_ssize_t)right < 0) != ((Py_ssize_t)left < 0) && result != 0) {
result += right;
}
return result;
}
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Remainder(left_obj, right_obj);
Py_DECREF(left_obj);
Py_DECREF(right_obj);
// Handle exceptions honestly because it could be ZeroDivisionError
if (result == NULL) {
return CPY_INT_TAG;
} else {
return CPyTagged_StealFromObject(result);
}
}
bool CPyTagged_IsEq_(CPyTagged left, CPyTagged right) {
if (CPyTagged_CheckShort(right)) {
return false;
} else {
int result = PyObject_RichCompareBool(CPyTagged_LongAsObject(left),
CPyTagged_LongAsObject(right), Py_EQ);
if (result == -1) {
CPyError_OutOfMemory();
}
return result;
}
}
bool CPyTagged_IsLt_(CPyTagged left, CPyTagged right) {
PyObject *left_obj = CPyTagged_AsObject(left);
PyObject *right_obj = CPyTagged_AsObject(right);
int result = PyObject_RichCompareBool(left_obj, right_obj, Py_LT);
Py_DECREF(left_obj);
Py_DECREF(right_obj);
if (result == -1) {
CPyError_OutOfMemory();
}
return result;
}
PyObject *CPyLong_FromStrWithBase(PyObject *o, CPyTagged base) {
Py_ssize_t base_size_t = CPyTagged_AsSsize_t(base);
return PyLong_FromUnicodeObject(o, base_size_t);
}
PyObject *CPyLong_FromStr(PyObject *o) {
CPyTagged base = CPyTagged_FromSsize_t(10);
return CPyLong_FromStrWithBase(o, base);
}
PyObject *CPyLong_FromFloat(PyObject *o) {
if (PyLong_Check(o)) {
CPy_INCREF(o);
return o;
} else {
return PyLong_FromDouble(PyFloat_AS_DOUBLE(o));
}
}
PyObject *CPyBool_Str(bool b) {
return PyObject_Str(b ? Py_True : Py_False);
}
static void CPyLong_NormalizeUnsigned(PyLongObject *v) {
Py_ssize_t i = v->ob_base.ob_size;
while (i > 0 && v->ob_digit[i - 1] == 0)
i--;
v->ob_base.ob_size = i;
}
// Bitwise op '&', '|' or '^' using the generic (slow) API
static CPyTagged GenericBitwiseOp(CPyTagged a, CPyTagged b, char op) {
PyObject *aobj = CPyTagged_AsObject(a);
PyObject *bobj = CPyTagged_AsObject(b);
PyObject *r;
if (op == '&') {
r = PyNumber_And(aobj, bobj);
} else if (op == '|') {
r = PyNumber_Or(aobj, bobj);
} else {
r = PyNumber_Xor(aobj, bobj);
}
if (unlikely(r == NULL)) {
CPyError_OutOfMemory();
}
Py_DECREF(aobj);
Py_DECREF(bobj);
return CPyTagged_StealFromObject(r);
}
// Return pointer to digits of a PyLong object. If it's a short
// integer, place digits in the buffer buf instead to avoid memory
// allocation (it's assumed to be big enough). Return the number of
// digits in *size. *size is negative if the integer is negative.
static digit *GetIntDigits(CPyTagged n, Py_ssize_t *size, digit *buf) {
if (CPyTagged_CheckShort(n)) {
Py_ssize_t val = CPyTagged_ShortAsSsize_t(n);
bool neg = val < 0;
int len = 1;
if (neg) {
val = -val;
}
buf[0] = val & PyLong_MASK;
if (val > PyLong_MASK) {
val >>= PyLong_SHIFT;
buf[1] = val & PyLong_MASK;
if (val > PyLong_MASK) {
buf[2] = val >> PyLong_SHIFT;
len = 3;
} else {
len = 2;
}
}
*size = neg ? -len : len;
return buf;
} else {
PyLongObject *obj = (PyLongObject *)CPyTagged_LongAsObject(n);
*size = obj->ob_base.ob_size;
return obj->ob_digit;
}
}
// Shared implementation of bitwise '&', '|' and '^' (specified by op) for at least
// one long operand. This is somewhat optimized for performance.
static CPyTagged BitwiseLongOp(CPyTagged a, CPyTagged b, char op) {
// Directly access the digits, as there is no fast C API function for this.
digit abuf[3];
digit bbuf[3];
Py_ssize_t asize;
Py_ssize_t bsize;
digit *adigits = GetIntDigits(a, &asize, abuf);
digit *bdigits = GetIntDigits(b, &bsize, bbuf);
PyLongObject *r;
if (unlikely(asize < 0 || bsize < 0)) {
// Negative operand. This is slower, but bitwise ops on them are pretty rare.
return GenericBitwiseOp(a, b, op);
}
// Optimized implementation for two non-negative integers.
// Swap a and b as needed to ensure a is no longer than b.
if (asize > bsize) {
digit *tmp = adigits;
adigits = bdigits;
bdigits = tmp;
Py_ssize_t tmp_size = asize;
asize = bsize;
bsize = tmp_size;
}
r = _PyLong_New(op == '&' ? asize : bsize);
if (unlikely(r == NULL)) {
CPyError_OutOfMemory();
}
Py_ssize_t i;
if (op == '&') {
for (i = 0; i < asize; i++) {
r->ob_digit[i] = adigits[i] & bdigits[i];
}
} else {
if (op == '|') {
for (i = 0; i < asize; i++) {
r->ob_digit[i] = adigits[i] | bdigits[i];
}
} else {
for (i = 0; i < asize; i++) {
r->ob_digit[i] = adigits[i] ^ bdigits[i];
}
}
for (; i < bsize; i++) {
r->ob_digit[i] = bdigits[i];
}
}
CPyLong_NormalizeUnsigned(r);
return CPyTagged_StealFromObject((PyObject *)r);
}
// Bitwise '&'
CPyTagged CPyTagged_And(CPyTagged left, CPyTagged right) {
if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) {
return left & right;
}
return BitwiseLongOp(left, right, '&');
}
// Bitwise '|'
CPyTagged CPyTagged_Or(CPyTagged left, CPyTagged right) {
if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) {
return left | right;
}
return BitwiseLongOp(left, right, '|');
}
// Bitwise '^'
CPyTagged CPyTagged_Xor(CPyTagged left, CPyTagged right) {
if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) {
return left ^ right;
}
return BitwiseLongOp(left, right, '^');
}
// Bitwise '~'
CPyTagged CPyTagged_Invert(CPyTagged num) {
if (likely(CPyTagged_CheckShort(num) && num != CPY_TAGGED_ABS_MIN)) {
return ~num & ~CPY_INT_TAG;
} else {
PyObject *obj = CPyTagged_AsObject(num);
PyObject *result = PyNumber_Invert(obj);
if (unlikely(result == NULL)) {
CPyError_OutOfMemory();
}
Py_DECREF(obj);
return CPyTagged_StealFromObject(result);
}
}
// Bitwise '>>'
CPyTagged CPyTagged_Rshift(CPyTagged left, CPyTagged right) {
if (likely(CPyTagged_CheckShort(left)
&& CPyTagged_CheckShort(right)
&& (Py_ssize_t)right >= 0)) {
CPyTagged count = CPyTagged_ShortAsSsize_t(right);
if (unlikely(count >= CPY_INT_BITS)) {
if ((Py_ssize_t)left >= 0) {
return 0;
} else {
return CPyTagged_ShortFromInt(-1);
}
}
return ((Py_ssize_t)left >> count) & ~CPY_INT_TAG;
} else {
// Long integer or negative shift -- use generic op
PyObject *lobj = CPyTagged_AsObject(left);
PyObject *robj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Rshift(lobj, robj);
Py_DECREF(lobj);
Py_DECREF(robj);
if (result == NULL) {
// Propagate error (could be negative shift count)
return CPY_INT_TAG;
}
return CPyTagged_StealFromObject(result);
}
}
static inline bool IsShortLshiftOverflow(Py_ssize_t short_int, Py_ssize_t shift) {
return ((Py_ssize_t)(short_int << shift) >> shift) != short_int;
}
// Bitwise '<<'
CPyTagged CPyTagged_Lshift(CPyTagged left, CPyTagged right) {
if (likely(CPyTagged_CheckShort(left)
&& CPyTagged_CheckShort(right)
&& (Py_ssize_t)right >= 0
&& right < CPY_INT_BITS * 2)) {
CPyTagged shift = CPyTagged_ShortAsSsize_t(right);
if (!IsShortLshiftOverflow(left, shift))
// Short integers, no overflow
return left << shift;
}
// Long integer or out of range shift -- use generic op
PyObject *lobj = CPyTagged_AsObject(left);
PyObject *robj = CPyTagged_AsObject(right);
PyObject *result = PyNumber_Lshift(lobj, robj);
Py_DECREF(lobj);
Py_DECREF(robj);
if (result == NULL) {
// Propagate error (could be negative shift count)
return CPY_INT_TAG;
}
return CPyTagged_StealFromObject(result);
}

View file

@ -0,0 +1,228 @@
// List primitive operations
//
// These are registered in mypyc.primitives.list_ops.
#include <Python.h>
#include "CPy.h"
PyObject *CPyList_Build(Py_ssize_t len, ...) {
Py_ssize_t i;
PyObject *res = PyList_New(len);
if (res == NULL) {
return NULL;
}
va_list args;
va_start(args, len);
for (i = 0; i < len; i++) {
// Steals the reference
PyObject *value = va_arg(args, PyObject *);
PyList_SET_ITEM(res, i, value);
}
va_end(args);
return res;
}
PyObject *CPyList_GetItemUnsafe(PyObject *list, CPyTagged index) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
PyObject *result = PyList_GET_ITEM(list, n);
Py_INCREF(result);
return result;
}
PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = PyList_GET_SIZE(list);
if (n >= 0) {
if (n >= size) {
PyErr_SetString(PyExc_IndexError, "list index out of range");
return NULL;
}
} else {
n += size;
if (n < 0) {
PyErr_SetString(PyExc_IndexError, "list index out of range");
return NULL;
}
}
PyObject *result = PyList_GET_ITEM(list, n);
Py_INCREF(result);
return result;
}
PyObject *CPyList_GetItem(PyObject *list, CPyTagged index) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = PyList_GET_SIZE(list);
if (n >= 0) {
if (n >= size) {
PyErr_SetString(PyExc_IndexError, "list index out of range");
return NULL;
}
} else {
n += size;
if (n < 0) {
PyErr_SetString(PyExc_IndexError, "list index out of range");
return NULL;
}
}
PyObject *result = PyList_GET_ITEM(list, n);
Py_INCREF(result);
return result;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
}
bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = PyList_GET_SIZE(list);
if (n >= 0) {
if (n >= size) {
PyErr_SetString(PyExc_IndexError, "list assignment index out of range");
return false;
}
} else {
n += size;
if (n < 0) {
PyErr_SetString(PyExc_IndexError, "list assignment index out of range");
return false;
}
}
// PyList_SET_ITEM doesn't decref the old element, so we do
Py_DECREF(PyList_GET_ITEM(list, n));
// N.B: Steals reference
PyList_SET_ITEM(list, n, value);
return true;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return false;
}
}
// This function should only be used to fill in brand new lists.
bool CPyList_SetItemUnsafe(PyObject *list, CPyTagged index, PyObject *value) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
PyList_SET_ITEM(list, n, value);
return true;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return false;
}
}
PyObject *CPyList_PopLast(PyObject *obj)
{
// I tried a specalized version of pop_impl for just removing the
// last element and it wasn't any faster in microbenchmarks than
// the generic one so I ditched it.
return list_pop_impl((PyListObject *)obj, -1);
}
PyObject *CPyList_Pop(PyObject *obj, CPyTagged index)
{
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
return list_pop_impl((PyListObject *)obj, n);
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
}
CPyTagged CPyList_Count(PyObject *obj, PyObject *value)
{
return list_count((PyListObject *)obj, value);
}
int CPyList_Insert(PyObject *list, CPyTagged index, PyObject *value)
{
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
return PyList_Insert(list, n, value);
}
// The max range doesn't exactly coincide with ssize_t, but we still
// want to keep the error message compatible with CPython.
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return -1;
}
PyObject *CPyList_Extend(PyObject *o1, PyObject *o2) {
return _PyList_Extend((PyListObject *)o1, o2);
}
// Return -2 or error, -1 if not found, or index of first match otherwise.
static Py_ssize_t _CPyList_Find(PyObject *list, PyObject *obj) {
Py_ssize_t i;
for (i = 0; i < Py_SIZE(list); i++) {
PyObject *item = PyList_GET_ITEM(list, i);
Py_INCREF(item);
int cmp = PyObject_RichCompareBool(item, obj, Py_EQ);
Py_DECREF(item);
if (cmp != 0) {
if (cmp > 0) {
return i;
} else {
return -2;
}
}
}
return -1;
}
int CPyList_Remove(PyObject *list, PyObject *obj) {
Py_ssize_t index = _CPyList_Find(list, obj);
if (index == -2) {
return -1;
}
if (index == -1) {
PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
return -1;
}
return PyList_SetSlice(list, index, index + 1, NULL);
}
CPyTagged CPyList_Index(PyObject *list, PyObject *obj) {
Py_ssize_t index = _CPyList_Find(list, obj);
if (index == -2) {
return CPY_INT_TAG;
}
if (index == -1) {
PyErr_SetString(PyExc_ValueError, "value is not in list");
return CPY_INT_TAG;
}
return index << 1;
}
PyObject *CPySequence_Multiply(PyObject *seq, CPyTagged t_size) {
Py_ssize_t size = CPyTagged_AsSsize_t(t_size);
if (size == -1 && PyErr_Occurred()) {
return NULL;
}
return PySequence_Repeat(seq, size);
}
PyObject *CPySequence_RMultiply(CPyTagged t_size, PyObject *seq) {
return CPySequence_Multiply(seq, t_size);
}
PyObject *CPyList_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
if (likely(PyList_CheckExact(obj)
&& CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end))) {
Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start);
Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end);
if (startn < 0) {
startn += PyList_GET_SIZE(obj);
}
if (endn < 0) {
endn += PyList_GET_SIZE(obj);
}
return PyList_GetSlice(obj, startn, endn);
}
return CPyObject_GetSlice(obj, start, end);
}

View file

@ -0,0 +1,785 @@
// Misc primitive operations + C helpers
//
// These are registered in mypyc.primitives.misc_ops.
#include <Python.h>
#include "CPy.h"
PyObject *CPy_GetCoro(PyObject *obj)
{
// If the type has an __await__ method, call it,
// otherwise, fallback to calling __iter__.
PyAsyncMethods* async_struct = Py_TYPE(obj)->tp_as_async;
if (async_struct != NULL && async_struct->am_await != NULL) {
return (async_struct->am_await)(obj);
} else {
// TODO: We should check that the type is a generator decorated with
// asyncio.coroutine
return PyObject_GetIter(obj);
}
}
PyObject *CPyIter_Send(PyObject *iter, PyObject *val)
{
// Do a send, or a next if second arg is None.
// (This behavior is to match the PEP 380 spec for yield from.)
_Py_IDENTIFIER(send);
if (Py_IsNone(val)) {
return CPyIter_Next(iter);
} else {
return _PyObject_CallMethodIdOneArg(iter, &PyId_send, val);
}
}
// A somewhat hairy implementation of specifically most of the error handling
// in `yield from` error handling. The point here is to reduce code size.
//
// This implements most of the bodies of the `except` blocks in the
// pseudocode in PEP 380.
//
// Returns true (1) if a StopIteration was received and we should return.
// Returns false (0) if a value should be yielded.
// In both cases the value is stored in outp.
// Signals an error (2) if the an exception should be propagated.
int CPy_YieldFromErrorHandle(PyObject *iter, PyObject **outp)
{
_Py_IDENTIFIER(close);
_Py_IDENTIFIER(throw);
PyObject *exc_type = CPy_ExcState()->exc_type;
PyObject *type, *value, *traceback;
PyObject *_m;
PyObject *res;
*outp = NULL;
if (PyErr_GivenExceptionMatches(exc_type, PyExc_GeneratorExit)) {
_m = _PyObject_GetAttrId(iter, &PyId_close);
if (_m) {
res = PyObject_CallFunctionObjArgs(_m, NULL);
Py_DECREF(_m);
if (!res)
return 2;
Py_DECREF(res);
} else if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
} else {
return 2;
}
} else {
_m = _PyObject_GetAttrId(iter, &PyId_throw);
if (_m) {
_CPy_GetExcInfo(&type, &value, &traceback);
res = PyObject_CallFunctionObjArgs(_m, type, value, traceback, NULL);
Py_DECREF(type);
Py_DECREF(value);
Py_DECREF(traceback);
Py_DECREF(_m);
if (res) {
*outp = res;
return 0;
} else {
res = CPy_FetchStopIterationValue();
if (res) {
*outp = res;
return 1;
}
}
} else if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
} else {
return 2;
}
}
CPy_Reraise();
return 2;
}
PyObject *CPy_FetchStopIterationValue(void)
{
PyObject *val = NULL;
_PyGen_FetchStopIterationValue(&val);
return val;
}
static bool _CPy_IsSafeMetaClass(PyTypeObject *metaclass) {
// mypyc classes can't work with metaclasses in
// general. Through some various nasty hacks we *do*
// manage to work with TypingMeta and its friends.
if (metaclass == &PyType_Type)
return true;
PyObject *module = PyObject_GetAttrString((PyObject *)metaclass, "__module__");
if (!module) {
PyErr_Clear();
return false;
}
bool matches = false;
if (PyUnicode_CompareWithASCIIString(module, "typing") == 0 &&
(strcmp(metaclass->tp_name, "TypingMeta") == 0
|| strcmp(metaclass->tp_name, "GenericMeta") == 0
|| strcmp(metaclass->tp_name, "_ProtocolMeta") == 0)) {
matches = true;
} else if (PyUnicode_CompareWithASCIIString(module, "typing_extensions") == 0 &&
strcmp(metaclass->tp_name, "_ProtocolMeta") == 0) {
matches = true;
} else if (PyUnicode_CompareWithASCIIString(module, "abc") == 0 &&
strcmp(metaclass->tp_name, "ABCMeta") == 0) {
matches = true;
}
Py_DECREF(module);
return matches;
}
// Create a heap type based on a template non-heap type.
// This is super hacky and maybe we should suck it up and use PyType_FromSpec instead.
// We allow bases to be NULL to represent just inheriting from object.
// We don't support NULL bases and a non-type metaclass.
PyObject *CPyType_FromTemplate(PyObject *template,
PyObject *orig_bases,
PyObject *modname) {
PyTypeObject *template_ = (PyTypeObject *)template;
PyHeapTypeObject *t = NULL;
PyTypeObject *dummy_class = NULL;
PyObject *name = NULL;
PyObject *bases = NULL;
PyObject *slots;
// If the type of the class (the metaclass) is NULL, we default it
// to being type. (This allows us to avoid needing to initialize
// it explicitly on windows.)
if (!Py_TYPE(template_)) {
Py_SET_TYPE(template_, &PyType_Type);
}
PyTypeObject *metaclass = Py_TYPE(template_);
if (orig_bases) {
bases = update_bases(orig_bases);
// update_bases doesn't increment the refcount if nothing changes,
// so we do it to make sure we have distinct "references" to both
if (bases == orig_bases)
Py_INCREF(bases);
// Find the appropriate metaclass from our base classes. We
// care about this because Generic uses a metaclass prior to
// Python 3.7.
metaclass = _PyType_CalculateMetaclass(metaclass, bases);
if (!metaclass)
goto error;
if (!_CPy_IsSafeMetaClass(metaclass)) {
PyErr_SetString(PyExc_TypeError, "mypyc classes can't have a metaclass");
goto error;
}
}
name = PyUnicode_FromString(template_->tp_name);
if (!name)
goto error;
// If there is a metaclass other than type, we would like to call
// its __new__ function. Unfortunately there doesn't seem to be a
// good way to mix a C extension class and creating it via a
// metaclass. We need to do it anyways, though, in order to
// support subclassing Generic[T] prior to Python 3.7.
//
// We solve this with a kind of atrocious hack: create a parallel
// class using the metaclass, determine the bases of the real
// class by pulling them out of the parallel class, creating the
// real class, and then merging its dict back into the original
// class. There are lots of cases where this won't really work,
// but for the case of GenericMeta setting a bunch of properties
// on the class we should be fine.
if (metaclass != &PyType_Type) {
assert(bases && "non-type metaclasses require non-NULL bases");
PyObject *ns = PyDict_New();
if (!ns)
goto error;
if (bases != orig_bases) {
if (PyDict_SetItemString(ns, "__orig_bases__", orig_bases) < 0)
goto error;
}
dummy_class = (PyTypeObject *)PyObject_CallFunctionObjArgs(
(PyObject *)metaclass, name, bases, ns, NULL);
Py_DECREF(ns);
if (!dummy_class)
goto error;
Py_DECREF(bases);
bases = dummy_class->tp_bases;
Py_INCREF(bases);
}
// Allocate the type and then copy the main stuff in.
t = (PyHeapTypeObject*)PyType_GenericAlloc(&PyType_Type, 0);
if (!t)
goto error;
memcpy((char *)t + sizeof(PyVarObject),
(char *)template_ + sizeof(PyVarObject),
sizeof(PyTypeObject) - sizeof(PyVarObject));
if (bases != orig_bases) {
if (PyObject_SetAttrString((PyObject *)t, "__orig_bases__", orig_bases) < 0)
goto error;
}
// Having tp_base set is I think required for stuff to get
// inherited in PyType_Ready, which we needed for subclassing
// BaseException. XXX: Taking the first element is wrong I think though.
if (bases) {
t->ht_type.tp_base = (PyTypeObject *)PyTuple_GET_ITEM(bases, 0);
Py_INCREF((PyObject *)t->ht_type.tp_base);
}
t->ht_name = name;
Py_INCREF(name);
t->ht_qualname = name;
t->ht_type.tp_bases = bases;
// references stolen so NULL these out
bases = name = NULL;
if (PyType_Ready((PyTypeObject *)t) < 0)
goto error;
assert(t->ht_type.tp_base != NULL);
// XXX: This is a terrible hack to work around a cpython check on
// the mro. It was needed for mypy.stats. I need to investigate
// what is actually going on here.
Py_INCREF(metaclass);
Py_SET_TYPE(t, metaclass);
if (dummy_class) {
if (PyDict_Merge(t->ht_type.tp_dict, dummy_class->tp_dict, 0) != 0)
goto error;
// This is the *really* tasteless bit. GenericMeta's __new__
// in certain versions of typing sets _gorg to point back to
// the class. We need to override it to keep it from pointing
// to the proxy.
if (PyDict_SetItemString(t->ht_type.tp_dict, "_gorg", (PyObject *)t) < 0)
goto error;
}
// Reject anything that would give us a nontrivial __slots__,
// because the layout will conflict
slots = PyObject_GetAttrString((PyObject *)t, "__slots__");
if (slots) {
// don't fail on an empty __slots__
int is_true = PyObject_IsTrue(slots);
Py_DECREF(slots);
if (is_true > 0)
PyErr_SetString(PyExc_TypeError, "mypyc classes can't have __slots__");
if (is_true != 0)
goto error;
} else {
PyErr_Clear();
}
if (PyObject_SetAttrString((PyObject *)t, "__module__", modname) < 0)
goto error;
if (init_subclass((PyTypeObject *)t, NULL))
goto error;
Py_XDECREF(dummy_class);
return (PyObject *)t;
error:
Py_XDECREF(t);
Py_XDECREF(bases);
Py_XDECREF(dummy_class);
Py_XDECREF(name);
return NULL;
}
static int _CPy_UpdateObjFromDict(PyObject *obj, PyObject *dict)
{
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(dict, &pos, &key, &value)) {
if (PyObject_SetAttr(obj, key, value) != 0) {
return -1;
}
}
return 0;
}
/* Support for our partial built-in support for dataclasses.
*
* Take a class we want to make a dataclass, remove any descriptors
* for annotated attributes, swap in the actual values of the class
* variables invoke dataclass, and then restore all of the
* descriptors.
*
* The purpose of all this is that dataclasses uses the values of
* class variables to drive which attributes are required and what the
* default values/factories are for optional attributes. This means
* that the class dict needs to contain those values instead of getset
* descriptors for the attributes when we invoke dataclass.
*
* We need to remove descriptors for attributes even when there is no
* default value for them, or else dataclass will think the descriptor
* is the default value. We remove only the attributes, since we don't
* want dataclasses to try generating functions when they are already
* implemented.
*
* Args:
* dataclass_dec: The decorator to apply
* tp: The class we are making a dataclass
* dict: The dictionary containing values that dataclasses needs
* annotations: The type annotation dictionary
*/
int
CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
PyObject *dict, PyObject *annotations) {
PyTypeObject *ttp = (PyTypeObject *)tp;
Py_ssize_t pos;
PyObject *res;
/* Make a copy of the original class __dict__ */
PyObject *orig_dict = PyDict_Copy(ttp->tp_dict);
if (!orig_dict) {
goto fail;
}
/* Delete anything that had an annotation */
pos = 0;
PyObject *key;
while (PyDict_Next(annotations, &pos, &key, NULL)) {
if (PyObject_DelAttr(tp, key) != 0) {
goto fail;
}
}
/* Copy in all the attributes that we want dataclass to see */
if (_CPy_UpdateObjFromDict(tp, dict) != 0) {
goto fail;
}
/* Run the @dataclass descriptor */
res = PyObject_CallFunctionObjArgs(dataclass_dec, tp, NULL);
if (!res) {
goto fail;
}
Py_DECREF(res);
/* Copy back the original contents of the dict */
if (_CPy_UpdateObjFromDict(tp, orig_dict) != 0) {
goto fail;
}
Py_DECREF(orig_dict);
return 1;
fail:
Py_XDECREF(orig_dict);
return 0;
}
// Support for pickling; reusable getstate and setstate functions
PyObject *
CPyPickle_SetState(PyObject *obj, PyObject *state)
{
if (_CPy_UpdateObjFromDict(obj, state) != 0) {
return NULL;
}
Py_RETURN_NONE;
}
PyObject *
CPyPickle_GetState(PyObject *obj)
{
PyObject *attrs = NULL, *state = NULL;
attrs = PyObject_GetAttrString((PyObject *)Py_TYPE(obj), "__mypyc_attrs__");
if (!attrs) {
goto fail;
}
if (!PyTuple_Check(attrs)) {
PyErr_SetString(PyExc_TypeError, "__mypyc_attrs__ is not a tuple");
goto fail;
}
state = PyDict_New();
if (!state) {
goto fail;
}
// Collect all the values of attributes in __mypyc_attrs__
// Attributes that are missing we just ignore
int i;
for (i = 0; i < PyTuple_GET_SIZE(attrs); i++) {
PyObject *key = PyTuple_GET_ITEM(attrs, i);
PyObject *value = PyObject_GetAttr(obj, key);
if (!value) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
continue;
}
goto fail;
}
int result = PyDict_SetItem(state, key, value);
Py_DECREF(value);
if (result != 0) {
goto fail;
}
}
Py_DECREF(attrs);
return state;
fail:
Py_XDECREF(attrs);
Py_XDECREF(state);
return NULL;
}
CPyTagged CPyTagged_Id(PyObject *o) {
return CPyTagged_FromSsize_t((Py_ssize_t)o);
}
#define MAX_INT_CHARS 22
#define _PyUnicode_LENGTH(op) \
(((PyASCIIObject *)(op))->length)
// using snprintf or PyUnicode_FromFormat was way slower than
// boxing the int and calling PyObject_Str on it, so we implement our own
static int fmt_ssize_t(char *out, Py_ssize_t n) {
bool neg = n < 0;
if (neg) n = -n;
// buf gets filled backward and then we copy it forward
char buf[MAX_INT_CHARS];
int i = 0;
do {
buf[i] = (n % 10) + '0';
n /= 10;
i++;
} while (n);
int len = i;
int j = 0;
if (neg) {
out[j++] = '-';
len++;
}
for (; j < len; j++, i--) {
out[j] = buf[i-1];
}
out[j] = '\0';
return len;
}
static PyObject *CPyTagged_ShortToStr(Py_ssize_t n) {
PyObject *obj = PyUnicode_New(MAX_INT_CHARS, 127);
if (!obj) return NULL;
int len = fmt_ssize_t((char *)PyUnicode_1BYTE_DATA(obj), n);
_PyUnicode_LENGTH(obj) = len;
return obj;
}
PyObject *CPyTagged_Str(CPyTagged n) {
if (CPyTagged_CheckShort(n)) {
return CPyTagged_ShortToStr(CPyTagged_ShortAsSsize_t(n));
} else {
return PyObject_Str(CPyTagged_AsObject(n));
}
}
void CPyDebug_Print(const char *msg) {
printf("%s\n", msg);
fflush(stdout);
}
int CPySequence_CheckUnpackCount(PyObject *sequence, Py_ssize_t expected) {
Py_ssize_t actual = Py_SIZE(sequence);
if (unlikely(actual != expected)) {
if (actual < expected) {
PyErr_Format(PyExc_ValueError, "not enough values to unpack (expected %zd, got %zd)",
expected, actual);
} else {
PyErr_Format(PyExc_ValueError, "too many values to unpack (expected %zd)", expected);
}
return -1;
}
return 0;
}
// Parse an integer (size_t) encoded as a variable-length binary sequence.
static const char *parse_int(const char *s, size_t *len) {
Py_ssize_t n = 0;
while ((unsigned char)*s >= 0x80) {
n = (n << 7) + (*s & 0x7f);
s++;
}
n = (n << 7) | *s++;
*len = n;
return s;
}
// Initialize static constant array of literal values
int CPyStatics_Initialize(PyObject **statics,
const char * const *strings,
const char * const *bytestrings,
const char * const *ints,
const double *floats,
const double *complex_numbers,
const int *tuples) {
PyObject **result = statics;
// Start with some hard-coded values
*result++ = Py_None;
Py_INCREF(Py_None);
*result++ = Py_False;
Py_INCREF(Py_False);
*result++ = Py_True;
Py_INCREF(Py_True);
if (strings) {
for (; **strings != '\0'; strings++) {
size_t num;
const char *data = *strings;
data = parse_int(data, &num);
while (num-- > 0) {
size_t len;
data = parse_int(data, &len);
PyObject *obj = PyUnicode_FromStringAndSize(data, len);
if (obj == NULL) {
return -1;
}
PyUnicode_InternInPlace(&obj);
*result++ = obj;
data += len;
}
}
}
if (bytestrings) {
for (; **bytestrings != '\0'; bytestrings++) {
size_t num;
const char *data = *bytestrings;
data = parse_int(data, &num);
while (num-- > 0) {
size_t len;
data = parse_int(data, &len);
PyObject *obj = PyBytes_FromStringAndSize(data, len);
if (obj == NULL) {
return -1;
}
*result++ = obj;
data += len;
}
}
}
if (ints) {
for (; **ints != '\0'; ints++) {
size_t num;
const char *data = *ints;
data = parse_int(data, &num);
while (num-- > 0) {
char *end;
PyObject *obj = PyLong_FromString(data, &end, 10);
if (obj == NULL) {
return -1;
}
data = end;
data++;
*result++ = obj;
}
}
}
if (floats) {
size_t num = (size_t)*floats++;
while (num-- > 0) {
PyObject *obj = PyFloat_FromDouble(*floats++);
if (obj == NULL) {
return -1;
}
*result++ = obj;
}
}
if (complex_numbers) {
size_t num = (size_t)*complex_numbers++;
while (num-- > 0) {
double real = *complex_numbers++;
double imag = *complex_numbers++;
PyObject *obj = PyComplex_FromDoubles(real, imag);
if (obj == NULL) {
return -1;
}
*result++ = obj;
}
}
if (tuples) {
int num = *tuples++;
while (num-- > 0) {
int num_items = *tuples++;
PyObject *obj = PyTuple_New(num_items);
if (obj == NULL) {
return -1;
}
int i;
for (i = 0; i < num_items; i++) {
PyObject *item = statics[*tuples++];
Py_INCREF(item);
PyTuple_SET_ITEM(obj, i, item);
}
*result++ = obj;
}
}
return 0;
}
// Call super(type(self), self)
PyObject *
CPy_Super(PyObject *builtins, PyObject *self) {
PyObject *super_type = PyObject_GetAttrString(builtins, "super");
if (!super_type)
return NULL;
PyObject *result = PyObject_CallFunctionObjArgs(
super_type, (PyObject*)Py_TYPE(self), self, NULL);
Py_DECREF(super_type);
return result;
}
// This helper function is a simplification of cpython/ceval.c/import_from()
PyObject *CPyImport_ImportFrom(PyObject *module, PyObject *package_name,
PyObject *import_name, PyObject *as_name) {
// check if the imported module has an attribute by that name
PyObject *x = PyObject_GetAttr(module, import_name);
if (x == NULL) {
// if not, attempt to import a submodule with that name
PyObject *fullmodname = PyUnicode_FromFormat("%U.%U", package_name, import_name);
if (fullmodname == NULL) {
goto fail;
}
// The following code is a simplification of cpython/import.c/PyImport_GetModule()
x = PyObject_GetItem(module, fullmodname);
Py_DECREF(fullmodname);
if (x == NULL) {
goto fail;
}
}
return x;
fail:
PyErr_Clear();
PyObject *package_path = PyModule_GetFilenameObject(module);
PyObject *errmsg = PyUnicode_FromFormat("cannot import name %R from %R (%S)",
import_name, package_name, package_path);
// NULL checks for errmsg and package_name done by PyErr_SetImportError.
PyErr_SetImportError(errmsg, package_name, package_path);
Py_DECREF(package_path);
Py_DECREF(errmsg);
return NULL;
}
// From CPython
static PyObject *
CPy_BinopTypeError(PyObject *left, PyObject *right, const char *op) {
PyErr_Format(PyExc_TypeError,
"unsupported operand type(s) for %.100s: "
"'%.100s' and '%.100s'",
op,
Py_TYPE(left)->tp_name,
Py_TYPE(right)->tp_name);
return NULL;
}
PyObject *
CPy_CallReverseOpMethod(PyObject *left,
PyObject *right,
const char *op,
_Py_Identifier *method) {
// Look up reverse method
PyObject *m = _PyObject_GetAttrId(right, method);
if (m == NULL) {
// If reverse method not defined, generate TypeError instead AttributeError
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
CPy_BinopTypeError(left, right, op);
}
return NULL;
}
// Call reverse method
PyObject *result = PyObject_CallOneArg(m, left);
Py_DECREF(m);
return result;
}
PyObject *CPySingledispatch_RegisterFunction(PyObject *singledispatch_func,
PyObject *cls,
PyObject *func) {
PyObject *registry = PyObject_GetAttrString(singledispatch_func, "registry");
PyObject *register_func = NULL;
PyObject *typing = NULL;
PyObject *get_type_hints = NULL;
PyObject *type_hints = NULL;
if (registry == NULL) goto fail;
if (func == NULL) {
// one argument case
if (PyType_Check(cls)) {
// passed a class
// bind cls to the first argument so that register gets called again with both the
// class and the function
register_func = PyObject_GetAttrString(singledispatch_func, "register");
if (register_func == NULL) goto fail;
return PyMethod_New(register_func, cls);
}
// passed a function
PyObject *annotations = PyFunction_GetAnnotations(cls);
const char *invalid_first_arg_msg =
"Invalid first argument to `register()`: %R. "
"Use either `@register(some_class)` or plain `@register` "
"on an annotated function.";
if (annotations == NULL) {
PyErr_Format(PyExc_TypeError, invalid_first_arg_msg, cls);
goto fail;
}
Py_INCREF(annotations);
func = cls;
typing = PyImport_ImportModule("typing");
if (typing == NULL) goto fail;
get_type_hints = PyObject_GetAttrString(typing, "get_type_hints");
type_hints = PyObject_CallOneArg(get_type_hints, func);
PyObject *argname;
Py_ssize_t pos = 0;
if (!PyDict_Next(type_hints, &pos, &argname, &cls)) {
// the functools implementation raises the same type error if annotations is an empty dict
PyErr_Format(PyExc_TypeError, invalid_first_arg_msg, cls);
goto fail;
}
if (!PyType_Check(cls)) {
const char *invalid_annotation_msg = "Invalid annotation for %R. %R is not a class.";
PyErr_Format(PyExc_TypeError, invalid_annotation_msg, argname, cls);
goto fail;
}
}
if (PyDict_SetItem(registry, cls, func) == -1) {
goto fail;
}
// clear the cache so we consider the newly added function when dispatching
PyObject *dispatch_cache = PyObject_GetAttrString(singledispatch_func, "dispatch_cache");
if (dispatch_cache == NULL) goto fail;
PyDict_Clear(dispatch_cache);
Py_INCREF(func);
return func;
fail:
Py_XDECREF(registry);
Py_XDECREF(register_func);
Py_XDECREF(typing);
Py_XDECREF(get_type_hints);
Py_XDECREF(type_hints);
return NULL;
}

View file

@ -0,0 +1,18 @@
#include <Python.h>
PyMODINIT_FUNC
PyInit_{modname}(void)
{{
PyObject *tmp;
if (!(tmp = PyImport_ImportModule("{libname}"))) return NULL;
Py_DECREF(tmp);
void *init_func = PyCapsule_Import("{libname}.init_{full_modname}", 0);
if (!init_func) {{
return NULL;
}}
return ((PyObject *(*)(void))init_func)();
}}
// distutils sometimes spuriously tells cl to export CPyInit___init__,
// so provide that so it chills out
PyMODINIT_FUNC PyInit___init__(void) {{ return PyInit_{modname}(); }}

View file

@ -0,0 +1,66 @@
#ifndef MYPYC_UTIL_H
#define MYPYC_UTIL_H
#include <Python.h>
#include <frameobject.h>
#include <assert.h>
#if defined(__clang__) || defined(__GNUC__)
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
#define CPy_Unreachable() __builtin_unreachable()
#else
#define likely(x) (x)
#define unlikely(x) (x)
#define CPy_Unreachable() abort()
#endif
#if defined(__clang__) || defined(__GNUC__)
#define CPy_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define CPy_NOINLINE __declspec(noinline)
#else
#define CPy_NOINLINE
#endif
// INCREF and DECREF that assert the pointer is not NULL.
// asserts are disabled in release builds so there shouldn't be a perf hit.
// I'm honestly kind of surprised that this isn't done by default.
#define CPy_INCREF(p) do { assert(p); Py_INCREF(p); } while (0)
#define CPy_DECREF(p) do { assert(p); Py_DECREF(p); } while (0)
// Here just for consistency
#define CPy_XDECREF(p) Py_XDECREF(p)
// Tagged integer -- our representation of Python 'int' objects.
// Small enough integers are represented as unboxed integers (shifted
// left by 1); larger integers (larger than 63 bits on a 64-bit
// platform) are stored as a tagged pointer (PyObject *)
// representing a Python int object, with the lowest bit set.
// Tagged integers are always normalized. A small integer *must not*
// have the tag bit set.
typedef size_t CPyTagged;
typedef size_t CPyPtr;
#define CPY_INT_BITS (CHAR_BIT * sizeof(CPyTagged))
#define CPY_TAGGED_MAX (((Py_ssize_t)1 << (CPY_INT_BITS - 2)) - 1)
#define CPY_TAGGED_MIN (-((Py_ssize_t)1 << (CPY_INT_BITS - 2)))
#define CPY_TAGGED_ABS_MIN (0-(size_t)CPY_TAGGED_MIN)
typedef PyObject CPyModule;
// Tag bit used for long integers
#define CPY_INT_TAG 1
typedef void (*CPyVTableItem)(void);
static inline CPyTagged CPyTagged_ShortFromInt(int x) {
return x << 1;
}
static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) {
return x << 1;
}
#endif

View file

@ -0,0 +1,407 @@
// Header file providing new C API functions to old Python versions.
//
// File distributed under the Zero Clause BSD (0BSD) license.
// Copyright Contributors to the pythoncapi_compat project.
//
// Homepage:
// https://github.com/pythoncapi/pythoncapi_compat
//
// Latest version:
// https://raw.githubusercontent.com/pythoncapi/pythoncapi_compat/master/pythoncapi_compat.h
//
// SPDX-License-Identifier: 0BSD
#ifndef PYTHONCAPI_COMPAT
#define PYTHONCAPI_COMPAT
#ifdef __cplusplus
extern "C" {
#endif
#include <Python.h>
#include "frameobject.h" // PyFrameObject, PyFrame_GetBack()
// Compatibility with Visual Studio 2013 and older which don't support
// the inline keyword in C (only in C++): use __inline instead.
#if (defined(_MSC_VER) && _MSC_VER < 1900 \
&& !defined(__cplusplus) && !defined(inline))
# define PYCAPI_COMPAT_INLINE(TYPE static __inline TYPE
#else
# define PYCAPI_COMPAT_STATIC_INLINE(TYPE) static inline TYPE
#endif
// C++ compatibility
#ifdef __cplusplus
# define PYCAPI_COMPAT_CAST(TYPE, EXPR) reinterpret_cast<TYPE>(EXPR)
# define PYCAPI_COMPAT_NULL nullptr
#else
# define PYCAPI_COMPAT_CAST(TYPE, EXPR) (TYPE)(EXPR)
# define PYCAPI_COMPAT_NULL NULL
#endif
// Cast argument to PyObject* type.
#ifndef _PyObject_CAST
# define _PyObject_CAST(op) PYCAPI_COMPAT_CAST(PyObject*, op)
#endif
#ifndef _PyObject_CAST_CONST
# define _PyObject_CAST_CONST(op) PYCAPI_COMPAT_CAST(const PyObject*, op)
#endif
// bpo-42262 added Py_NewRef() to Python 3.10.0a3
#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef)
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
_Py_NewRef(PyObject *obj)
{
Py_INCREF(obj);
return obj;
}
#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj))
#endif
// bpo-42262 added Py_XNewRef() to Python 3.10.0a3
#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef)
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
_Py_XNewRef(PyObject *obj)
{
Py_XINCREF(obj);
return obj;
}
#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj))
#endif
// See https://bugs.python.org/issue42522
#if !defined(_Py_StealRef)
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
__Py_StealRef(PyObject *obj)
{
Py_DECREF(obj);
return obj;
}
#define _Py_StealRef(obj) __Py_StealRef(_PyObject_CAST(obj))
#endif
// See https://bugs.python.org/issue42522
#if !defined(_Py_XStealRef)
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
__Py_XStealRef(PyObject *obj)
{
Py_XDECREF(obj);
return obj;
}
#define _Py_XStealRef(obj) __Py_XStealRef(_PyObject_CAST(obj))
#endif
// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT)
PYCAPI_COMPAT_STATIC_INLINE(void)
_Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt)
{
ob->ob_refcnt = refcnt;
}
#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt)
#endif
// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2.
// It is excluded from the limited C API.
#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API)
#define Py_SETREF(op, op2) \
do { \
PyObject *_py_tmp = _PyObject_CAST(op); \
(op) = (op2); \
Py_DECREF(_py_tmp); \
} while (0)
#define Py_XSETREF(op, op2) \
do { \
PyObject *_py_tmp = _PyObject_CAST(op); \
(op) = (op2); \
Py_XDECREF(_py_tmp); \
} while (0)
#endif
// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse()
// to Python 3.10.0b1.
#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is)
# define Py_Is(x, y) ((x) == (y))
#endif
#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone)
# define Py_IsNone(x) Py_Is(x, Py_None)
#endif
#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsTrue)
# define Py_IsTrue(x) Py_Is(x, Py_True)
#endif
#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsFalse)
# define Py_IsFalse(x) Py_Is(x, Py_False)
#endif
// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE)
PYCAPI_COMPAT_STATIC_INLINE(void)
_Py_SET_TYPE(PyObject *ob, PyTypeObject *type)
{
ob->ob_type = type;
}
#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type)
#endif
// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE)
PYCAPI_COMPAT_STATIC_INLINE(void)
_Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size)
{
ob->ob_size = size;
}
#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size)
#endif
// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1
#if PY_VERSION_HEX < 0x030900B1
PYCAPI_COMPAT_STATIC_INLINE(PyCodeObject*)
PyFrame_GetCode(PyFrameObject *frame)
{
assert(frame != PYCAPI_COMPAT_NULL);
assert(frame->f_code != PYCAPI_COMPAT_NULL);
return PYCAPI_COMPAT_CAST(PyCodeObject*, Py_NewRef(frame->f_code));
}
#endif
PYCAPI_COMPAT_STATIC_INLINE(PyCodeObject*)
_PyFrame_GetCodeBorrow(PyFrameObject *frame)
{
return PYCAPI_COMPAT_CAST(PyCodeObject *,
_Py_StealRef(PyFrame_GetCode(frame)));
}
// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1
#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(PyFrameObject*)
PyFrame_GetBack(PyFrameObject *frame)
{
assert(frame != PYCAPI_COMPAT_NULL);
return PYCAPI_COMPAT_CAST(PyFrameObject*, Py_XNewRef(frame->f_back));
}
#endif
#if !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(PyFrameObject*)
_PyFrame_GetBackBorrow(PyFrameObject *frame)
{
return PYCAPI_COMPAT_CAST(PyFrameObject *,
_Py_XStealRef(PyFrame_GetBack(frame)));
}
#endif
// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5
#if PY_VERSION_HEX < 0x030900A5
PYCAPI_COMPAT_STATIC_INLINE(PyInterpreterState *)
PyThreadState_GetInterpreter(PyThreadState *tstate)
{
assert(tstate != PYCAPI_COMPAT_NULL);
return tstate->interp;
}
#endif
// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1
#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(PyFrameObject*)
PyThreadState_GetFrame(PyThreadState *tstate)
{
assert(tstate != PYCAPI_COMPAT_NULL);
return PYCAPI_COMPAT_CAST(PyFrameObject *, Py_XNewRef(tstate->frame));
}
#endif
#if !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(PyFrameObject*)
_PyThreadState_GetFrameBorrow(PyThreadState *tstate)
{
return PYCAPI_COMPAT_CAST(PyFrameObject*,
_Py_XStealRef(PyThreadState_GetFrame(tstate)));
}
#endif
// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5
#if PY_VERSION_HEX < 0x030900A5
PYCAPI_COMPAT_STATIC_INLINE(PyInterpreterState*)
PyInterpreterState_Get(void)
{
PyThreadState *tstate;
PyInterpreterState *interp;
tstate = PyThreadState_GET();
if (tstate == PYCAPI_COMPAT_NULL) {
Py_FatalError("GIL released (tstate is NULL)");
}
interp = tstate->interp;
if (interp == PYCAPI_COMPAT_NULL) {
Py_FatalError("no current interpreter");
}
return interp;
}
#endif
// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6
#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(uint64_t)
PyThreadState_GetID(PyThreadState *tstate)
{
assert(tstate != PYCAPI_COMPAT_NULL);
return tstate->id;
}
#endif
// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2
#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(void)
PyThreadState_EnterTracing(PyThreadState *tstate)
{
tstate->tracing++;
#if PY_VERSION_HEX >= 0x030A00A1
tstate->cframe->use_tracing = 0;
#else
tstate->use_tracing = 0;
#endif
}
#endif
// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2
#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(void)
PyThreadState_LeaveTracing(PyThreadState *tstate)
{
int use_tracing = (tstate->c_tracefunc != PYCAPI_COMPAT_NULL
|| tstate->c_profilefunc != PYCAPI_COMPAT_NULL);
tstate->tracing--;
#if PY_VERSION_HEX >= 0x030A00A1
tstate->cframe->use_tracing = use_tracing;
#else
tstate->use_tracing = use_tracing;
#endif
}
#endif
// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1
#if PY_VERSION_HEX < 0x030900A1
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
PyObject_CallNoArgs(PyObject *func)
{
return PyObject_CallFunctionObjArgs(func, NULL);
}
#endif
// bpo-39245 made PyObject_CallOneArg() public (previously called
// _PyObject_CallOneArg) in Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4
PYCAPI_COMPAT_STATIC_INLINE(PyObject*)
PyObject_CallOneArg(PyObject *func, PyObject *arg)
{
return PyObject_CallFunctionObjArgs(func, arg, NULL);
}
#endif
// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3
#if PY_VERSION_HEX < 0x030A00A3
PYCAPI_COMPAT_STATIC_INLINE(int)
PyModule_AddObjectRef(PyObject *mod, const char *name, PyObject *value)
{
int res;
Py_XINCREF(value);
res = PyModule_AddObject(mod, name, value);
if (res < 0) {
Py_XDECREF(value);
}
return res;
}
#endif
// bpo-40024 added PyModule_AddType() to Python 3.9.0a5
#if PY_VERSION_HEX < 0x030900A5
PYCAPI_COMPAT_STATIC_INLINE(int)
PyModule_AddType(PyObject *mod, PyTypeObject *type)
{
const char *name, *dot;
if (PyType_Ready(type) < 0) {
return -1;
}
// inline _PyType_Name()
name = type->tp_name;
assert(name != PYCAPI_COMPAT_NULL);
dot = strrchr(name, '.');
if (dot != PYCAPI_COMPAT_NULL) {
name = dot + 1;
}
return PyModule_AddObjectRef(mod, name, _PyObject_CAST(type));
}
#endif
// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6.
// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2.
#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(int)
PyObject_GC_IsTracked(PyObject* obj)
{
return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj));
}
#endif
// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6.
// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final.
#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION)
PYCAPI_COMPAT_STATIC_INLINE(int)
PyObject_GC_IsFinalized(PyObject *obj)
{
PyGC_Head *gc = PYCAPI_COMPAT_CAST(PyGC_Head *, obj) - 1;
return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc));
}
#endif
// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4
#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE)
PYCAPI_COMPAT_STATIC_INLINE(int)
_Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) {
return ob->ob_type == type;
}
#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST_CONST(ob), type)
#endif
// Py_UNUSED() was added to Python 3.4.0b2.
#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED)
# if defined(__GNUC__) || defined(__clang__)
# define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
# else
# define Py_UNUSED(name) _unused_ ## name
# endif
#endif
#ifdef __cplusplus
}
#endif
#endif // PYTHONCAPI_COMPAT

View file

@ -0,0 +1,419 @@
// Collects code that was copied in from cpython, for a couple of different reasons:
// * We wanted to modify it to produce a more efficient version for our uses
// * We needed to call it and it was static :(
// * We wanted to call it and needed to backport it
#ifndef CPY_PYTHONSUPPORT_H
#define CPY_PYTHONSUPPORT_H
#include <stdbool.h>
#include <Python.h>
#include "pythoncapi_compat.h"
#include <frameobject.h>
#include <assert.h>
#include "mypyc_util.h"
#ifdef __cplusplus
extern "C" {
#endif
#if 0
} // why isn't emacs smart enough to not indent this
#endif
/////////////////////////////////////////
// Adapted from bltinmodule.c in Python 3.7.0
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 7
_Py_IDENTIFIER(__mro_entries__);
static PyObject*
update_bases(PyObject *bases)
{
Py_ssize_t i, j;
PyObject *base, *meth, *new_base, *result, *new_bases = NULL;
PyObject *stack[1] = {bases};
assert(PyTuple_Check(bases));
Py_ssize_t nargs = PyTuple_GET_SIZE(bases);
for (i = 0; i < nargs; i++) {
base = PyTuple_GET_ITEM(bases, i);
if (PyType_Check(base)) {
if (new_bases) {
/* If we already have made a replacement, then we append every normal base,
otherwise just skip it. */
if (PyList_Append(new_bases, base) < 0) {
goto error;
}
}
continue;
}
if (_PyObject_LookupAttrId(base, &PyId___mro_entries__, &meth) < 0) {
goto error;
}
if (!meth) {
if (new_bases) {
if (PyList_Append(new_bases, base) < 0) {
goto error;
}
}
continue;
}
new_base = _PyObject_FastCall(meth, stack, 1);
Py_DECREF(meth);
if (!new_base) {
goto error;
}
if (!PyTuple_Check(new_base)) {
PyErr_SetString(PyExc_TypeError,
"__mro_entries__ must return a tuple");
Py_DECREF(new_base);
goto error;
}
if (!new_bases) {
/* If this is a first successful replacement, create new_bases list and
copy previously encountered bases. */
if (!(new_bases = PyList_New(i))) {
goto error;
}
for (j = 0; j < i; j++) {
base = PyTuple_GET_ITEM(bases, j);
PyList_SET_ITEM(new_bases, j, base);
Py_INCREF(base);
}
}
j = PyList_GET_SIZE(new_bases);
if (PyList_SetSlice(new_bases, j, j, new_base) < 0) {
goto error;
}
Py_DECREF(new_base);
}
if (!new_bases) {
return bases;
}
result = PyList_AsTuple(new_bases);
Py_DECREF(new_bases);
return result;
error:
Py_XDECREF(new_bases);
return NULL;
}
#else
static PyObject*
update_bases(PyObject *bases)
{
return bases;
}
#endif
// From Python 3.7's typeobject.c
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 6
_Py_IDENTIFIER(__init_subclass__);
static int
init_subclass(PyTypeObject *type, PyObject *kwds)
{
PyObject *super, *func, *result;
PyObject *args[2] = {(PyObject *)type, (PyObject *)type};
super = _PyObject_FastCall((PyObject *)&PySuper_Type, args, 2);
if (super == NULL) {
return -1;
}
func = _PyObject_GetAttrId(super, &PyId___init_subclass__);
Py_DECREF(super);
if (func == NULL) {
return -1;
}
result = _PyObject_FastCallDict(func, NULL, 0, kwds);
Py_DECREF(func);
if (result == NULL) {
return -1;
}
Py_DECREF(result);
return 0;
}
#else
static int
init_subclass(PyTypeObject *type, PyObject *kwds)
{
return 0;
}
#endif
// Adapted from longobject.c in Python 3.7.0
/* This function adapted from PyLong_AsLongLongAndOverflow, but with
* some safety checks removed and specialized to only work for objects
* that are already longs.
* About half of the win this provides, though, just comes from being
* able to inline the function, which in addition to saving function call
* overhead allows the out-parameter overflow flag to be collapsed into
* control flow.
* Additionally, we check against the possible range of CPyTagged, not of
* Py_ssize_t. */
static inline Py_ssize_t
CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
{
/* This version by Tim Peters */
PyLongObject *v = (PyLongObject *)vv;
size_t x, prev;
Py_ssize_t res;
Py_ssize_t i;
int sign;
*overflow = 0;
res = -1;
i = Py_SIZE(v);
if (likely(i == 1)) {
res = v->ob_digit[0];
} else if (likely(i == 0)) {
res = 0;
} else if (i == -1) {
res = -(sdigit)v->ob_digit[0];
} else {
sign = 1;
x = 0;
if (i < 0) {
sign = -1;
i = -(i);
}
while (--i >= 0) {
prev = x;
x = (x << PyLong_SHIFT) + v->ob_digit[i];
if ((x >> PyLong_SHIFT) != prev) {
*overflow = sign;
goto exit;
}
}
/* Haven't lost any bits, but casting to long requires extra
* care (see comment above).
*/
if (x <= (size_t)CPY_TAGGED_MAX) {
res = (Py_ssize_t)x * sign;
}
else if (sign < 0 && x == CPY_TAGGED_ABS_MIN) {
res = CPY_TAGGED_MIN;
}
else {
*overflow = sign;
/* res is already set to -1 */
}
}
exit:
return res;
}
// Adapted from listobject.c in Python 3.7.0
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
PyObject **items;
size_t new_allocated, num_allocated_bytes;
Py_ssize_t allocated = self->allocated;
/* Bypass realloc() when a previous overallocation is large enough
to accommodate the newsize. If the newsize falls lower than half
the allocated size, then proceed with the realloc() to shrink the list.
*/
if (allocated >= newsize && newsize >= (allocated >> 1)) {
assert(self->ob_item != NULL || newsize == 0);
Py_SET_SIZE(self, newsize);
return 0;
}
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) {
PyErr_NoMemory();
return -1;
}
if (newsize == 0)
new_allocated = 0;
num_allocated_bytes = new_allocated * sizeof(PyObject *);
items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);
if (items == NULL) {
PyErr_NoMemory();
return -1;
}
self->ob_item = items;
Py_SET_SIZE(self, newsize);
self->allocated = new_allocated;
return 0;
}
// Changed to use PyList_SetSlice instead of the internal list_ass_slice
static PyObject *
list_pop_impl(PyListObject *self, Py_ssize_t index)
{
PyObject *v;
int status;
if (Py_SIZE(self) == 0) {
/* Special-case most common failure cause */
PyErr_SetString(PyExc_IndexError, "pop from empty list");
return NULL;
}
if (index < 0)
index += Py_SIZE(self);
if (index < 0 || index >= Py_SIZE(self)) {
PyErr_SetString(PyExc_IndexError, "pop index out of range");
return NULL;
}
v = self->ob_item[index];
if (index == Py_SIZE(self) - 1) {
status = list_resize(self, Py_SIZE(self) - 1);
if (status >= 0)
return v; /* and v now owns the reference the list had */
else
return NULL;
}
Py_INCREF(v);
status = PyList_SetSlice((PyObject *)self, index, index+1, (PyObject *)NULL);
if (status < 0) {
Py_DECREF(v);
return NULL;
}
return v;
}
// Tweaked to directly use CPyTagged
static CPyTagged
list_count(PyListObject *self, PyObject *value)
{
Py_ssize_t count = 0;
Py_ssize_t i;
for (i = 0; i < Py_SIZE(self); i++) {
int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ);
if (cmp > 0)
count++;
else if (cmp < 0)
return CPY_INT_TAG;
}
return CPyTagged_ShortFromSsize_t(count);
}
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION < 8
static PyObject *
_PyDict_GetItemStringWithError(PyObject *v, const char *key)
{
PyObject *kv, *rv;
kv = PyUnicode_FromString(key);
if (kv == NULL) {
return NULL;
}
rv = PyDict_GetItemWithError(v, kv);
Py_DECREF(kv);
return rv;
}
#endif
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION < 6
/* _PyUnicode_EqualToASCIIString got added in 3.5.3 (argh!) so we can't actually know
* whether it will be present at runtime, so we just assume we don't have it in 3.5. */
#define CPyUnicode_EqualToASCIIString(x, y) (PyUnicode_CompareWithASCIIString((x), (y)) == 0)
#elif PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 6
#define CPyUnicode_EqualToASCIIString(x, y) _PyUnicode_EqualToASCIIString(x, y)
#endif
// Adapted from genobject.c in Python 3.7.2
// Copied because it wasn't in 3.5.2 and it is undocumented anyways.
/*
* Set StopIteration with specified value. Value can be arbitrary object
* or NULL.
*
* Returns 0 if StopIteration is set and -1 if any other exception is set.
*/
static int
CPyGen_SetStopIterationValue(PyObject *value)
{
PyObject *e;
if (value == NULL ||
(!PyTuple_Check(value) && !PyExceptionInstance_Check(value)))
{
/* Delay exception instantiation if we can */
PyErr_SetObject(PyExc_StopIteration, value);
return 0;
}
/* Construct an exception instance manually with
* PyObject_CallFunctionObjArgs and pass it to PyErr_SetObject.
*
* We do this to handle a situation when "value" is a tuple, in which
* case PyErr_SetObject would set the value of StopIteration to
* the first element of the tuple.
*
* (See PyErr_SetObject/_PyErr_CreateException code for details.)
*/
e = PyObject_CallFunctionObjArgs(PyExc_StopIteration, value, NULL);
if (e == NULL) {
return -1;
}
PyErr_SetObject(PyExc_StopIteration, e);
Py_DECREF(e);
return 0;
}
// Copied from dictobject.c and dictobject.h, these are not Public before
// Python 3.8. Also remove some error checks that we do in the callers.
typedef struct {
PyObject_HEAD
PyDictObject *dv_dict;
} _CPyDictViewObject;
static PyObject *
_CPyDictView_New(PyObject *dict, PyTypeObject *type)
{
_CPyDictViewObject *dv = PyObject_GC_New(_CPyDictViewObject, type);
if (dv == NULL)
return NULL;
Py_INCREF(dict);
dv->dv_dict = (PyDictObject *)dict;
PyObject_GC_Track(dv);
return (PyObject *)dv;
}
#ifdef __cplusplus
}
#endif
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >=10
static int
_CPyObject_HasAttrId(PyObject *v, _Py_Identifier *name) {
PyObject *tmp = NULL;
int result = _PyObject_LookupAttrId(v, name, &tmp);
if (tmp) {
Py_DECREF(tmp);
}
return result;
}
#else
#define _CPyObject_HasAttrId _PyObject_HasAttrId
#endif
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION < 9
// OneArgs and NoArgs functions got added in 3.9
#define _PyObject_CallMethodIdNoArgs(self, name) \
_PyObject_CallMethodIdObjArgs((self), (name), NULL)
#define _PyObject_CallMethodIdOneArg(self, name, arg) \
_PyObject_CallMethodIdObjArgs((self), (name), (arg), NULL)
#define PyObject_CallNoArgs(callable) \
PyObject_CallFunctionObjArgs((callable), NULL)
#define PyObject_CallOneArg(callable, arg) \
PyObject_CallFunctionObjArgs((callable), (arg), NULL)
#endif
#endif

View file

@ -0,0 +1,17 @@
// Set primitive operations
//
// These are registered in mypyc.primitives.set_ops.
#include <Python.h>
#include "CPy.h"
bool CPySet_Remove(PyObject *set, PyObject *key) {
int success = PySet_Discard(set, key);
if (success == 1) {
return true;
}
if (success == 0) {
_PyErr_SetKeyError(key);
}
return false;
}

View file

@ -0,0 +1,27 @@
"""Build script for mypyc C runtime library unit tests.
The tests are written in C++ and use the Google Test framework.
"""
from distutils.core import setup, Extension
import sys
if sys.platform == 'darwin':
kwargs = {'language': 'c++'}
compile_args = []
else:
kwargs = {} # type: ignore
compile_args = ['--std=c++11']
setup(name='test_capi',
version='0.1',
ext_modules=[Extension(
'test_capi',
['test_capi.cc', 'init.c', 'int_ops.c', 'list_ops.c', 'exc_ops.c', 'generic_ops.c'],
depends=['CPy.h', 'mypyc_util.h', 'pythonsupport.h'],
extra_compile_args=['-Wno-unused-function', '-Wno-sign-compare'] + compile_args,
library_dirs=['../external/googletest/make'],
libraries=['gtest'],
include_dirs=['../external/googletest', '../external/googletest/include'],
**kwargs
)])

View file

@ -0,0 +1,241 @@
// String primitive operations
//
// These are registered in mypyc.primitives.str_ops.
#include <Python.h>
#include "CPy.h"
PyObject *CPyStr_GetItem(PyObject *str, CPyTagged index) {
if (PyUnicode_READY(str) != -1) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = PyUnicode_GET_LENGTH(str);
if (n < 0)
n += size;
if (n < 0 || n >= size) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
enum PyUnicode_Kind kind = (enum PyUnicode_Kind)PyUnicode_KIND(str);
void *data = PyUnicode_DATA(str);
Py_UCS4 ch = PyUnicode_READ(kind, data, n);
PyObject *unicode = PyUnicode_New(1, ch);
if (unicode == NULL)
return NULL;
if (PyUnicode_KIND(unicode) == PyUnicode_1BYTE_KIND) {
PyUnicode_1BYTE_DATA(unicode)[0] = (Py_UCS1)ch;
} else if (PyUnicode_KIND(unicode) == PyUnicode_2BYTE_KIND) {
PyUnicode_2BYTE_DATA(unicode)[0] = (Py_UCS2)ch;
} else {
assert(PyUnicode_KIND(unicode) == PyUnicode_4BYTE_KIND);
PyUnicode_4BYTE_DATA(unicode)[0] = ch;
}
return unicode;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
} else {
PyObject *index_obj = CPyTagged_AsObject(index);
return PyObject_GetItem(str, index_obj);
}
}
// A simplification of _PyUnicode_JoinArray() from CPython 3.9.6
PyObject *CPyStr_Build(Py_ssize_t len, ...) {
Py_ssize_t i;
va_list args;
// Calculate the total amount of space and check
// whether all components have the same kind.
Py_ssize_t sz = 0;
Py_UCS4 maxchar = 0;
int use_memcpy = 1; // Use memcpy by default
PyObject *last_obj = NULL;
va_start(args, len);
for (i = 0; i < len; i++) {
PyObject *item = va_arg(args, PyObject *);
if (!PyUnicode_Check(item)) {
PyErr_Format(PyExc_TypeError,
"sequence item %zd: expected str instance,"
" %.80s found",
i, Py_TYPE(item)->tp_name);
return NULL;
}
if (PyUnicode_READY(item) == -1)
return NULL;
size_t add_sz = PyUnicode_GET_LENGTH(item);
Py_UCS4 item_maxchar = PyUnicode_MAX_CHAR_VALUE(item);
maxchar = Py_MAX(maxchar, item_maxchar);
// Using size_t to avoid overflow during arithmetic calculation
if (add_sz > (size_t)(PY_SSIZE_T_MAX - sz)) {
PyErr_SetString(PyExc_OverflowError,
"join() result is too long for a Python string");
return NULL;
}
sz += add_sz;
// If these strings have different kind, we would call
// _PyUnicode_FastCopyCharacters() in the following part.
if (use_memcpy && last_obj != NULL) {
if (PyUnicode_KIND(last_obj) != PyUnicode_KIND(item))
use_memcpy = 0;
}
last_obj = item;
}
va_end(args);
// Construct the string
PyObject *res = PyUnicode_New(sz, maxchar);
if (res == NULL)
return NULL;
if (use_memcpy) {
unsigned char *res_data = PyUnicode_1BYTE_DATA(res);
unsigned int kind = PyUnicode_KIND(res);
va_start(args, len);
for (i = 0; i < len; ++i) {
PyObject *item = va_arg(args, PyObject *);
Py_ssize_t itemlen = PyUnicode_GET_LENGTH(item);
if (itemlen != 0) {
memcpy(res_data, PyUnicode_DATA(item), kind * itemlen);
res_data += kind * itemlen;
}
}
va_end(args);
assert(res_data == PyUnicode_1BYTE_DATA(res) + kind * PyUnicode_GET_LENGTH(res));
} else {
Py_ssize_t res_offset = 0;
va_start(args, len);
for (i = 0; i < len; ++i) {
PyObject *item = va_arg(args, PyObject *);
Py_ssize_t itemlen = PyUnicode_GET_LENGTH(item);
if (itemlen != 0) {
_PyUnicode_FastCopyCharacters(res, res_offset, item, 0, itemlen);
res_offset += itemlen;
}
}
va_end(args);
assert(res_offset == PyUnicode_GET_LENGTH(res));
}
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
}
PyObject *CPyStr_Split(PyObject *str, PyObject *sep, CPyTagged max_split) {
Py_ssize_t temp_max_split = CPyTagged_AsSsize_t(max_split);
if (temp_max_split == -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
return PyUnicode_Split(str, sep, temp_max_split);
}
PyObject *CPyStr_Replace(PyObject *str, PyObject *old_substr,
PyObject *new_substr, CPyTagged max_replace) {
Py_ssize_t temp_max_replace = CPyTagged_AsSsize_t(max_replace);
if (temp_max_replace == -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
return PyUnicode_Replace(str, old_substr, new_substr, temp_max_replace);
}
bool CPyStr_Startswith(PyObject *self, PyObject *subobj) {
Py_ssize_t start = 0;
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
return PyUnicode_Tailmatch(self, subobj, start, end, -1);
}
bool CPyStr_Endswith(PyObject *self, PyObject *subobj) {
Py_ssize_t start = 0;
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
return PyUnicode_Tailmatch(self, subobj, start, end, 1);
}
/* This does a dodgy attempt to append in place */
PyObject *CPyStr_Append(PyObject *o1, PyObject *o2) {
PyUnicode_Append(&o1, o2);
return o1;
}
PyObject *CPyStr_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
if (likely(PyUnicode_CheckExact(obj)
&& CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end))) {
Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start);
Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end);
if (startn < 0) {
startn += PyUnicode_GET_LENGTH(obj);
if (startn < 0) {
startn = 0;
}
}
if (endn < 0) {
endn += PyUnicode_GET_LENGTH(obj);
if (endn < 0) {
endn = 0;
}
}
return PyUnicode_Substring(obj, startn, endn);
}
return CPyObject_GetSlice(obj, start, end);
}
/* Check if the given string is true (i.e. it's length isn't zero) */
bool CPyStr_IsTrue(PyObject *obj) {
Py_ssize_t length = PyUnicode_GET_LENGTH(obj);
return length != 0;
}
Py_ssize_t CPyStr_Size_size_t(PyObject *str) {
if (PyUnicode_READY(str) != -1) {
return PyUnicode_GET_LENGTH(str);
}
return -1;
}
PyObject *CPy_Decode(PyObject *obj, PyObject *encoding, PyObject *errors) {
const char *enc = NULL;
const char *err = NULL;
if (encoding) {
enc = PyUnicode_AsUTF8AndSize(encoding, NULL);
if (!enc) return NULL;
}
if (errors) {
err = PyUnicode_AsUTF8AndSize(errors, NULL);
if (!err) return NULL;
}
if (PyBytes_Check(obj)) {
return PyUnicode_Decode(((PyBytesObject *)obj)->ob_sval,
((PyVarObject *)obj)->ob_size,
enc, err);
} else {
return PyUnicode_FromEncodedObject(obj, enc, err);
}
}
PyObject *CPy_Encode(PyObject *obj, PyObject *encoding, PyObject *errors) {
const char *enc = NULL;
const char *err = NULL;
if (encoding) {
enc = PyUnicode_AsUTF8AndSize(encoding, NULL);
if (!enc) return NULL;
}
if (errors) {
err = PyUnicode_AsUTF8AndSize(errors, NULL);
if (!err) return NULL;
}
if (PyUnicode_Check(obj)) {
return PyUnicode_AsEncodedString(obj, enc, err);
} else {
PyErr_BadArgument();
return NULL;
}
}

View file

@ -0,0 +1,585 @@
// Test cases
#include <gtest/gtest.h>
#include <Python.h>
#include "CPy.h"
static PyObject *moduleDict;
static PyObject *int_from_str(const char *str) {
return PyLong_FromString(str, 0, 10);
}
static std::string str_from_object(PyObject *x) {
PyObject *str = PyObject_Str(x);
const char *utf8 = PyUnicode_AsUTF8(str);
return std::string(utf8);
}
static std::string str_from_int(CPyTagged x) {
return str_from_object(CPyTagged_AsObject(x));
}
static bool is_py_equal(PyObject *x, PyObject *y) {
int result = PyObject_RichCompareBool(x, y, Py_EQ);
if (result < 0) {
std::cout << "ERROR: Rich compare failed";
}
return result == 1;
}
static bool is_int_equal(CPyTagged x, CPyTagged y) {
if (CPyTagged_CheckShort(x)) {
return x == y;
} else if (CPyTagged_CheckShort(y)) {
return false;
} else {
return is_py_equal(CPyTagged_LongAsObject(x), CPyTagged_LongAsObject(y));
}
}
static void fail(std::string message) {
std::cerr << message << "\n";
exit(1);
}
static PyObject *eval(std::string expr) {
PyObject *dict = PyDict_New();
auto result = PyRun_String(expr.c_str(), Py_eval_input, moduleDict, dict);
Py_DECREF(dict);
if (result == 0) {
fail("Python exception");
}
return result;
}
static CPyTagged eval_int(std::string expr) {
auto o = eval(expr);
EXPECT_TRUE(PyLong_Check(o));
return CPyTagged_FromObject(o);
}
static PyObject *empty_list() {
PyObject *list = PyList_New(0);
EXPECT_TRUE(list);
return list;
}
static void list_append(PyObject *list, std::string expr) {
PyObject *obj = eval(expr);
int result = PyList_Append(list, obj);
EXPECT_TRUE(result == 0);
}
class CAPITest : public ::testing::Test {
protected:
PyObject *max_short;
PyObject *min_short;
PyObject *min_pos_long;
PyObject *max_neg_long;
Py_ssize_t c_max_short;
Py_ssize_t c_min_short;
Py_ssize_t c_min_pos_long;
Py_ssize_t c_max_neg_long;
virtual void SetUp() {
if (!moduleDict) {
fail("Could not find module dictionary");
}
c_max_short = CPY_TAGGED_MAX; // 2**62-1
c_min_pos_long = c_max_short + 1; // 2**62
c_min_short = CPY_TAGGED_MIN; // -2**62
c_max_neg_long = c_min_short - 1; // -(2**62+1)
max_short = PyLong_FromSsize_t(c_max_short);
min_pos_long = PyLong_FromSsize_t(c_min_pos_long);
min_short = PyLong_FromSsize_t(c_min_short);
max_neg_long = PyLong_FromSsize_t(c_max_neg_long);
}
virtual void TearDown() {
Py_DECREF(max_short);
Py_DECREF(min_pos_long);
Py_DECREF(min_short);
Py_DECREF(max_neg_long);
}
};
TEST_F(CAPITest, test_cint_conversions) {
EXPECT_EQ(CPyTagged_ShortFromInt(0), 0);
EXPECT_EQ(CPyTagged_ShortFromInt(3), 6);
EXPECT_EQ(CPyTagged_ShortFromInt(-5), -10);
EXPECT_EQ(CPyTagged_ShortAsSsize_t(0), 0);
EXPECT_EQ(CPyTagged_ShortAsSsize_t(6), 3);
EXPECT_EQ(CPyTagged_ShortAsSsize_t(-10), -5);
}
TEST_F(CAPITest, test_is_long_int) {
EXPECT_TRUE(CPyTagged_CheckLong(1));
EXPECT_TRUE(CPyTagged_CheckLong(15));
EXPECT_FALSE(CPyTagged_CheckLong(0));
EXPECT_FALSE(CPyTagged_CheckLong(6));
EXPECT_FALSE(CPyTagged_CheckLong(-4));
}
TEST_F(CAPITest, test_is_short_int) {
EXPECT_FALSE(CPyTagged_CheckShort(1));
EXPECT_FALSE(CPyTagged_CheckShort(15));
EXPECT_TRUE(CPyTagged_CheckShort(0));
EXPECT_TRUE(CPyTagged_CheckShort(6));
EXPECT_TRUE(CPyTagged_CheckShort(-4));
}
TEST_F(CAPITest, test_obj_to_short_int) {
EXPECT_EQ(CPyTagged_FromObject(int_from_str("0")), CPyTagged_ShortFromInt(0));
EXPECT_EQ(CPyTagged_FromObject(int_from_str("1234")), CPyTagged_ShortFromInt(1234));
EXPECT_EQ(CPyTagged_FromObject(int_from_str("-1234")), CPyTagged_ShortFromInt(-1234));
EXPECT_EQ(CPyTagged_FromObject(max_short), CPyTagged_ShortFromSsize_t(c_max_short));
EXPECT_EQ(CPyTagged_FromObject(min_short), CPyTagged_ShortFromSsize_t(c_min_short));
}
TEST_F(CAPITest, test_obj_to_long_int) {
// A value larger than 2**64
PyObject *large = int_from_str("18464758493694263305");
PyObject *small = int_from_str("-18464758493694263305");
CPyTagged x;
x = CPyTagged_FromObject(large);
ASSERT_TRUE(CPyTagged_CheckLong(x));
EXPECT_TRUE(is_py_equal(large, CPyTagged_LongAsObject(x)));
x = CPyTagged_FromObject(small);
ASSERT_TRUE(CPyTagged_CheckLong(x));
EXPECT_TRUE(is_py_equal(small, CPyTagged_LongAsObject(x)));
x = CPyTagged_FromObject(min_pos_long);
ASSERT_TRUE(CPyTagged_CheckLong(x));
EXPECT_TRUE(is_py_equal(min_pos_long, CPyTagged_LongAsObject(x)));
x = CPyTagged_FromObject(max_neg_long);
ASSERT_TRUE(CPyTagged_CheckLong(x));
EXPECT_TRUE(is_py_equal(max_neg_long, CPyTagged_LongAsObject(x)));
}
TEST_F(CAPITest, test_short_int_to_obj) {
EXPECT_TRUE(is_py_equal(CPyTagged_AsObject(CPyTagged_ShortFromInt(0)), int_from_str("0")));
EXPECT_TRUE(is_py_equal(CPyTagged_AsObject(CPyTagged_ShortFromInt(1234)),
int_from_str("1234")));
EXPECT_TRUE(is_py_equal(CPyTagged_AsObject(CPyTagged_ShortFromInt(-1234)),
int_from_str("-1234")));
EXPECT_TRUE(is_py_equal(CPyTagged_AsObject(CPyTagged_ShortFromSsize_t(c_max_short)),
max_short));
EXPECT_TRUE(is_py_equal(CPyTagged_AsObject(CPyTagged_ShortFromSsize_t(c_min_short)),
min_short));
}
TEST_F(CAPITest, test_long_int_to_obj) {
// A value larger than 2**64
PyObject *large = int_from_str("18464758493694263305");
PyObject *small = int_from_str("-18464758493694263305");
PyObject *x;
x = CPyTagged_AsObject(CPyTagged_FromObject(large));
EXPECT_TRUE(is_py_equal(large, x));
x = CPyTagged_AsObject(CPyTagged_FromObject(small));
EXPECT_TRUE(is_py_equal(small, x));
x = CPyTagged_AsObject(CPyTagged_FromObject(min_pos_long));
EXPECT_TRUE(is_py_equal(min_pos_long, x));
x = CPyTagged_AsObject(CPyTagged_FromObject(max_neg_long));
EXPECT_TRUE(is_py_equal(max_neg_long, x));
}
#define EXPECT_INT_EQUAL(x, y) \
do { \
if (!is_int_equal(x, y)) \
std::cout << "Failure: " << str_from_int(x) << " != " << str_from_int(y) << \
"\n"; \
EXPECT_TRUE(is_int_equal(x, y)); \
} while (false)
#define ASSERT_ADD(x, y, result) \
EXPECT_TRUE(is_int_equal(CPyTagged_Add(eval_int(x), eval_int(y)), eval_int(result)))
TEST_F(CAPITest, test_add_short_int) {
ASSERT_ADD("13", "8", "21");
ASSERT_ADD("-13", "8", "-5");
ASSERT_ADD("13", "-7", "6");
ASSERT_ADD("13", "-14", "-1");
ASSERT_ADD("-3", "-5", "-8");
}
TEST_F(CAPITest, test_add_long_ints) {
ASSERT_ADD("2**64", "2**65", "2**64 + 2**65");
ASSERT_ADD("2**64", "-2**65", "2**64 - 2**65");
}
TEST_F(CAPITest, test_add_long_and_short) {
ASSERT_ADD("1", "2**65", "1 + 2**65");
ASSERT_ADD("2**65", "1", "1 + 2**65");
}
TEST_F(CAPITest, test_add_short_overflow) {
// Overfloat
ASSERT_ADD("2**62 - 1", "1", "2**62");
ASSERT_ADD("-2**62", "-1", "-2**62 - 1");
}
TEST_F(CAPITest, test_add_short_edge_cases) {
// Close but not quite overflow
ASSERT_ADD("2**62 - 2", "1", "2**62 - 1");
ASSERT_ADD("-2**62 + 1", "-1", "-2**62");
// Max magnitudes
ASSERT_ADD("2**62 - 1", "2**62 - 1", "2**63 - 2");
ASSERT_ADD("2**62 - 1", "-2**62", "-1");
}
#define ASSERT_SUBTRACT(x, y, result) \
EXPECT_TRUE(is_int_equal(CPyTagged_Subtract(eval_int(x), eval_int(y)), eval_int(result)))
TEST_F(CAPITest, test_subtract_short_int) {
ASSERT_SUBTRACT("13", "8", "5");
ASSERT_SUBTRACT("8", "13", "-5");
ASSERT_SUBTRACT("-13", "8", "-21");
ASSERT_SUBTRACT("13", "-7", "20");
ASSERT_SUBTRACT("-3", "-5", "2");
}
TEST_F(CAPITest, test_subtract_long_int) {
ASSERT_SUBTRACT("2**65", "2**64", "2**65 - 2**64");
ASSERT_SUBTRACT("2**65", "-2**64", "2**65 + 2**64");
}
TEST_F(CAPITest, test_subtract_long_and_short) {
ASSERT_SUBTRACT("1", "2**65", "1 - 2**65");
ASSERT_SUBTRACT("2**65", "1", "2**65 - 1");
}
TEST_F(CAPITest, test_subtract_short_overflow) {
ASSERT_SUBTRACT("2**62-1", "-1", "2**62");
ASSERT_SUBTRACT("-2**62", "1", "-2**62 - 1");
ASSERT_SUBTRACT("0", "-2**62", "2**62");
ASSERT_SUBTRACT("1", "-2**62 + 1", "2**62");
ASSERT_SUBTRACT("-2", "2**62 - 1", "-2**62 - 1");
}
TEST_F(CAPITest, test_subtract_short_edge_cases) {
// Close but not quite overflow
ASSERT_SUBTRACT("2**62 - 2", "-1", "2**62 - 1");
ASSERT_SUBTRACT("-2**62 + 1", "1", "-2**62");
// Max magnitudes
ASSERT_SUBTRACT("2**62 - 1", "-2**62", "2**63 - 1");
ASSERT_SUBTRACT("-2**62", "2**62 - 1", "-2**63 + 1");
}
#define ASSERT_MULTIPLY(x, y, result) \
EXPECT_TRUE(is_int_equal(CPyTagged_Multiply(eval_int(x), eval_int(y)), eval_int(result)))
TEST_F(CAPITest, test_multiply_int) {
ASSERT_MULTIPLY("0", "0", "0");
ASSERT_MULTIPLY("3", "5", "15");
ASSERT_MULTIPLY("2**40", "2**40", "2**80");
ASSERT_MULTIPLY("2**30-1", "2**30-1", "(2**30-1)**2");
ASSERT_MULTIPLY("2**30", "2**30-1", "2**30 * (2**30-1)");
ASSERT_MULTIPLY("2**30-1", "2**30", "2**30 * (2**30-1)");
ASSERT_MULTIPLY("2**15", "2**15-1", "2**15 * (2**15-1)");
ASSERT_MULTIPLY("2**15-1", "2**15", "2**15 * (2**15-1)");
ASSERT_MULTIPLY("3", "-5", "-15");
ASSERT_MULTIPLY("-3", "5", "-15");
ASSERT_MULTIPLY("-3", "-5", "15");
}
#define ASSERT_FLOOR_DIV(x, y, result) \
EXPECT_INT_EQUAL(CPyTagged_FloorDivide(eval_int(x), eval_int(y)), eval_int(result))
TEST_F(CAPITest, test_floor_divide_short_int) {
ASSERT_FLOOR_DIV("18", "6", "3");
ASSERT_FLOOR_DIV("17", "6", "2");
ASSERT_FLOOR_DIV("12", "6", "2");
ASSERT_FLOOR_DIV("15", "5", "3");
ASSERT_FLOOR_DIV("14", "5", "2");
ASSERT_FLOOR_DIV("11", "5", "2");
ASSERT_FLOOR_DIV("-18", "6", "-3");
ASSERT_FLOOR_DIV("-13", "6", "-3");
ASSERT_FLOOR_DIV("-12", "6", "-2");
ASSERT_FLOOR_DIV("18", "-6", "-3");
ASSERT_FLOOR_DIV("13", "-6", "-3");
ASSERT_FLOOR_DIV("12", "-6", "-2");
ASSERT_FLOOR_DIV("-3", "-3", "1");
ASSERT_FLOOR_DIV("-5", "-3", "1");
ASSERT_FLOOR_DIV("-6", "-3", "2");
ASSERT_FLOOR_DIV("2**60", "3", "2**60 // 3");
ASSERT_FLOOR_DIV("-2**62", "-1", "2**62");
ASSERT_FLOOR_DIV("-2**62", "1", "-2**62");
ASSERT_FLOOR_DIV("2**62 - 1", "1", "2**62 - 1");
ASSERT_FLOOR_DIV("2**62 - 1", "-1", "-2**62 + 1");
ASSERT_FLOOR_DIV("2**60", "3", "2**60 // 3");
ASSERT_FLOOR_DIV("-2**30", "-1", "2**30");
ASSERT_FLOOR_DIV("-2**30", "1", "-2**30");
ASSERT_FLOOR_DIV("2**30 - 1", "1", "2**30 - 1");
ASSERT_FLOOR_DIV("2**30 - 1", "-1", "-2**30 + 1");
}
TEST_F(CAPITest, test_floor_divide_long_int) {
ASSERT_FLOOR_DIV("2**100", "3", "2**100 // 3");
ASSERT_FLOOR_DIV("3", "2**100", "0");
ASSERT_FLOOR_DIV("2**100", "2**70 // 3", "2**100 // (2**70 // 3)");
}
#define ASSERT_REMAINDER(x, y, result) \
EXPECT_INT_EQUAL(CPyTagged_Remainder(eval_int(x), eval_int(y)), eval_int(result))
TEST_F(CAPITest, test_remainder_short_int) {
ASSERT_REMAINDER("18", "6", "0");
ASSERT_REMAINDER("17", "6", "5");
ASSERT_REMAINDER("13", "6", "1");
ASSERT_REMAINDER("12", "6", "0");
ASSERT_REMAINDER("15", "5", "0");
ASSERT_REMAINDER("14", "5", "4");
ASSERT_REMAINDER("11", "5", "1");
ASSERT_REMAINDER("-18", "6", "0");
ASSERT_REMAINDER("-13", "6", "5");
ASSERT_REMAINDER("-12", "6", "0");
ASSERT_REMAINDER("18", "-6", "0");
ASSERT_REMAINDER("13", "-6", "-5");
ASSERT_REMAINDER("12", "-6", "0");
ASSERT_REMAINDER("-3", "-3", "0");
ASSERT_REMAINDER("-5", "-3", "-2");
ASSERT_REMAINDER("-6", "-3", "0");
ASSERT_REMAINDER("-1", "2**62 - 1", "2**62 - 2");
ASSERT_REMAINDER("1", "-2**62", "-2**62 + 1");
}
TEST_F(CAPITest, test_remainder_long_int) {
ASSERT_REMAINDER("2**100", "3", "2**100 % 3");
ASSERT_REMAINDER("3", "2**100", "3");
ASSERT_REMAINDER("2**100", "2**70 // 3", "2**100 % (2**70 // 3)");
}
#define INT_EQ(x, y) \
CPyTagged_IsEq(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_equality) {
EXPECT_TRUE(INT_EQ("0", "0"));
EXPECT_TRUE(INT_EQ("5", "5"));
EXPECT_TRUE(INT_EQ("-7", "-7"));
EXPECT_TRUE(INT_EQ("2**65 + 0x1234", "2**65 + 0x1234"));
EXPECT_TRUE(INT_EQ("-2**65 + 0x1234", "-2**65 + 0x1234"));
EXPECT_FALSE(INT_EQ("0", "1"));
EXPECT_FALSE(INT_EQ("5", "4"));
EXPECT_FALSE(INT_EQ("-7", "7"));
EXPECT_FALSE(INT_EQ("-7", "-6"));
EXPECT_FALSE(INT_EQ("-7", "-5"));
EXPECT_FALSE(INT_EQ("2**65 + 0x1234", "2**65 + 0x1233"));
EXPECT_FALSE(INT_EQ("2**65 + 0x1234", "2**66 + 0x1234"));
EXPECT_FALSE(INT_EQ("2**65 + 0x1234", "-2**65 - 0x1234"));
EXPECT_FALSE(INT_EQ("-2**65 + 0x1234", "-2**65 + 0x1233"));
}
#define INT_NE(x, y) \
CPyTagged_IsNe(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_non_equality) {
EXPECT_FALSE(INT_NE("0", "0"));
EXPECT_FALSE(INT_NE("5", "5"));
EXPECT_FALSE(INT_NE("-7", "-7"));
EXPECT_FALSE(INT_NE("2**65 + 0x1234", "2**65 + 0x1234"));
EXPECT_FALSE(INT_NE("-2**65 + 0x1234", "-2**65 + 0x1234"));
EXPECT_TRUE(INT_NE("0", "1"));
EXPECT_TRUE(INT_NE("5", "4"));
EXPECT_TRUE(INT_NE("-7", "7"));
EXPECT_TRUE(INT_NE("-7", "-6"));
EXPECT_TRUE(INT_NE("-7", "-5"));
EXPECT_TRUE(INT_NE("2**65 + 0x1234", "2**65 + 0x1233"));
EXPECT_TRUE(INT_NE("2**65 + 0x1234", "2**66 + 0x1234"));
EXPECT_TRUE(INT_NE("2**65 + 0x1234", "-2**65 - 0x1234"));
EXPECT_TRUE(INT_NE("-2**65 + 0x1234", "-2**65 + 0x1233"));
}
#define INT_LT(x, y) \
CPyTagged_IsLt(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_less_than) {
EXPECT_TRUE(INT_LT("0", "5"));
EXPECT_TRUE(INT_LT("4", "5"));
EXPECT_TRUE(INT_LT("-3", "1"));
EXPECT_TRUE(INT_LT("-3", "0"));
EXPECT_TRUE(INT_LT("-3", "-2"));
EXPECT_FALSE(INT_LT("5", "0"));
EXPECT_FALSE(INT_LT("5", "4"));
EXPECT_FALSE(INT_LT("1", "-3"));
EXPECT_FALSE(INT_LT("0", "-3"));
EXPECT_FALSE(INT_LT("-2", "-3"));
EXPECT_FALSE(INT_LT("-3", "-3"));
EXPECT_FALSE(INT_LT("3", "3"));
EXPECT_TRUE(INT_LT("5", "2**65"));
EXPECT_TRUE(INT_LT("-2**65", "-5"));
EXPECT_TRUE(INT_LT("-2**66", "2**65"));
EXPECT_TRUE(INT_LT("2**65", "2**66"));
EXPECT_TRUE(INT_LT("-2**66", "-2**65"));
EXPECT_FALSE(INT_LT("2**65", "5"));
EXPECT_FALSE(INT_LT("-5", "-2**65"));
EXPECT_FALSE(INT_LT("2**65", "-2**66"));
EXPECT_FALSE(INT_LT("2**66", "2**65"));
EXPECT_FALSE(INT_LT("-2**65", "-2**66"));
EXPECT_FALSE(INT_LT("-2**65", "-2**65"));
EXPECT_FALSE(INT_LT("2**65", "2**65"));
}
#define INT_GE(x, y) \
CPyTagged_IsGe(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_greater_than_or_equal) {
EXPECT_TRUE(INT_GE("3", "2"));
EXPECT_TRUE(INT_GE("3", "3"));
EXPECT_FALSE(INT_GE("3", "4"));
EXPECT_TRUE(INT_GE("3", "-4"));
EXPECT_TRUE(INT_GE("-3", "-4"));
EXPECT_TRUE(INT_GE("-3", "-3"));
EXPECT_FALSE(INT_GE("-3", "-2"));
EXPECT_FALSE(INT_GE("-3", "2"));
EXPECT_TRUE(INT_GE("2**65", "2**65"));
EXPECT_TRUE(INT_GE("2**65", "2**65 - 1"));
EXPECT_FALSE(INT_GE("2**65", "2**65 + 1"));
EXPECT_TRUE(INT_GE("2**65", "2**60"));
}
#define INT_GT(x, y) \
CPyTagged_IsGt(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_greater_than) {
EXPECT_TRUE(INT_GT("5", "0"));
EXPECT_TRUE(INT_GT("5", "4"));
EXPECT_FALSE(INT_GT("5", "5"));
EXPECT_FALSE(INT_GT("5", "6"));
EXPECT_TRUE(INT_GT("1", "-3"));
EXPECT_FALSE(INT_GT("-3", "1"));
EXPECT_TRUE(INT_GT("0", "-3"));
EXPECT_TRUE(INT_GT("-2", "-3"));
EXPECT_FALSE(INT_GT("-2", "-2"));
EXPECT_FALSE(INT_GT("-2", "-1"));
EXPECT_TRUE(INT_GT("2**65", "5"));
EXPECT_TRUE(INT_GT("2**65", "2**65 - 1"));
EXPECT_FALSE(INT_GT("2**65", "2**65"));
EXPECT_FALSE(INT_GT("2**65", "2**65 + 1"));
EXPECT_FALSE(INT_GT("-2**65", "1"));
EXPECT_TRUE(INT_GT("-2**65", "-2**65 - 1"));
EXPECT_FALSE(INT_GT("-2**65", "-2**65"));
EXPECT_FALSE(INT_GT("-2**65", "-2**65 + 1"));
}
#define INT_LE(x, y) \
CPyTagged_IsLe(eval_int(x), eval_int(y))
TEST_F(CAPITest, test_int_less_than_or_equal) {
EXPECT_TRUE(INT_LE("0", "5"));
EXPECT_TRUE(INT_LE("5", "6"));
EXPECT_TRUE(INT_LE("5", "5"));
EXPECT_FALSE(INT_LE("5", "4"));
EXPECT_FALSE(INT_LE("1", "-3"));
EXPECT_TRUE(INT_LE("-3", "1"));
EXPECT_TRUE(INT_LT("-3", "0"));
EXPECT_TRUE(INT_LE("-2", "-1"));
EXPECT_TRUE(INT_LE("-2", "-2"));
EXPECT_FALSE(INT_LT("-2", "-3"));
EXPECT_TRUE(INT_LE("5", "2**65"));
EXPECT_FALSE(INT_LE("2**65", "5"));
EXPECT_TRUE(INT_LE("2**65", "2**65 + 1"));
EXPECT_TRUE(INT_LE("2**65", "2**65"));
EXPECT_FALSE(INT_LE("2**65", "2**65 - 1"));
EXPECT_TRUE(INT_LE("-2**65", "1"));
EXPECT_FALSE(INT_LE("1", "-2**65"));
EXPECT_TRUE(INT_LE("-2**65", "-2**65 + 1"));
EXPECT_TRUE(INT_LE("-2**65", "-2**65"));
EXPECT_FALSE(INT_LE("-2**65", "-2**65 - 1"));
}
#define list_get_eq(list, index, value) \
is_py_equal(CPyList_GetItem(list, eval_int(index)), eval(value))
TEST_F(CAPITest, test_list_get) {
auto l = empty_list();
list_append(l, "3");
list_append(l, "5");
list_append(l, "7");
EXPECT_TRUE(list_get_eq(l, "0", "3"));
EXPECT_TRUE(list_get_eq(l, "1", "5"));
EXPECT_TRUE(list_get_eq(l, "2", "7"));
EXPECT_TRUE(list_get_eq(l, "-1", "7"));
EXPECT_TRUE(list_get_eq(l, "-2", "5"));
EXPECT_TRUE(list_get_eq(l, "-3", "3"));
}
TEST_F(CAPITest, test_tagged_as_long_long) {
auto s = eval_int("3");
auto neg = eval_int("-1");
auto l = eval_int("2**128");
EXPECT_TRUE(CPyTagged_AsSsize_t(s) == 3);
EXPECT_FALSE(PyErr_Occurred());
EXPECT_TRUE(CPyTagged_AsSsize_t(neg) == -1);
EXPECT_FALSE(PyErr_Occurred());
EXPECT_TRUE(CPyTagged_AsSsize_t(l) == -1);
EXPECT_TRUE(PyErr_Occurred());
PyErr_Clear();
}
////
// Python module glue to drive the C-API tests.
//
// The reason we have this as an extension module instead of a
// standalone binary is because building an extension module is pretty
// well behaved (just specify it with distutils/setuptools and it will
// get compiled and linked against the running python) while linking a
// library against libpython is a huge non-standard
// PITA: python-config locations are janky and it behaves in weird
// ways that I don't understand, while this works very cleanly.
static PyObject *run_tests(PyObject *dummy, PyObject *should_be_null) {
// Fake command line arguments. We could arrange to actually pass
// in command line arguments (either real ones or ones given as
// arguments) but have not bothered.
int argc = 1;
char asdf[] = "test_capi"; // InitGoogleTest wants char** which means it can't be const...
char *argv[] = {asdf, NULL};
::testing::InitGoogleTest(&argc, argv);
return PyLong_FromLong(RUN_ALL_TESTS());
}
static PyMethodDef test_methods[] = {
{"run_tests", run_tests, METH_NOARGS, "Run the C API tests"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef test_module = {
PyModuleDef_HEAD_INIT,
"test_capi",
NULL,
-1,
test_methods
};
PyMODINIT_FUNC
PyInit_test_capi(void)
{
PyObject *module = PyModule_Create(&test_module);
if (module) {
moduleDict = PyModule_GetDict(module);
}
return module;
}

View file

@ -0,0 +1,61 @@
// Tuple primitive operations
//
// These are registered in mypyc.primitives.tuple_ops.
#include <Python.h>
#include "CPy.h"
PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index) {
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
Py_ssize_t size = PyTuple_GET_SIZE(tuple);
if (n >= 0) {
if (n >= size) {
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
return NULL;
}
} else {
n += size;
if (n < 0) {
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
return NULL;
}
}
PyObject *result = PyTuple_GET_ITEM(tuple, n);
Py_INCREF(result);
return result;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return NULL;
}
}
PyObject *CPySequenceTuple_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end) {
if (likely(PyTuple_CheckExact(obj)
&& CPyTagged_CheckShort(start) && CPyTagged_CheckShort(end))) {
Py_ssize_t startn = CPyTagged_ShortAsSsize_t(start);
Py_ssize_t endn = CPyTagged_ShortAsSsize_t(end);
if (startn < 0) {
startn += PyTuple_GET_SIZE(obj);
}
if (endn < 0) {
endn += PyTuple_GET_SIZE(obj);
}
return PyTuple_GetSlice(obj, startn, endn);
}
return CPyObject_GetSlice(obj, start, end);
}
// PyTuple_SET_ITEM does no error checking,
// and should only be used to fill in brand new tuples.
bool CPySequenceTuple_SetItemUnsafe(PyObject *tuple, CPyTagged index, PyObject *value)
{
if (CPyTagged_CheckShort(index)) {
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
PyTuple_SET_ITEM(tuple, n, value);
return true;
} else {
PyErr_SetString(PyExc_OverflowError, CPYTHON_LARGE_INT_ERRMSG);
return false;
}
}