/* interpreters module */
/* low-level access to interpreter primitives */

#ifndef Py_BUILD_CORE_BUILTIN
#  define Py_BUILD_CORE_MODULE 1
#endif

#include "Python.h"
#include "pycore_abstract.h"      // _PyIndex_Check()
#include "pycore_crossinterp.h"   // struct _xid
#include "pycore_interp.h"        // _PyInterpreterState_IDIncref()
#include "pycore_initconfig.h"    // _PyErr_SetFromPyStatus()
#include "pycore_modsupport.h"    // _PyArg_BadArgument()
#include "pycore_namespace.h"     // _PyNamespace_New()
#include "pycore_pybuffer.h"      // _PyBuffer_ReleaseInInterpreterAndRawFree()
#include "pycore_pyerrors.h"      // _Py_excinfo
#include "pycore_pylifecycle.h"   // _PyInterpreterConfig_AsDict()
#include "pycore_pystate.h"       // _PyInterpreterState_SetRunningMain()

#include "marshal.h"              // PyMarshal_ReadObjectFromString()

#include "_interpreters_common.h"


#define MODULE_NAME _interpreters
#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME)
#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME)


static PyInterpreterState *
_get_current_interp(void)
{
    // PyInterpreterState_Get() aborts if lookup fails, so don't need
    // to check the result for NULL.
    return PyInterpreterState_Get();
}

#define look_up_interp _PyInterpreterState_LookUpIDObject


static PyObject *
_get_current_module(void)
{
    PyObject *name = PyUnicode_FromString(MODULE_NAME_STR);
    if (name == NULL) {
        return NULL;
    }
    PyObject *mod = PyImport_GetModule(name);
    Py_DECREF(name);
    if (mod == NULL) {
        return NULL;
    }
    assert(mod != Py_None);
    return mod;
}


static int
is_running_main(PyInterpreterState *interp)
{
    if (_PyInterpreterState_IsRunningMain(interp)) {
        return 1;
    }
    // Unlike with the general C-API, we can be confident that someone
    // using this module for the main interpreter is doing so through
    // the main program.  Thus we can make this extra check.  This benefits
    // applications that embed Python but haven't been updated yet
    // to call_PyInterpreterState_SetRunningMain().
    if (_Py_IsMainInterpreter(interp)) {
        return 1;
    }
    return 0;
}


/* Cross-interpreter Buffer Views *******************************************/

// XXX Release when the original interpreter is destroyed.

typedef struct {
    PyObject_HEAD
    Py_buffer *view;
    int64_t interpid;
} XIBufferViewObject;

static PyObject *
xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data)
{
    assert(_PyCrossInterpreterData_DATA(data) != NULL);
    assert(_PyCrossInterpreterData_OBJ(data) == NULL);
    assert(_PyCrossInterpreterData_INTERPID(data) >= 0);
    XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject));
    if (self == NULL) {
        return NULL;
    }
    PyObject_Init((PyObject *)self, cls);
    self->view = (Py_buffer *)_PyCrossInterpreterData_DATA(data);
    self->interpid = _PyCrossInterpreterData_INTERPID(data);
    return (PyObject *)self;
}

static void
xibufferview_dealloc(XIBufferViewObject *self)
{
    PyInterpreterState *interp = _PyInterpreterState_LookUpID(self->interpid);
    /* If the interpreter is no longer alive then we have problems,
       since other objects may be using the buffer still. */
    assert(interp != NULL);

    if (_PyBuffer_ReleaseInInterpreterAndRawFree(interp, self->view) < 0) {
        // XXX Emit a warning?
        PyErr_Clear();
    }

    PyTypeObject *tp = Py_TYPE(self);
    tp->tp_free(self);
    /* "Instances of heap-allocated types hold a reference to their type."
     * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol
     * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse
    */
    // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse,
    // like we do for _abc._abc_data?
    Py_DECREF(tp);
}

static int
xibufferview_getbuf(XIBufferViewObject *self, Py_buffer *view, int flags)
{
    /* Only PyMemoryView_FromObject() should ever call this,
       via _memoryview_from_xid() below. */
    *view = *self->view;
    view->obj = (PyObject *)self;
    // XXX Should we leave it alone?
    view->internal = NULL;
    return 0;
}

static PyType_Slot XIBufferViewType_slots[] = {
    {Py_tp_dealloc, (destructor)xibufferview_dealloc},
    {Py_bf_getbuffer, (getbufferproc)xibufferview_getbuf},
    // We don't bother with Py_bf_releasebuffer since we don't need it.
    {0, NULL},
};

static PyType_Spec XIBufferViewType_spec = {
    .name = MODULE_NAME_STR ".CrossInterpreterBufferView",
    .basicsize = sizeof(XIBufferViewObject),
    .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
              Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE),
    .slots = XIBufferViewType_slots,
};


static PyTypeObject * _get_current_xibufferview_type(void);

static PyObject *
_memoryview_from_xid(_PyCrossInterpreterData *data)
{
    PyTypeObject *cls = _get_current_xibufferview_type();
    if (cls == NULL) {
        return NULL;
    }
    PyObject *obj = xibufferview_from_xid(cls, data);
    if (obj == NULL) {
        return NULL;
    }
    return PyMemoryView_FromObject(obj);
}

