-
Notifications
You must be signed in to change notification settings - Fork 150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[EXTREMELY WIP] Attempt to use the Python Stable ABI #308
Conversation
SHOOOT! There was a git mixup. I'll rebase to master soon 😆 |
I recommend you work off the dev_4.0 branch so you don't have to work with all the python 2 compatibility. |
b622b10
to
7993c2d
Compare
7993c2d
to
805003d
Compare
We have to wrap the buffer interface with our own support for `bytes` and `bytearray`. It is pretty disgusting. I'm starting to make all the types we use heap-allocated instead of statically declared. TODO Soon: - Finish convert all the types to heap types - Finish the various missing functions from `pyembed.c`
In the stable ABI, this is the only way to declare types. We should have better cross-version compatibility now.
It's depreciated and un-nessicarry TODO: What is the story here with the stable API I'm under the impression it was included (and nessicarry for old versions), however the python 3.9 docs claim it will be 'removed in version 3.11'.
Same thing is needed for PyRun_String This is needed on the stable ABI, where those symbols aren't defined.
805003d
to
4920dc9
Compare
Okay, I merged this onto the development branching the GitHub Web UI. Technically, this can't break the build because it wasn't working in the first place 🙈 This is definitely still unstable I want to bring up a couple of changes that I have made in this, some of which impact regular (non-stable API) compilations. Major changes so far
I don't think this code would be worth a review in its present state (it doesn't compile, and I plan to remove the buffer API). However, I would like to if you have any objections to the overall design. Have a good day 😄 |
When the limited ABI is enabled, we can't access `PyThreadState.interp`. Instead, we have to use the API function `PyThreadState_GetInterpreter`. Unfortunately, this API is new as of Python 3.9 so versions before that simply cannot know the current interpreter that is running in the main thread...... As such, I include a new define `JEP_ASSUME_SINGLE_INTERPRETER` where we just kind of wing it and assume there is only one active interpreter (which is actually pretty common in CPython land - a lot of extensions aren't built with subinterpreters in mind).
Everything compiles now, but it crashes on first load ^_^
9879f85
to
3d35060
Compare
This properly creates a 'bases' tuple for passing to `PyType_FromSpecAndBases`. Better error handling too. This fixes a SEGFAULT we had....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a very good idea, because of the limitation of the Python CAPI.
It's not designed with embedding in mind, and jep
is such a niche use case it's unlikely to support everything we need even if it becomes more popular....
* To work around it, we define the extern function ref by hand. | ||
*/ | ||
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030A0000 | ||
extern const char *PyUnicode_AsUTF8AndSize(PyObject *unicode, Py_ssize_t *size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a hack
* | ||
* TODO: Make this configuration optional | ||
*/ | ||
#define Py_LIMITED_API 0x03080000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a hack.
Arguably, we could make this some sort of build flag given in the makfile or setup.py
.
However, it's too unstable and feature-incomplete to ever become the default, so why bother?
compile_commands.json | ||
|
||
# Clangd | ||
.cache/clangd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, these should just be here anyways 😉
abort(); // Not a fast sequence: Undefined behavior | ||
} | ||
} | ||
static inline PyObject *PySequence_Fast_GET_ITEM(PyObject *target, Py_ssize_t index) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the performance matter here?
If YES, then why are we paying the performance price of the stable ABI in other places?
If NO, then why are we reimplementing this whole method here?
@@ -169,4 +169,6 @@ extern jclass JDOUBLE_ARRAY_TYPE; | |||
#define DEFINE_CLASS_GLOBAL(var, name) extern jclass var; | |||
CLASS_TABLE(DEFINE_CLASS_GLOBAL) | |||
|
|||
int jep_util_type_ready(PyTypeObject **target_type, PyType_Spec *spec, PyTypeObject *base); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice utility. I feel like in general, the project should switch to using heap-allocated types.
That seems like it's becoming the new sort of standard for CAPI extensions.
@@ -551,7 +591,7 @@ void pyembed_shared_import(JNIEnv *env, jstring module) | |||
{ | |||
const char *moduleName = NULL; | |||
PyObject *pymodule = NULL; | |||
PyEval_AcquireThread(mainThreadState); | |||
PyEval_AcquireThread(get_main_pystate()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is gross. We're emulating yet another API on top of the Stable API......
*/ | ||
PyGILState_STATE state = PyGILState_Ensure(); | ||
jepThread->tstate = PyThreadState_Get(); | ||
PyGILState_Release(state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this hack supposed to be more 'stable'?
#ifdef Py_LIMITED_API | ||
inum = (int) PyList_Size(interfaces); | ||
if (inum < 0) { | ||
assert(PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What the heck?
// Change target to UTF8 cstring, then delegate | ||
target.target_type = JEP_RUN_STRING_C; | ||
target.c_string = result_string; | ||
exec_result = pyembed_util_run(target, file_name, input_type, locals, globals); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets not reimplement functionality that's already in CPython!!!
@@ -516,49 +525,31 @@ static PyMethodDef pyjobject_methods[] = { | |||
|
|||
static PyMemberDef pyjobject_members[] = { | |||
{"__dict__", T_OBJECT, offsetof(PyJObject, attr), READONLY}, | |||
{"__dictoffset__", T_PYSSIZET, offsetof(PyJObject, attr), READONLY}, // NOTE: Sets `PyTypeObject.tp_dictoffset` for heap types... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting replacement for tp_dictoffset
......
Looks like this is actually used in the internals somewhere...
Oddly, the heap-type API is mostly undocumented....
I'm closing this because I feel the Python "Stable" CAPI is too limited and is unlikely to provide any benefits for I think some of the refactorings that I've done here is still a good idea, even though I don't think this PR should be accepted. Thank you so much for your time! |
The benefit of this is that jep could compile once and support multiple python versions. This could potentially allow the distribution of compiled binaries/wheels ahead of time.
Mostly this just means moving from fast access macros
to the checked runtime calls. This comes at some very minor performance cost (that is only present when we're compiling against the limited ABI).
Most of the changes so far involved the Unicode APIs. We can't have direct access to the internal
PyUnicode_KIND
representation.Right now I've just hardcoded it on with a
#define PY_LIMITED_API
at the top of thejep_platform.h
file, but of course I intend to make it configurable later.It looks like some of the functions required for embedding are missing.
Not only are flags like
Py_VerboseFlag
andPy_OptimizeFlag
unavailable, so are things like
PyRun_String
.We might have to sacrifice the
Py_VerboseFlag
and other config optionsentirely, I'm not seeing a replacement available.
However, the other problems I'm sure I can work around....