static int
_memoryview_shared(PyThreadState *tstate, PyObject *obj,
                   _PyCrossInterpreterData *data)
{
    Py_buffer *view = PyMem_RawMalloc(sizeof(Py_buffer));
    if (view == NULL) {
        return -1;
    }
    if (PyObject_GetBuffer(obj, view, PyBUF_FULL_RO) < 0) {
        PyMem_RawFree(view);
        return -1;
    }
    _PyCrossInterpreterData_Init(data, tstate->interp, view, NULL,
                                 _memoryview_from_xid);
    return 0;
}

static int
register_memoryview_xid(PyObject *mod, PyTypeObject **p_state)
{
    // XIBufferView
    assert(*p_state == NULL);
    PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec(
                mod, &XIBufferViewType_spec, NULL);
    if (cls == NULL) {
        return -1;
    }
    if (PyModule_AddType(mod, cls) < 0) {
        Py_DECREF(cls);
        return -1;
    }
    *p_state = cls;

    // Register XID for the builtin memoryview type.
    if (ensure_xid_class(&PyMemoryView_Type, _memoryview_shared) < 0) {
        return -1;
    }
    // We don't ever bother un-registering memoryview.

    return 0;
}



/* module state *************************************************************/

typedef struct {
    int _notused;

    /* heap types */
    PyTypeObject *XIBufferViewType;
} module_state;

static inline module_state *
get_module_state(PyObject *mod)
{
    assert(mod != NULL);
    module_state *state = PyModule_GetState(mod);
    assert(state != NULL);
    return state;
}

static module_state *
_get_current_module_state(void)
{
    PyObject *mod = _get_current_module();
    if (mod == NULL) {
        // XXX import it?
        PyErr_SetString(PyExc_RuntimeError,
                        MODULE_NAME_STR " module not imported yet");
        return NULL;
    }
    module_state *state = get_module_state(mod);
    Py_DECREF(mod);
    return state;
}

static int
traverse_module_state(module_state *state, visitproc visit, void *arg)
{
    /* heap types */
    Py_VISIT(state->XIBufferViewType);

    return 0;
}

static int
clear_module_state(module_state *state)
{
    /* heap types */
    Py_CLEAR(state->XIBufferViewType);

    return 0;
}


static PyTypeObject *
_get_current_xibufferview_type(void)
{
    module_state *state = _get_current_module_state();
    if (state == NULL) {
        return NULL;
    }
    return state->XIBufferViewType;
}


/* Python code **************************************************************/

static const char *
check_code_str(PyUnicodeObject *text)
{
    assert(text != NULL);
    if (PyUnicode_GET_LENGTH(text) == 0) {
        return "too short";
    }

    // XXX Verify that it parses?

    return NULL;
}

static const char *
check_code_object(PyCodeObject *code)
{
    assert(code != NULL);
    if (code->co_argcount > 0
        || code->co_posonlyargcount > 0
        || code->co_kwonlyargcount > 0
        || code->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
    {
        return "arguments not supported";
    }
    if (code->co_ncellvars > 0) {
        return "closures not supported";
    }
    // We trust that no code objects under co_consts have unbound cell vars.

    if (_PyCode_HAS_EXECUTORS(code) || _PyCode_HAS_INSTRUMENTATION(code)) {
        return "only basic functions are supported";
    }
    if (code->_co_monitoring != NULL) {
        return "only basic functions are supported";
    }
    if (code->co_extra != NULL) {
        return "only basic functions are supported";
    }

    return NULL;
}

#define RUN_TEXT 1
#define RUN_CODE 2

static const char *
get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p)
{
    const char *codestr = NULL;
    Py_ssize_t len = -1;
    PyObject *bytes_obj = NULL;
    int flags = 0;

    if (PyUnicode_Check(arg)) {
        assert(PyUnicode_CheckExact(arg)
               && (check_code_str((PyUnicodeObject *)arg) == NULL));
        codestr = PyUnicode_AsUTF8AndSize(arg, &len);
        if (codestr == NULL) {
            return NULL;
        }
        if (strlen(codestr) != (size_t)len) {
            PyErr_SetString(PyExc_ValueError,
                            "source code string cannot contain null bytes");
            return NULL;
        }
        flags = RUN_TEXT;
    }
    else {
        assert(PyCode_Check(arg)
               && (check_code_object((PyCodeObject *)arg) == NULL));
        flags = RUN_CODE;

        // Serialize the code object.
        bytes_obj = PyMarshal_WriteObjectToString(arg, Py_MARSHAL_VERSION);
        if (bytes_obj == NULL) {
            return NULL;
        }
        codestr = PyBytes_AS_STRING(bytes_obj);
        len = PyBytes_GET_SIZE(bytes_obj);
    }

    *flags_p = flags;
    *bytes_p = bytes_obj;
    *len_p = len;
    return codestr;
}


/* interpreter-specific code ************************************************/

static int
init_named_config(PyInterpreterConfig *config, const char *name)
{
    if (name == NULL
            || strcmp(name, "") == 0
            || strcmp(name, "default") == 0)
    {
        name = "isolated";
    }

    if (strcmp(name, "isolated") == 0) {
        *config = (PyInterpreterConfig)_PyInterpreterConfig_INIT;
    }
    else if (strcmp(name, "legacy") == 0) {
        *config = (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT;
    }
    else if (strcmp(name, "empty") == 0) {
        *config = (PyInterpreterConfig){0};
    }
    else {
        PyErr_Format(PyExc_ValueError,
                     "unsupported config name '%s'", name);
        return -1;
    }
    return 0;
}

static int
config_from_object(PyObject *configobj, PyInterpreterConfig *config)
{
    if (configobj == NULL || configobj == Py_None) {
        if (init_named_config(config, NULL) < 0) {
            return -1;
        }
    }
    else if (PyUnicode_Check(configobj)) {
        if (init_named_config(config, PyUnicode_AsUTF8(configobj)) < 0) {
            return -1;
        }
    }
    else {
        PyObject *dict = PyObject_GetAttrString(configobj, "__dict__");
        if (dict == NULL) {
            PyErr_Format(PyExc_TypeError, "bad config %R", configobj);
            return -1;
        }
        int res = _PyInterpreterConfig_InitFromDict(config, dict);
        Py_DECREF(dict);
        if (res < 0) {
            return -1;
        }
    }
    return 0;
}


static int
_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
{
    PyObject *result = NULL;
    if (flags & RUN_TEXT) {
        result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
    }
    else if (flags & RUN_CODE) {
        PyObject *code = PyMarshal_ReadObjectFromString(codestr, codestrlen);
        if (code != NULL) {
            result = PyEval_EvalCode(code, ns, ns);
            Py_DECREF(code);
        }
    }
    else {
        Py_UNREACHABLE();
    }
    if (result == NULL) {
        return -1;
    }
    Py_DECREF(result);  // We throw away the result.
    return 0;
}

static int
_run_in_interpreter(PyInterpreterState *interp,
                    const char *codestr, Py_ssize_t codestrlen,
                    PyObject *shareables, int flags,
                    PyObject **p_excinfo)
{
    assert(!PyErr_Occurred());
    _PyXI_session session = {0};

    // Prep and switch interpreters.
    if (_PyXI_Enter(&session, interp, shareables) < 0) {
        assert(!PyErr_Occurred());
        PyObject *excinfo = _PyXI_ApplyError(session.error);
        if (excinfo != NULL) {
            *p_excinfo = excinfo;
        }
        assert(PyErr_Occurred());
        return -1;
    }

    // Run the script.
    int res = _run_script(session.main_ns, codestr, codestrlen, flags);

    // Clean up and switch back.
    _PyXI_Exit(&session);

    // Propagate any exception out to the caller.
    assert(!PyErr_Occurred());
    if (res < 0) {
        PyObject *excinfo = _PyXI_ApplyCapturedException(&session);
        if (excinfo != NULL) {
            *p_excinfo = excinfo;
        }
    }
    else {
        assert(!_PyXI_HasCapturedException(&session));
    }

    return res;
}


/* module level code ********************************************************/

static long
get_whence(PyInterpreterState *interp)
{
    return _PyInterpreterState_GetWhence(interp);
}


static PyInterpreterState *
resolve_interp(PyObject *idobj, int restricted, int reqready, const char *op)
{
    PyInterpreterState *interp;
    if (idobj == NULL) {
        interp = PyInterpreterState_Get();
    }
    else {
        interp = look_up_interp(idobj);
        if (interp == NULL) {
            return NULL;
        }
    }

    if (reqready && !_PyInterpreterState_IsReady(interp)) {
        if (idobj == NULL) {
            PyErr_Format(PyExc_InterpreterError,
                         "cannot %s current interpreter (not ready)", op);
        }
        else {
            PyErr_Format(PyExc_InterpreterError,
                         "cannot %s interpreter %R (not ready)", op, idobj);
        }
        return NULL;
    }

    if (restricted && get_whence(interp) != _PyInterpreterState_WHENCE_STDLIB) {
        if (idobj == NULL) {
            PyErr_Format(PyExc_InterpreterError,
                         "cannot %s unrecognized current interpreter", op);
        }
        else {
            PyErr_Format(PyExc_InterpreterError,
                         "cannot %s unrecognized interpreter %R", op, idobj);
        }
        return NULL;
    }

    return interp;
}


static PyObject *
get_summary(PyInterpreterState *interp)
{
    PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
    if (idobj == NULL) {
        return NULL;
    }
    PyObject *whenceobj = PyLong_FromLong(
                            get_whence(interp));
    if (whenceobj == NULL) {
        Py_DECREF(idobj);
        return NULL;
    }
    PyObject *res = PyTuple_Pack(2, idobj, whenceobj);
    Py_DECREF(idobj);
    Py_DECREF(whenceobj);
    return res;
}


static PyObject *
interp_new_config(PyObject *self, PyObject *args, PyObject *kwds)
{
    const char *name = NULL;
    if (!PyArg_ParseTuple(args, "|s:" MODULE_NAME_STR ".new_config",
                          &name))
    {
        return NULL;
    }
    PyObject *overrides = kwds;

    PyInterpreterConfig config;
    if (init_named_config(&config, name) < 0) {
        return NULL;
    }

    if (overrides != NULL && PyDict_GET_SIZE(overrides) > 0) {
        if (_PyInterpreterConfig_UpdateFromDict(&config, overrides) < 0) {
            return NULL;
        }
    }

    PyObject *dict = _PyInterpreterConfig_AsDict(&config);
    if (dict == NULL) {
        return NULL;
    }

    PyObject *configobj = _PyNamespace_New(dict);
    Py_DECREF(dict);
    return configobj;
}

PyDoc_STRVAR(new_config_doc,
"new_config(name='isolated', /, **overrides) -> type.SimpleNamespace\n\
\n\
Return a representation of a new PyInterpreterConfig.\n\
\n\
The name determines the initial values of the config.  Supported named\n\
configs are: default, isolated, legacy, and empty.\n\
\n\
Any keyword arguments are set on the corresponding config fields,\n\
overriding the initial values.");


static PyObject *
interp_create(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"config", "reqrefs", NULL};
    PyObject *configobj = NULL;
    int reqrefs = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O$p:create", kwlist,
                                     &configobj, &reqrefs)) {
        return NULL;
    }

    PyInterpreterConfig config;
    if (config_from_object(configobj, &config) < 0) {
        return NULL;
    }

    long whence = _PyInterpreterState_WHENCE_STDLIB;
    PyInterpreterState *interp = \
            _PyXI_NewInterpreter(&config, &whence, NULL, NULL);
    if (interp == NULL) {
        // XXX Move the chained exception to interpreters.create()?
        PyObject *exc = PyErr_GetRaisedException();
        assert(exc != NULL);
        PyErr_SetString(PyExc_InterpreterError, "interpreter creation failed");
        _PyErr_ChainExceptions1(exc);
        return NULL;
    }
    assert(_PyInterpreterState_IsReady(interp));

    PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
    if (idobj == NULL) {
        _PyXI_EndInterpreter(interp, NULL, NULL);
        return NULL;
    }

    if (reqrefs) {
        // Decref to 0 will destroy the interpreter.
        _PyInterpreterState_RequireIDRef(interp, 1);
    }

    return idobj;
}


PyDoc_STRVAR(create_doc,
"create([config], *, reqrefs=False) -> ID\n\
\n\
Create a new interpreter and return a unique generated ID.\n\
\n\
The caller is responsible for destroying the interpreter before exiting,\n\
typically by using _interpreters.destroy().  This can be managed \n\
automatically by passing \"reqrefs=True\" and then using _incref() and\n\
_decref() appropriately.\n\
\n\
\"config\" must be a valid interpreter config or the name of a\n\
predefined config (\"isolated\" or \"legacy\").  The default\n\
is \"isolated\".");


static PyObject *
interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "restrict", NULL};
    PyObject *id;
    int restricted = 0;
    // XXX Use "L" for id?
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O|$p:destroy", kwlist, &id, &restricted))
    {
        return NULL;
    }

    // Look up the interpreter.
    int reqready = 0;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "destroy");
    if (interp == NULL) {
        return NULL;
    }

    // Ensure we don't try to destroy the current interpreter.
    PyInterpreterState *current = _get_current_interp();
    if (current == NULL) {
        return NULL;
    }
    if (interp == current) {
        PyErr_SetString(PyExc_InterpreterError,
                        "cannot destroy the current interpreter");
        return NULL;
    }

    // Ensure the interpreter isn't running.
    /* XXX We *could* support destroying a running interpreter but
       aren't going to worry about it for now. */
    if (is_running_main(interp)) {
        PyErr_Format(PyExc_InterpreterError, "interpreter running");
        return NULL;
    }

    // Destroy the interpreter.
    _PyXI_EndInterpreter(interp, NULL, NULL);

    Py_RETURN_NONE;
}

PyDoc_STRVAR(destroy_doc,
"destroy(id, *, restrict=False)\n\
\n\
Destroy the identified interpreter.\n\
\n\
Attempting to destroy the current interpreter raises InterpreterError.\n\
So does an unrecognized ID.");


static PyObject *
interp_list_all(PyObject *self, PyObject *args, PyObject *kwargs)
{
    static char *kwlist[] = {"require_ready", NULL};
    int reqready = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
                                     "|$p:" MODULE_NAME_STR ".list_all",
                                     kwlist, &reqready))
    {
        return NULL;
    }

    PyObject *ids = PyList_New(0);
    if (ids == NULL) {
        return NULL;
    }

    PyInterpreterState *interp = PyInterpreterState_Head();
    while (interp != NULL) {
        if (!reqready || _PyInterpreterState_IsReady(interp)) {
            PyObject *item = get_summary(interp);
            if (item == NULL) {
                Py_DECREF(ids);
                return NULL;
            }

            // insert at front of list
            int res = PyList_Insert(ids, 0, item);
            Py_DECREF(item);
            if (res < 0) {
                Py_DECREF(ids);
                return NULL;
            }
        }
        interp = PyInterpreterState_Next(interp);
    }

    return ids;
}

PyDoc_STRVAR(list_all_doc,
"list_all() -> [(ID, whence)]\n\
\n\
Return a list containing the ID of every existing interpreter.");


static PyObject *
interp_get_current(PyObject *self, PyObject *Py_UNUSED(ignored))
{
    PyInterpreterState *interp =_get_current_interp();
    if (interp == NULL) {
        return NULL;
    }
    assert(_PyInterpreterState_IsReady(interp));
    return get_summary(interp);
}

PyDoc_STRVAR(get_current_doc,
"get_current() -> (ID, whence)\n\
\n\
Return the ID of current interpreter.");


static PyObject *
interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
{
    PyInterpreterState *interp = _PyInterpreterState_Main();
    assert(_PyInterpreterState_IsReady(interp));
    return get_summary(interp);
}

PyDoc_STRVAR(get_main_doc,
"get_main() -> (ID, whence)\n\
\n\
Return the ID of main interpreter.");


static PyObject *
interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
{
    static char *kwlist[] = {"id", "updates", "restrict", NULL};
    PyObject *id, *updates;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
                                     "OO|$p:" MODULE_NAME_STR ".set___main___attrs",
                                     kwlist, &id, &updates, &restricted))
    {
        return NULL;
    }

    // Look up the interpreter.
    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "update __main__ for");
    if (interp == NULL) {
        return NULL;
    }

    // Check the updates.
    if (updates != Py_None) {
        Py_ssize_t size = PyObject_Size(updates);
        if (size < 0) {
            return NULL;
        }
        if (size == 0) {
            PyErr_SetString(PyExc_ValueError,
                            "arg 2 must be a non-empty mapping");
            return NULL;
        }
    }

    _PyXI_session session = {0};

    // Prep and switch interpreters, including apply the updates.
    if (_PyXI_Enter(&session, interp, updates) < 0) {
        if (!PyErr_Occurred()) {
            _PyXI_ApplyCapturedException(&session);
            assert(PyErr_Occurred());
        }
        else {
            assert(!_PyXI_HasCapturedException(&session));
        }
        return NULL;
    }

    // Clean up and switch back.
    _PyXI_Exit(&session);

    Py_RETURN_NONE;
}

PyDoc_STRVAR(set___main___attrs_doc,
"set___main___attrs(id, ns, *, restrict=False)\n\
\n\
Bind the given attributes in the interpreter's __main__ module.");


static PyUnicodeObject *
convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
                   const char *expected)
{
    PyUnicodeObject *str = NULL;
    if (PyUnicode_CheckExact(arg)) {
        str = (PyUnicodeObject *)Py_NewRef(arg);
    }
    else if (PyUnicode_Check(arg)) {
        // XXX str = PyUnicode_FromObject(arg);
        str = (PyUnicodeObject *)Py_NewRef(arg);
    }
    else {
        _PyArg_BadArgument(fname, displayname, expected, arg);
        return NULL;
    }

    const char *err = check_code_str(str);
    if (err != NULL) {
        Py_DECREF(str);
        PyErr_Format(PyExc_ValueError,
                     "%.200s(): bad script text (%s)", fname, err);
        return NULL;
    }

    return str;
}

static PyCodeObject *
convert_code_arg(PyObject *arg, const char *fname, const char *displayname,
                 const char *expected)
{
    const char *kind = NULL;
    PyCodeObject *code = NULL;
    if (PyFunction_Check(arg)) {
        if (PyFunction_GetClosure(arg) != NULL) {
            PyErr_Format(PyExc_ValueError,
                         "%.200s(): closures not supported", fname);
            return NULL;
        }
        code = (PyCodeObject *)PyFunction_GetCode(arg);
        if (code == NULL) {
            if (PyErr_Occurred()) {
                // This chains.
                PyErr_Format(PyExc_ValueError,
                             "%.200s(): bad func", fname);
            }
            else {
                PyErr_Format(PyExc_ValueError,
                             "%.200s(): func.__code__ missing", fname);
            }
            return NULL;
        }
        Py_INCREF(code);
        kind = "func";
    }
    else if (PyCode_Check(arg)) {
        code = (PyCodeObject *)Py_NewRef(arg);
        kind = "code object";
    }
    else {
        _PyArg_BadArgument(fname, displayname, expected, arg);
        return NULL;
    }

    const char *err = check_code_object(code);
    if (err != NULL) {
        Py_DECREF(code);
        PyErr_Format(PyExc_ValueError,
                     "%.200s(): bad %s (%s)", fname, kind, err);
        return NULL;
    }

    return code;
}

static int
_interp_exec(PyObject *self, PyInterpreterState *interp,
             PyObject *code_arg, PyObject *shared_arg, PyObject **p_excinfo)
{
    // Extract code.
    Py_ssize_t codestrlen = -1;
    PyObject *bytes_obj = NULL;
    int flags = 0;
    const char *codestr = get_code_str(code_arg,
                                       &codestrlen, &bytes_obj, &flags);
    if (codestr == NULL) {
        return -1;
    }

    // Run the code in the interpreter.
    int res = _run_in_interpreter(interp, codestr, codestrlen,
                                  shared_arg, flags, p_excinfo);
    Py_XDECREF(bytes_obj);
    if (res < 0) {
        return -1;
    }

    return 0;
}

static PyObject *
interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "code", "shared", "restrict", NULL};
    PyObject *id, *code;
    PyObject *shared = NULL;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "OO|O$p:" MODULE_NAME_STR ".exec", kwlist,
                                     &id, &code, &shared, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "exec code for");
    if (interp == NULL) {
        return NULL;
    }

    const char *expected = "a string, a function, or a code object";
    if (PyUnicode_Check(code)) {
         code = (PyObject *)convert_script_arg(code, MODULE_NAME_STR ".exec",
                                               "argument 2", expected);
    }
    else {
         code = (PyObject *)convert_code_arg(code, MODULE_NAME_STR ".exec",
                                             "argument 2", expected);
    }
    if (code == NULL) {
        return NULL;
    }

    PyObject *excinfo = NULL;
    int res = _interp_exec(self, interp, code, shared, &excinfo);
    Py_DECREF(code);
    if (res < 0) {
        assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
        return excinfo;
    }
    Py_RETURN_NONE;
}

PyDoc_STRVAR(exec_doc,
"exec(id, code, shared=None, *, restrict=False)\n\
\n\
Execute the provided code in the identified interpreter.\n\
This is equivalent to running the builtin exec() under the target\n\
interpreter, using the __dict__ of its __main__ module as both\n\
globals and locals.\n\
\n\
\"code\" may be a string containing the text of a Python script.\n\
\n\
Functions (and code objects) are also supported, with some restrictions.\n\
The code/function must not take any arguments or be a closure\n\
(i.e. have cell vars).  Methods and other callables are not supported.\n\
\n\
If a function is provided, its code object is used and all its state\n\
is ignored, including its __globals__ dict.");

static PyObject *
interp_call(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "callable", "args", "kwargs",
                             "restrict", NULL};
    PyObject *id, *callable;
    PyObject *args_obj = NULL;
    PyObject *kwargs_obj = NULL;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
                                     &id, &callable, &args_obj, &kwargs_obj,
                                     &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "make a call in");
    if (interp == NULL) {
        return NULL;
    }

    if (args_obj != NULL) {
        PyErr_SetString(PyExc_ValueError, "got unexpected args");
        return NULL;
    }
    if (kwargs_obj != NULL) {
        PyErr_SetString(PyExc_ValueError, "got unexpected kwargs");
        return NULL;
    }

    PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call",
                                                  "argument 2", "a function");
    if (code == NULL) {
        return NULL;
    }

    PyObject *excinfo = NULL;
    int res = _interp_exec(self, interp, code, NULL, &excinfo);
    Py_DECREF(code);
    if (res < 0) {
        assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
        return excinfo;
    }
    Py_RETURN_NONE;
}

PyDoc_STRVAR(call_doc,
"call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
\n\
Call the provided object in the identified interpreter.\n\
Pass the given args and kwargs, if possible.\n\
\n\
\"callable\" may be a plain function with no free vars that takes\n\
no arguments.\n\
\n\
The function's code object is used and all its state\n\
is ignored, including its __globals__ dict.");

static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "script", "shared", "restrict", NULL};
    PyObject *id, *script;
    PyObject *shared = NULL;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "OU|O$p:" MODULE_NAME_STR ".run_string",
                                     kwlist, &id, &script, &shared, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "run a string in");
    if (interp == NULL) {
        return NULL;
    }

    script = (PyObject *)convert_script_arg(script, MODULE_NAME_STR ".exec",
                                            "argument 2", "a string");
    if (script == NULL) {
        return NULL;
    }

    PyObject *excinfo = NULL;
    int res = _interp_exec(self, interp, script, shared, &excinfo);
    Py_DECREF(script);
    if (res < 0) {
        assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
        return excinfo;
    }
    Py_RETURN_NONE;
}

PyDoc_STRVAR(run_string_doc,
"run_string(id, script, shared=None, *, restrict=False)\n\
\n\
Execute the provided string in the identified interpreter.\n\
\n\
(See " MODULE_NAME_STR ".exec().");

static PyObject *
interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "func", "shared", "restrict", NULL};
    PyObject *id, *func;
    PyObject *shared = NULL;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "OO|O$p:" MODULE_NAME_STR ".run_func",
                                     kwlist, &id, &func, &shared, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "run a function in");
    if (interp == NULL) {
        return NULL;
    }

    PyCodeObject *code = convert_code_arg(func, MODULE_NAME_STR ".exec",
                                          "argument 2",
                                          "a function or a code object");
    if (code == NULL) {
        return NULL;
    }

    PyObject *excinfo = NULL;
    int res = _interp_exec(self, interp, (PyObject *)code, shared, &excinfo);
    Py_DECREF(code);
    if (res < 0) {
        assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
        return excinfo;
    }
    Py_RETURN_NONE;
}

PyDoc_STRVAR(run_func_doc,
"run_func(id, func, shared=None, *, restrict=False)\n\
\n\
Execute the body of the provided function in the identified interpreter.\n\
Code objects are also supported.  In both cases, closures and args\n\
are not supported.  Methods and other callables are not supported either.\n\
\n\
(See " MODULE_NAME_STR ".exec().");


static PyObject *
object_is_shareable(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"obj", NULL};
    PyObject *obj;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O:is_shareable", kwlist, &obj)) {
        return NULL;
    }

    if (_PyObject_CheckCrossInterpreterData(obj) == 0) {
        Py_RETURN_TRUE;
    }
    PyErr_Clear();
    Py_RETURN_FALSE;
}

PyDoc_STRVAR(is_shareable_doc,
"is_shareable(obj) -> bool\n\
\n\
Return True if the object's data may be shared between interpreters and\n\
False otherwise.");


static PyObject *
interp_is_running(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "restrict", NULL};
    PyObject *id;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O|$p:is_running", kwlist,
                                     &id, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "check if running for");
    if (interp == NULL) {
        return NULL;
    }

    if (is_running_main(interp)) {
        Py_RETURN_TRUE;
    }
    Py_RETURN_FALSE;
}

PyDoc_STRVAR(is_running_doc,
"is_running(id, *, restrict=False) -> bool\n\
\n\
Return whether or not the identified interpreter is running.");


static PyObject *
interp_get_config(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "restrict", NULL};
    PyObject *idobj = NULL;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O|$p:get_config", kwlist,
                                     &idobj, &restricted))
    {
        return NULL;
    }
    if (idobj == Py_None) {
        idobj = NULL;
    }

    int reqready = 0;
    PyInterpreterState *interp = \
            resolve_interp(idobj, restricted, reqready, "get the config of");
    if (interp == NULL) {
        return NULL;
    }

    PyInterpreterConfig config;
    if (_PyInterpreterConfig_InitFromState(&config, interp) < 0) {
        return NULL;
    }
    PyObject *dict = _PyInterpreterConfig_AsDict(&config);
    if (dict == NULL) {
        return NULL;
    }

    PyObject *configobj = _PyNamespace_New(dict);
    Py_DECREF(dict);
    return configobj;
}

PyDoc_STRVAR(get_config_doc,
"get_config(id, *, restrict=False) -> types.SimpleNamespace\n\
\n\
Return a representation of the config used to initialize the interpreter.");


static PyObject *
interp_whence(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", NULL};
    PyObject *id;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O:whence", kwlist, &id))
    {
        return NULL;
    }

    PyInterpreterState *interp = look_up_interp(id);
    if (interp == NULL) {
        return NULL;
    }

    long whence = get_whence(interp);
    return PyLong_FromLong(whence);
}

PyDoc_STRVAR(whence_doc,
"whence(id) -> int\n\
\n\
Return an identifier for where the interpreter was created.");


static PyObject *
interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "implieslink", "restrict", NULL};
    PyObject *id;
    int implieslink = 0;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O|$pp:incref", kwlist,
                                     &id, &implieslink, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "incref");
    if (interp == NULL) {
        return NULL;
    }

    if (implieslink) {
        // Decref to 0 will destroy the interpreter.
        _PyInterpreterState_RequireIDRef(interp, 1);
    }
    _PyInterpreterState_IDIncref(interp);

    Py_RETURN_NONE;
}


static PyObject *
interp_decref(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"id", "restrict", NULL};
    PyObject *id;
    int restricted = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "O|$p:decref", kwlist, &id, &restricted))
    {
        return NULL;
    }

    int reqready = 1;
    PyInterpreterState *interp = \
            resolve_interp(id, restricted, reqready, "decref");
    if (interp == NULL) {
        return NULL;
    }

    _PyInterpreterState_IDDecref(interp);

    Py_RETURN_NONE;
}


static PyObject *
capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"exc", NULL};
    PyObject *exc_arg = NULL;
    if (!PyArg_ParseTupleAndKeywords(args, kwds,
                                     "|O:capture_exception", kwlist,
                                     &exc_arg))
    {
        return NULL;
    }

    PyObject *exc = exc_arg;
    if (exc == NULL || exc == Py_None) {
        exc = PyErr_GetRaisedException();
        if (exc == NULL) {
            Py_RETURN_NONE;
        }
    }
    else if (!PyExceptionInstance_Check(exc)) {
        PyErr_Format(PyExc_TypeError, "expected exception, got %R", exc);
        return NULL;
    }
    PyObject *captured = NULL;

    _PyXI_excinfo info = {0};
    if (_PyXI_InitExcInfo(&info, exc) < 0) {
        goto finally;
    }
    captured = _PyXI_ExcInfoAsObject(&info);
    if (captured == NULL) {
        goto finally;
    }

    PyObject *formatted = _PyXI_FormatExcInfo(&info);
    if (formatted == NULL) {
        Py_CLEAR(captured);
        goto finally;
    }
    int res = PyObject_SetAttrString(captured, "formatted", formatted);
    Py_DECREF(formatted);
    if (res < 0) {
        Py_CLEAR(captured);
        goto finally;
    }

finally:
    _PyXI_ClearExcInfo(&info);
    if (exc != exc_arg) {
        if (PyErr_Occurred()) {
            PyErr_SetRaisedException(exc);
        }
        else {
            _PyErr_ChainExceptions1(exc);
        }
    }
    return captured;
}

PyDoc_STRVAR(capture_exception_doc,
"capture_exception(exc=None) -> types.SimpleNamespace\n\
\n\
Return a snapshot of an exception.  If \"exc\" is None\n\
then the current exception, if any, is used (but not cleared).\n\
\n\
The returned snapshot is the same as what _interpreters.exec() returns.");


static PyMethodDef module_functions[] = {
    {"new_config",                _PyCFunction_CAST(interp_new_config),
     METH_VARARGS | METH_KEYWORDS, new_config_doc},

    {"create",                    _PyCFunction_CAST(interp_create),
     METH_VARARGS | METH_KEYWORDS, create_doc},
    {"destroy",                   _PyCFunction_CAST(interp_destroy),
     METH_VARARGS | METH_KEYWORDS, destroy_doc},
    {"list_all",                  _PyCFunction_CAST(interp_list_all),
     METH_VARARGS | METH_KEYWORDS, list_all_doc},
    {"get_current",               interp_get_current,
     METH_NOARGS, get_current_doc},
    {"get_main",                  interp_get_main,
     METH_NOARGS, get_main_doc},

    {"is_running",                _PyCFunction_CAST(interp_is_running),
     METH_VARARGS | METH_KEYWORDS, is_running_doc},
    {"get_config",                _PyCFunction_CAST(interp_get_config),
     METH_VARARGS | METH_KEYWORDS, get_config_doc},
    {"whence",                    _PyCFunction_CAST(interp_whence),
     METH_VARARGS | METH_KEYWORDS, whence_doc},
    {"exec",                      _PyCFunction_CAST(interp_exec),
     METH_VARARGS | METH_KEYWORDS, exec_doc},
    {"call",                      _PyCFunction_CAST(interp_call),
     METH_VARARGS | METH_KEYWORDS, call_doc},
    {"run_string",                _PyCFunction_CAST(interp_run_string),
     METH_VARARGS | METH_KEYWORDS, run_string_doc},
    {"run_func",                  _PyCFunction_CAST(interp_run_func),
     METH_VARARGS | METH_KEYWORDS, run_func_doc},

    {"set___main___attrs",        _PyCFunction_CAST(interp_set___main___attrs),
     METH_VARARGS | METH_KEYWORDS, set___main___attrs_doc},

    {"incref",                    _PyCFunction_CAST(interp_incref),
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"decref",                    _PyCFunction_CAST(interp_decref),
     METH_VARARGS | METH_KEYWORDS, NULL},

    {"is_shareable",              _PyCFunction_CAST(object_is_shareable),
     METH_VARARGS | METH_KEYWORDS, is_shareable_doc},

    {"capture_exception",         _PyCFunction_CAST(capture_exception),
     METH_VARARGS | METH_KEYWORDS, capture_exception_doc},

    {NULL,                        NULL}           /* sentinel */
};


/* initialization function */

PyDoc_STRVAR(module_doc,
"This module provides primitive operations to manage Python interpreters.\n\
The 'interpreters' module provides a more convenient interface.");

static int
module_exec(PyObject *mod)
{
    PyInterpreterState *interp = PyInterpreterState_Get();
    module_state *state = get_module_state(mod);

#define ADD_WHENCE(NAME) \
    if (PyModule_AddIntConstant(mod, "WHENCE_" #NAME,                   \
                                _PyInterpreterState_WHENCE_##NAME) < 0) \
    {                                                                   \
        goto error;                                                     \
    }
    ADD_WHENCE(UNKNOWN)
    ADD_WHENCE(RUNTIME)
    ADD_WHENCE(LEGACY_CAPI)
    ADD_WHENCE(CAPI)
    ADD_WHENCE(XI)
    ADD_WHENCE(STDLIB)
#undef ADD_WHENCE

    // exceptions
    if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterError) < 0) {
        goto error;
    }
    if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterNotFoundError) < 0) {
        goto error;
    }
    PyObject *PyExc_NotShareableError = \
                _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError;
    if (PyModule_AddType(mod, (PyTypeObject *)PyExc_NotShareableError) < 0) {
        goto error;
    }

    if (register_memoryview_xid(mod, &state->XIBufferViewType) < 0) {
        goto error;
    }

    return 0;

error:
    return -1;
}

static struct PyModuleDef_Slot module_slots[] = {
    {Py_mod_exec, module_exec},
    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
    {0, NULL},
};

static int
module_traverse(PyObject *mod, visitproc visit, void *arg)
{
    module_state *state = get_module_state(mod);
    assert(state != NULL);
    traverse_module_state(state, visit, arg);
    return 0;
}

static int
module_clear(PyObject *mod)
{
    module_state *state = get_module_state(mod);
    assert(state != NULL);
    clear_module_state(state);
    return 0;
}

static void
module_free(void *mod)
{
    module_state *state = get_module_state(mod);
    assert(state != NULL);
    clear_module_state(state);
}

static struct PyModuleDef moduledef = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = MODULE_NAME_STR,
    .m_doc = module_doc,
    .m_size = sizeof(module_state),
    .m_methods = module_functions,
    .m_slots = module_slots,
    .m_traverse = module_traverse,
    .m_clear = module_clear,
    .m_free = (freefunc)module_free,
};

PyMODINIT_FUNC
MODINIT_FUNC_NAME(void)
{
    return PyModuleDef_Init(&moduledef);
}
