/*
 * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.graal.python.builtins.modules.ctypes;

import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ArgError;
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PyCFuncPtr;
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PyCPointer;
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PyCPointerType;
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError;
import static com.oracle.graal.python.builtins.modules.ctypes.CArgObjectBuiltins.paramFunc;
import static com.oracle.graal.python.builtins.modules.ctypes.CDataTypeBuiltins.PyCData_FromBaseObj;
import static com.oracle.graal.python.builtins.modules.ctypes.CDataTypeBuiltins.PyCData_GetContainer;
import static com.oracle.graal.python.builtins.modules.ctypes.FFIType.FFI_TYPES.FFI_TYPE_STRUCT;
import static com.oracle.graal.python.builtins.modules.ctypes.PyCFuncPtrBuiltins.PyCFuncPtrFromDllNode.strchr;
import static com.oracle.graal.python.builtins.modules.ctypes.PyCPointerTypeBuiltins._type_;
import static com.oracle.graal.python.nodes.ErrorMessages.ARGUMENT_D;
import static com.oracle.graal.python.nodes.ErrorMessages.BYREF_ARGUMENT_MUST_BE_A_CTYPES_INSTANCE_NOT_S;
import static com.oracle.graal.python.nodes.ErrorMessages.CAST_ARGUMENT_2_MUST_BE_A_POINTER_TYPE_NOT_S;
import static com.oracle.graal.python.nodes.ErrorMessages.COULD_NOT_CONVERT_THE_HANDLE_ATTRIBUTE_TO_A_POINTER;
import static com.oracle.graal.python.nodes.ErrorMessages.DON_T_KNOW_HOW_TO_CONVERT_PARAMETER_D;
import static com.oracle.graal.python.nodes.ErrorMessages.EXCEPTED_CTYPES_INSTANCE;
import static com.oracle.graal.python.nodes.ErrorMessages.FFI_CALL_FAILED;
import static com.oracle.graal.python.nodes.ErrorMessages.FFI_PREP_CIF_FAILED;
import static com.oracle.graal.python.nodes.ErrorMessages.INT_TOO_LONG_TO_CONVERT;
import static com.oracle.graal.python.nodes.ErrorMessages.INVALID_TYPE;
import static com.oracle.graal.python.nodes.ErrorMessages.MEMORY_CANNOT_BE_RESIZED_BECAUSE_THIS_OBJECT_DOESN_T_OWN_IT;
import static com.oracle.graal.python.nodes.ErrorMessages.MINIMUM_SIZE_IS_D;
import static com.oracle.graal.python.nodes.ErrorMessages.MUST_BE_A_CTYPES_TYPE;
import static com.oracle.graal.python.nodes.ErrorMessages.NOT_A_CTYPES_TYPE_OR_OBJECT;
import static com.oracle.graal.python.nodes.ErrorMessages.NO_ALIGNMENT_INFO;
import static com.oracle.graal.python.nodes.ErrorMessages.THIS_TYPE_HAS_NO_SIZE;
import static com.oracle.graal.python.nodes.ErrorMessages.TOO_MANY_ARGUMENTS_D_MAXIMUM_IS_D;
import static com.oracle.graal.python.runtime.PosixConstants.RTLD_GLOBAL;
import static com.oracle.graal.python.runtime.PosixConstants.RTLD_LOCAL;
import static com.oracle.graal.python.runtime.PosixConstants.RTLD_NOW;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.NotImplementedError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OSError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OverflowError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.RuntimeError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError;
import static com.oracle.graal.python.util.PythonUtils.append;
import static com.oracle.graal.python.util.PythonUtils.newStringBuilder;
import static com.oracle.graal.python.util.PythonUtils.sbToString;

import java.util.List;
import java.util.logging.Level;

import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.annotations.ArgumentClinic;
import com.oracle.graal.python.annotations.ArgumentClinic.ClinicConversion;
import com.oracle.graal.python.builtins.Builtin;
import com.oracle.graal.python.builtins.CoreFunctions;
import com.oracle.graal.python.builtins.Python3Core;
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
import com.oracle.graal.python.builtins.PythonBuiltins;
import com.oracle.graal.python.builtins.modules.PythonCextBuiltins.PyLongAsVoidPtr;
import com.oracle.graal.python.builtins.modules.SysModuleBuiltins.AuditNode;
import com.oracle.graal.python.builtins.modules.ctypes.CFieldBuiltins.GetFuncNode;
import com.oracle.graal.python.builtins.modules.ctypes.CtypesNodes.PyTypeCheck;
import com.oracle.graal.python.builtins.modules.ctypes.FFIType.FieldGet;
import com.oracle.graal.python.builtins.modules.ctypes.PtrValue.ByteArrayStorage;
import com.oracle.graal.python.builtins.modules.ctypes.StgDictBuiltins.PyObjectStgDictNode;
import com.oracle.graal.python.builtins.modules.ctypes.StgDictBuiltins.PyTypeStgDictNode;
import com.oracle.graal.python.builtins.objects.PNone;
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
import com.oracle.graal.python.builtins.objects.bytes.BytesUtils;
import com.oracle.graal.python.builtins.objects.bytes.PBytes;
import com.oracle.graal.python.builtins.objects.cext.capi.CApiGuards;
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.AddRefCntNode;
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.SubRefCntNode;
import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage;
import com.oracle.graal.python.builtins.objects.common.HashingStorageLibrary;
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetInternalByteArrayNode;
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetInternalObjectArrayNode;
import com.oracle.graal.python.builtins.objects.dict.PDict;
import com.oracle.graal.python.builtins.objects.function.PKeyword;
import com.oracle.graal.python.builtins.objects.module.PythonModule;
import com.oracle.graal.python.builtins.objects.str.PString;
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetBaseClassNode;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetNameNode;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsTypeNode;
import com.oracle.graal.python.lib.PyLongCheckNode;
import com.oracle.graal.python.lib.PyNumberAsSizeNode;
import com.oracle.graal.python.lib.PyObjectHashNode;
import com.oracle.graal.python.lib.PyObjectHashNodeGen;
import com.oracle.graal.python.nodes.PGuards;
import com.oracle.graal.python.nodes.PNodeWithRaise;
import com.oracle.graal.python.nodes.PRaiseNode;
import com.oracle.graal.python.nodes.SpecialMethodNames;
import com.oracle.graal.python.nodes.attributes.GetAttributeNode;
import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode;
import com.oracle.graal.python.nodes.call.CallNode;
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryClinicBuiltinNode;
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
import com.oracle.graal.python.nodes.object.GetClassNode;
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
import com.oracle.graal.python.nodes.util.CastToJavaStringNode;
import com.oracle.graal.python.runtime.PythonContext;
import com.oracle.graal.python.runtime.exception.PException;
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
import com.oracle.graal.python.util.PythonUtils;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.NodeFactory;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.dsl.UnsupportedSpecializationException;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.nfi.api.SignatureLibrary;

@CoreFunctions(defineModule = "_ctypes")
public class CtypesModuleBuiltins extends PythonBuiltins {
    @Override
    protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
        return CtypesModuleBuiltinsFactory.getFactories();
    }

    @CompilationFinal private Object strlenFunction;
    @CompilationFinal private Object memcpyFunction;

    private static final String NFI_LANGUAGE = "nfi";

    protected static final int FUNCFLAG_STDCALL = 0x0;
    protected static final int FUNCFLAG_CDECL = 0x1;
    protected static final int FUNCFLAG_HRESULT = 0x2;
    protected static final int FUNCFLAG_PYTHONAPI = 0x4;
    protected static final int FUNCFLAG_USE_ERRNO = 0x8;
    protected static final int FUNCFLAG_USE_LASTERROR = 0x10;

    protected static final int TYPEFLAG_ISPOINTER = 0x100;
    protected static final int TYPEFLAG_HASPOINTER = 0x200;
    protected static final int TYPEFLAG_HASUNION = 0x400;
    protected static final int TYPEFLAG_HASBITFIELD = 0x800;

    protected static final int DICTFLAG_FINAL = 0x1000;

    @Override
    public void initialize(Python3Core core) {
        super.initialize(core);
        builtinConstants.put("_pointer_type_cache", core.factory().createDict());
        builtinConstants.put("FUNCFLAG_CDECL", FUNCFLAG_CDECL);
        builtinConstants.put("FUNCFLAG_USE_ERRNO", FUNCFLAG_USE_ERRNO);
        builtinConstants.put("FUNCFLAG_USE_LASTERROR", FUNCFLAG_USE_LASTERROR);
        builtinConstants.put("FUNCFLAG_PYTHONAPI", FUNCFLAG_PYTHONAPI);
        builtinConstants.put("__version__", "1.1.0");
        builtinConstants.put("CFuncPtr", core.lookupType(PyCFuncPtr));
        builtinConstants.put("ArgumentError", core.lookupType(ArgError));
    }

    @Override
    public void postInitialize(Python3Core core) {
        super.postInitialize(core);
        PythonObjectFactory factory = core.factory();
        PythonModule ctypesModule = core.lookupBuiltinModule("_ctypes");
        ctypesModule.setAttribute("_string_at_addr", factory.createNativeVoidPtr(StringAtFunction.create()));
        ctypesModule.setAttribute("_cast_addr", factory.createNativeVoidPtr(CastFunction.create()));
        ctypesModule.setAttribute("_wstring_at_addr", factory.createNativeVoidPtr(WStringAtFunction.create()));
        int rtldLocal = RTLD_LOCAL.getValueIfDefined();
        ctypesModule.setAttribute("RTLD_LOCAL", rtldLocal);
        ctypesModule.setAttribute("RTLD_GLOBAL", RTLD_GLOBAL.getValueIfDefined());

        DLHandler handle = DlOpenNode.loadNFILibrary(core.getContext(), NFIBackend.NATIVE, "", rtldLocal);
        setCtypeNFIHelpers(this, core.getContext(), handle);
        NativeFunction memmove = MemMoveFunction.create(handle, core.getContext());
        ctypesModule.setAttribute("_memmove_addr", factory.createNativeVoidPtr(memmove, memmove.adr));
        NativeFunction memset = MemSetFunction.create(handle, core.getContext());
        ctypesModule.setAttribute("_memset_addr", factory.createNativeVoidPtr(memset, memset.adr));
    }

    Object getStrlenFunction() {
        return strlenFunction;
    }

    Object getMemcpyFunction() {
        return memcpyFunction;
    }

    private static void setCtypeNFIHelpers(CtypesModuleBuiltins ctypesModuleBuiltins, PythonContext context, DLHandler h) {
        ctypesModuleBuiltins.strlenFunction = createNFIHelperFunction(context, h, "strlen", "(POINTER):UINT32");
        ctypesModuleBuiltins.memcpyFunction = createNFIHelperFunction(context, h, "memcpy", "([UINT8], POINTER, UINT32):POINTER");
    }

    private static Object createNFIHelperFunction(PythonContext context, DLHandler h, String name, String signature) {
        try {
            Object symbol = InteropLibrary.getUncached().readMember(h.library, name);
            Source source = Source.newBuilder(NFI_LANGUAGE, signature, name).build();
            Object nfiSignature = context.getEnv().parseInternal(source).call();
            return SignatureLibrary.getUncached().bind(nfiSignature, symbol);
        } catch (UnsupportedMessageException | UnknownIdentifierException e) {
            // not supported.. carry on
            return null;
        }
    }

    protected static final String UNPICKLE = "_unpickle";

    enum NFIBackend {
        NATIVE(""),
        LLVM("with llvm ");

        private final String withClause;

        NFIBackend(String withClause) {
            this.withClause = withClause;
        }
    }

    @ExportLibrary(value = InteropLibrary.class, delegateTo = "sym")
    protected static final class NativeFunction implements TruffleObject {
        final Object sym;
        final long adr;
        final String name;

        Object function;
        String signature;

        final boolean isManaged;

        NativeFunction(Object sym, long adr, String name, boolean isManaged) {
            this.sym = sym;
            this.adr = adr;
            this.name = name;
            this.function = null;
            this.signature = null;
            this.isManaged = isManaged;
        }

        protected boolean isManaged() {
            return isManaged;
        }

        protected boolean isLLVM() {
            return sym instanceof CallLLVMFunction;
        }

        protected boolean isManaged(long address) {
            return adr == address;
        }
    }

    @ExportLibrary(value = InteropLibrary.class, delegateTo = "library")
    protected static final class DLHandler implements TruffleObject {
        final Object library;
        final String name;
        final long adr;
        final boolean isManaged;
        boolean isClosed;

        private DLHandler(Object library, long adr, String name, boolean isManaged) {
            this.library = library;
            this.adr = adr;
            this.name = name;
            this.isManaged = isManaged;
            this.isClosed = false;
        }

        public Object getLibrary() {
            return library;
        }
    }

    @TruffleBoundary
    protected static void registerAddress(PythonContext context, long adr, Object o) {
        context.getCtypesAdrMap().put(adr, o);
    }

    @TruffleBoundary
    protected static Object getObjectAtAddress(PythonContext context, long adr) {
        return context.getCtypesAdrMap().get(adr);
    }

    public static final class CtypesThreadState {
        /*
         * ctypes maintains thread-local storage that has space for two error numbers: private
         * copies of the system 'errno' value and, on Windows, the system error code accessed by the
         * GetLastError() and SetLastError() api functions.
         *
         * Foreign functions created with CDLL(..., use_errno=True), when called, swap the system
         * 'errno' value with the private copy just before the actual function call, and swapped
         * again immediately afterwards. The 'use_errno' parameter defaults to False, in this case
         * 'ctypes_errno' is not touched.
         *
         * On Windows, foreign functions created with CDLL(..., use_last_error=True) or WinDLL(...,
         * use_last_error=True) swap the system LastError value with the ctypes private copy.
         *
         * The values are also swapped immediately before and after ctypes callback functions are
         * called, if the callbacks are constructed using the new optional use_errno parameter set
         * to True: CFUNCTYPE(..., use_errno=TRUE) or WINFUNCTYPE(..., use_errno=True).
         *
         * New ctypes functions are provided to access the ctypes private copies from Python:
         *
         * - ctypes.set_errno(value) and ctypes.set_last_error(value) store 'value' in the private
         * copy and returns the previous value.
         *
         * - ctypes.get_errno() and ctypes.get_last_error() returns the current ctypes private
         * copies value.
         */
        // (mq) TODO: add another field for errno (Windows support)
        int errno; // see '_ctypes_get_errobj'

        NFIBackend backendType;
        EconomicMapStorage ptrtype_cache;
        EconomicMapStorage cache;

        public CtypesThreadState() {
            this.ptrtype_cache = EconomicMapStorage.create();
            this.cache = EconomicMapStorage.create();
            this.backendType = NFIBackend.NATIVE;
            this.errno = 0;
        }

        @TruffleBoundary
        static CtypesThreadState initCtypesThreadState(PythonContext context, PythonLanguage language) {
            CtypesThreadState ctypes = new CtypesThreadState();
            context.getThreadState(language).setCtypes(ctypes);
            return ctypes;
        }

        static CtypesThreadState get(PythonContext context, PythonLanguage language) {
            CtypesThreadState ctypes = context.getThreadState(language).getCtypes();
            if (ctypes == null) {
                ctypes = initCtypesThreadState(context, language);
            }
            return ctypes;
        }
    }

    @Builtin(name = "get_errno", maxNumOfPositionalArgs = 1, declaresExplicitSelf = true)
    @GenerateNodeFactory
    protected abstract static class GetErrnoNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object getErrno(@SuppressWarnings("unused") PythonModule module,
                        @Cached AuditNode auditNode) {
            auditNode.audit("ctypes.get_errno");
            return get_errno(getContext(), getLanguage());
        }

        static Object get_errno(PythonContext context, PythonLanguage language) {
            CtypesThreadState ctypes = CtypesThreadState.get(context, language);
            return ctypes.errno;
        }
    }

    @Builtin(name = "set_errno", minNumOfPositionalArgs = 1, parameterNames = {"errno"})
    @ArgumentClinic(name = "errno", conversion = ClinicConversion.Int)
    @GenerateNodeFactory
    protected abstract static class SetErrnoNode extends PythonUnaryClinicBuiltinNode {

        @Override
        protected ArgumentClinicProvider getArgumentClinic() {
            return CtypesModuleBuiltinsClinicProviders.SetErrnoNodeClinicProviderGen.INSTANCE;
        }

        @Specialization
        Object setErrno(int newErrno,
                        @Cached AuditNode auditNode) {
            auditNode.audit("ctypes.set_errno", newErrno);
            return set_errno(newErrno, getContext(), getLanguage());
        }

        static Object set_errno(int newErrno, PythonContext context, PythonLanguage language) {
            CtypesThreadState ctypes = CtypesThreadState.get(context, language);
            int oldErrno = ctypes.errno;
            ctypes.errno = newErrno;
            return oldErrno;
        }
    }

    @Builtin(name = "POINTER", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class PointerTypeNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object POINTER(VirtualFrame frame, Object cls,
                        @CachedLibrary(limit = "1") HashingStorageLibrary hlib,
                        @Cached IsTypeNode isTypeNode,
                        @Cached CallNode callNode,
                        @Cached GetNameNode getNameNode,
                        @Cached CastToJavaStringNode toJavaStringNode) {
            CtypesThreadState ctypes = CtypesThreadState.get(getContext(), getLanguage());
            Object result = hlib.getItem(ctypes.ptrtype_cache, cls);
            if (result != null) {
                return result;
            }
            Object key;
            if (PGuards.isString(cls)) {
                String name = toJavaStringNode.execute(cls);
                String buf = PythonUtils.format("LP_%s", name);
                Object[] args = new Object[]{buf, PyCPointer, factory().createDict()};
                result = callNode.execute(frame, PyCPointerType, args, PKeyword.EMPTY_KEYWORDS);
                key = factory().createNativeVoidPtr(result);
            } else if (isTypeNode.execute(cls)) {
                String buf = PythonUtils.format("LP_%s", getNameNode.execute(cls));
                PTuple bases = factory().createTuple(new Object[]{PyCPointer});
                Object[] args = new Object[]{buf, bases, factory().createDict(new PKeyword[]{new PKeyword(_type_, cls)})};
                result = callNode.execute(frame, PyCPointerType, args, PKeyword.EMPTY_KEYWORDS);
                key = cls;
            } else {
                throw raise(TypeError, MUST_BE_A_CTYPES_TYPE);
            }
            hlib.setItem(ctypes.ptrtype_cache, key, result);
            return result;
        }
    }

    @Builtin(name = "pointer", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class PointerObjectNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object pointer(VirtualFrame frame, Object arg,
                        @CachedLibrary(limit = "1") HashingStorageLibrary hlib,
                        @Cached PointerTypeNode callPOINTER,
                        @Cached CallNode callNode,
                        @Cached GetClassNode getClassNode) {
            CtypesThreadState ctypes = CtypesThreadState.get(getContext(), getLanguage());
            Object typ = hlib.getItem(ctypes.ptrtype_cache, getClassNode.execute(arg));
            if (typ != null) {
                return callNode.execute(frame, typ, arg);
            }
            typ = callPOINTER.execute(frame, getClassNode.execute(arg));
            return callNode.execute(frame, typ, arg);
        }
    }

    @ImportStatic(SpecialMethodNames.class)
    @Builtin(name = UNPICKLE, minNumOfPositionalArgs = 2)
    @GenerateNodeFactory
    protected abstract static class UnpickleNode extends PythonBinaryBuiltinNode {

        @Specialization
        Object unpickle(VirtualFrame frame, Object typ, PTuple state,
                        @Cached CallNode callNode,
                        @Cached("create(New)") LookupAndCallUnaryNode lookupAndCallUnaryNode,
                        @Cached("create(__SETSTATE__)") GetAttributeNode setStateAttr) {
            Object obj = lookupAndCallUnaryNode.executeObject(frame, typ);
            Object meth = setStateAttr.executeObject(frame, obj);
            callNode.execute(frame, meth, state);
            return obj;
        }

    }

    @Builtin(name = "buffer_info", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class BufferInfoNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object buffer_info(Object arg,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Cached PyObjectStgDictNode pyObjectStgDictNode) {
            StgDictObject dict = pyTypeStgDictNode.execute(arg);
            if (dict == null) {
                dict = pyObjectStgDictNode.execute(arg);
            }
            if (dict == null) {
                throw raise(TypeError, NOT_A_CTYPES_TYPE_OR_OBJECT);
            }
            Object[] shape = new Object[dict.ndim];
            for (int i = 0; i < dict.ndim; ++i) {
                shape[i] = dict.shape[i];
            }

            return factory().createTuple(new Object[]{dict.format, dict.ndim, factory().createTuple(shape)});
        }
    }

    @Builtin(name = "resize", minNumOfPositionalArgs = 2, parameterNames = {"", "size"})
    @ArgumentClinic(name = "size", conversion = ClinicConversion.Int)
    @GenerateNodeFactory
    protected abstract static class ResizeNode extends PythonBinaryClinicBuiltinNode {

        @Override
        protected ArgumentClinicProvider getArgumentClinic() {
            return CtypesModuleBuiltinsClinicProviders.ResizeNodeClinicProviderGen.INSTANCE;
        }

        @Specialization
        Object resize(CDataObject obj, int size,
                        @Cached PyObjectStgDictNode pyObjectStgDictNode) {
            StgDictObject dict = pyObjectStgDictNode.execute(obj);
            if (dict == null) {
                throw raise(TypeError, EXCEPTED_CTYPES_INSTANCE);
            }
            if (size < dict.size) {
                throw raise(ValueError, MINIMUM_SIZE_IS_D, dict.size);
            }
            if (obj.b_needsfree == 0) {
                throw raise(ValueError, MEMORY_CANNOT_BE_RESIZED_BECAUSE_THIS_OBJECT_DOESN_T_OWN_IT);
            }
            /*- TODO
            if (size <= sizeof(obj.b_value)) {
                // internal default buffer is large enough
                obj.b_size = size;
                return PNone.NONE;
            }
            Object ptr;
            if (!_CDataObject_HasExternalBuffer(obj)) {
                /*
                 * We are currently using the objects default buffer, but it isn't large enough any
                 * more.
                 * /
                ptr = PyMem_Malloc(size);
                memset(ptr, 0, size);
                memmove(ptr, obj.b_ptr, obj.b_size);
            } else {
                ptr = PyMem_Realloc(obj.b_ptr, size);
            }
            obj.b_ptr.ptr = ptr;
            */
            obj.b_size = size;
            return PNone.NONE;
        }
    }

    @Builtin(name = "dlopen", minNumOfPositionalArgs = 1, parameterNames = {"name", "mode"})
    // TODO: 'name' might need to be processed using FSConverter.
    @ArgumentClinic(name = "name", conversion = ClinicConversion.String, defaultValue = "\"\"", useDefaultForNone = true)
    @ArgumentClinic(name = "mode", conversion = ClinicConversion.Int, defaultValue = "Integer.MIN_VALUE", useDefaultForNone = true)
    @GenerateNodeFactory
    protected abstract static class DlOpenNode extends PythonBinaryClinicBuiltinNode {

        private static final String MACOS_Security_LIB = "/System/Library/Frameworks/Security.framework/Security";
        private static final String MACOS_CoreFoundation_LIB = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
        // "LibFFILibrary(" + handle + ")"
        private static final String LIBFFI_ADR_FORMAT_PREFIX = "LibFFILibrary";
        private static final int LIBFFI_ADR_FORMAT_START = LIBFFI_ADR_FORMAT_PREFIX.length() + 1;

        private static final TruffleLogger LOGGER = PythonLanguage.getLogger(DlOpenNode.class);

        protected static String flagsToString(int flag) {
            return (flag & RTLD_LOCAL.getValueIfDefined()) != 0 ? "RTLD_LOCAL|RTLD_NOW" : "RTLD_GLOBAL|RTLD_NOW";
        }

        @Override
        protected ArgumentClinicProvider getArgumentClinic() {
            return CtypesModuleBuiltinsClinicProviders.DlOpenNodeClinicProviderGen.INSTANCE;
        }

        @TruffleBoundary
        protected static DLHandler loadNFILibrary(PythonContext context, NFIBackend backendType, String name, int flags) {
            CompilerAsserts.neverPartOfCompilation();
            String src;
            if (PString.length(name) > 0) {
                src = PythonUtils.format("%sload (%s) \"%s\"", backendType.withClause, flagsToString(flags), name);
            } else {
                src = "default";
            }
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine(String.format("Loading native library %s %s", name, backendType.withClause));
            }
            Source loadSrc = Source.newBuilder(NFI_LANGUAGE, src, "load:" + name).internal(true).build();
            Object handler = context.getEnv().parseInternal(loadSrc).call();
            String handleStr = (String) InteropLibrary.getUncached().toDisplayString(handler);
            long adr = Long.parseLong(handleStr.substring(LIBFFI_ADR_FORMAT_START, handleStr.length() - 1));
            return new DLHandler(handler, adr, name, false);
        }

        @TruffleBoundary
        protected static Object loadLLVMLibrary(PythonContext context, String path) throws Exception {
            CompilerAsserts.neverPartOfCompilation();
            Source loadSrc = Source.newBuilder(PythonLanguage.LLVM_LANGUAGE, context.getPublicTruffleFileRelaxed(path)).build();
            return context.getEnv().parseInternal(loadSrc).call();
        }

        @TruffleBoundary
        private static String getErrMsg(Exception e) {
            String errmsg = e != null ? e.getMessage() : null;
            if (errmsg == null || PString.length(errmsg) == 0) {
                return "dlopen() error";
            }
            return errmsg;
        }

        @Specialization
        Object py_dl_open(VirtualFrame frame, String name_str, int m,
                        @Cached PyObjectHashNode hashNode,
                        @Cached AuditNode auditNode) {
            int mode = m != Integer.MIN_VALUE ? m : RTLD_LOCAL.getValueIfDefined();
            mode |= RTLD_NOW.getValueIfDefined();
            auditNode.audit("ctypes.dlopen", name_str);
            CtypesThreadState ctypes = CtypesThreadState.get(getContext(), getLanguage());
            DLHandler handle;
            Exception exception = null;
            try {
                if (PString.endsWith(name_str, getContext().getSoAbi())) {
                    Object handler = loadLLVMLibrary(getContext(), name_str);
                    long adr = hashNode.execute(frame, handler);
                    handle = new DLHandler(handler, adr, name_str, true);
                    registerAddress(getContext(), handle.adr, handle);
                    return factory().createNativeVoidPtr(handle);
                } else {
                    /*-
                     TODO: (mq) cryptography in macos isn't always compatible with ctypes. 
                     */
                    if (!PString.equals(name_str, MACOS_Security_LIB) && !PString.equals(name_str, MACOS_CoreFoundation_LIB)) {
                        handle = loadNFILibrary(getContext(), ctypes.backendType, name_str, mode);
                        registerAddress(getContext(), handle.adr, handle);
                        return factory().createNativeVoidPtr(handle, handle.adr);
                    }
                }
            } catch (Exception e) {
                exception = e;
            }
            throw raise(OSError, getErrMsg(exception));
        }

    }

    static String ctypes_dlerror() {
        return "dlerror";
    }

    static DLHandler getHandler(Object ptr, PythonContext context) {
        Object adr = ptr;
        if (PGuards.isInteger(adr)) {
            adr = getObjectAtAddress(context, (Long) adr);
        }
        if (adr instanceof DLHandler) {
            return (DLHandler) adr;
        }
        return null;
    }

    static NativeFunction getNativeFunction(Object ptr, PythonContext context) {
        Object adr = ptr;
        if (PGuards.isInteger(adr)) {
            adr = getObjectAtAddress(context, (Long) adr);
        }
        if (adr instanceof NativeFunction) {
            return (NativeFunction) adr;
        }
        return null;
    }

    @Builtin(name = "dlclose", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class DlCloseNode extends PythonUnaryBuiltinNode {

        private static boolean dlclose(Object ptr, PythonContext context) {
            DLHandler handle = getHandler(ptr, context);
            if (handle != null) {
                handle.isClosed = true;
                return true;
            }
            return false;
        }

        @Specialization
        Object py_dl_close(Object h,
                        @Cached PyLongAsVoidPtr asVoidPtr) {
            Object handle = asVoidPtr.execute(h);

            if (!dlclose(handle, getContext())) {
                throw raise(OSError, ctypes_dlerror());
            }
            return PNone.NONE;
        }
    }

    protected static DLHandler getHandleFromLongObject(Object obj, PythonContext context,
                    PyLongAsVoidPtr asVoidPtr,
                    PRaiseNode raiseNode) {
        Object h = null;
        try {
            h = asVoidPtr.execute(obj);
        } catch (PException e) {
            // throw later.
        }
        DLHandler handle = h != null ? getHandler(h, context) : null;
        if (handle == null) {
            throw raiseNode.raise(ValueError, COULD_NOT_CONVERT_THE_HANDLE_ATTRIBUTE_TO_A_POINTER);
        }
        return handle;
    }

    protected static NativeFunction getFunctionFromLongObject(Object obj, PythonContext context,
                    PyLongAsVoidPtr asVoidPtr) {
        if (obj instanceof NativeFunction) {
            return (NativeFunction) obj;
        }
        Object f = asVoidPtr.execute(obj);
        return f != null ? getNativeFunction(f, context) : null;
    }

    protected abstract static class CtypesDlSymNode extends PNodeWithRaise {

        protected abstract Object execute(VirtualFrame frame, DLHandler handle, Object n, PythonContext context, PythonObjectFactory factory, PythonBuiltinClassType error);

        @Specialization
        protected Object ctypes_dlsym(VirtualFrame frame, DLHandler handle, Object n, PythonContext context, PythonObjectFactory factory, PythonBuiltinClassType error,
                        @Cached PyObjectHashNode hashNode,
                        @Cached CastToJavaStringNode asString,
                        @CachedLibrary(limit = "1") InteropLibrary ilib) {
            String name = asString.execute(n);
            if (handle == null || handle.isClosed) {
                throw raise(error, ctypes_dlerror());
            }
            try {
                Object sym = ilib.readMember(handle.library, name);
                boolean isManaged = handle.isManaged;
                long adr = isManaged ? hashNode.execute(frame, sym) : ilib.asPointer(sym);
                sym = isManaged ? CallLLVMFunction.create(sym, ilib) : sym;
                NativeFunction func = new NativeFunction(sym, adr, name, isManaged);
                registerAddress(context, adr, func);
                // PyLong_FromVoidPtr(ptr);
                if (!isManaged) {
                    return factory.createNativeVoidPtr(func, adr);
                } else {
                    return factory.createNativeVoidPtr(func);
                }
            } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                throw raise(error, e);
            }
        }
    }

    @Builtin(name = "dlsym", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class DlSymNode extends PythonBinaryBuiltinNode {

        @Specialization
        Object py_dl_sym(VirtualFrame frame, Object obj, Object name,
                        @Cached PyLongAsVoidPtr asVoidPtr,
                        @Cached AuditNode auditNode,
                        @Cached CtypesDlSymNode dlSymNode) {
            auditNode.audit("ctypes.dlsym/handle", obj, name);
            DLHandler handle = getHandleFromLongObject(obj, getContext(), asVoidPtr, getRaiseNode());
            return dlSymNode.execute(frame, handle, name, getContext(), factory(), OSError);
        }
    }

    @Builtin(name = "alignment", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class AlignmentNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object align_func(Object obj,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Cached PyObjectStgDictNode pyObjectStgDictNode) {
            StgDictObject dict = pyTypeStgDictNode.execute(obj);
            if (dict != null) {
                return dict.align;
            }

            dict = pyObjectStgDictNode.execute(obj);
            if (dict != null) {
                return dict.align;
            }

            throw raise(TypeError, NO_ALIGNMENT_INFO);
        }
    }

    @Builtin(name = "sizeof", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class SizeOfNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object doit(Object obj,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode) {
            StgDictObject dict = pyTypeStgDictNode.execute(obj);
            if (dict != null) {
                return dict.size;
            }

            if (pyTypeCheck.isCDataObject(obj)) {
                return ((CDataObject) obj).b_size;
            }
            throw raise(TypeError, THIS_TYPE_HAS_NO_SIZE);
        }
    }

    @Builtin(name = "byref", minNumOfPositionalArgs = 1, parameterNames = {"", "offset"})
    @ArgumentClinic(name = "offset", conversion = ClinicConversion.Int, defaultValue = "0", useDefaultForNone = true)
    @GenerateNodeFactory
    protected abstract static class ByRefNode extends PythonBinaryClinicBuiltinNode {

        @Override
        protected ArgumentClinicProvider getArgumentClinic() {
            return CtypesModuleBuiltinsClinicProviders.ByRefNodeClinicProviderGen.INSTANCE;
        }

        @Specialization
        Object doit(CDataObject obj, int offset,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached PyObjectStgDictNode pyObjectStgDictNode) {
            if (!pyTypeCheck.isCDataObject(obj)) {
                return error(null, obj, offset);
            }
            FFIType ffiType = pyObjectStgDictNode.execute(obj).ffi_type_pointer;
            PyCArgObject parg = factory().createCArgObject();
            parg.tag = 'P';
            // parg.pffi_type = FFIType.ffi_type_pointer;
            parg.pffi_type = ffiType.getAsArray();
            parg.obj = obj;
            if (parg.pffi_type != ffiType && !obj.b_ptr.isManagedBytes()) {
                parg.value = PtrValue.allocate(parg.pffi_type, ffiType.size);
                parg.value.writeArrayElement(ffiType, 0, obj.b_ptr.getPrimitiveValue(ffiType));
                obj.b_ptr.ptr = parg.value.ptr;
            }
            parg.value = obj.b_ptr.ref(offset);

            return parg;
        }

        @SuppressWarnings("unused")
        @Fallback
        Object error(VirtualFrame frame, Object obj, Object off) {
            Object clazz = GetClassNode.getUncached().execute(obj);
            String name = GetNameNode.getUncached().execute(clazz);
            throw raise(TypeError, BYREF_ARGUMENT_MUST_BE_A_CTYPES_INSTANCE_NOT_S, name);
        }
    }

    protected static Object getObjectAt(PythonContext context, Object ptr) {
        if (PGuards.isInteger(ptr)) {
            return getObjectAtAddress(context, (Long) ptr);
        }
        return ptr;
    }

    @Builtin(name = "call_function", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
    @GenerateNodeFactory
    protected abstract static class CallFunctionNode extends PythonTernaryBuiltinNode {

        @Specialization
        Object call_function(VirtualFrame frame, PythonModule ctypesModule, Object f, PTuple arguments,
                        @Cached AuditNode auditNode,
                        @Cached CallProcNode callProcNode,
                        @Cached GetInternalObjectArrayNode getArray,
                        @Cached PyLongAsVoidPtr asVoidPtr) {
            // Object func = _parse_voidp(tuple[0]);
            NativeFunction func = (NativeFunction) getObjectAt(getContext(), asVoidPtr.execute(f));
            auditNode.audit("ctypes.call_function", func, arguments);
            CtypesThreadState ctypes = CtypesThreadState.get(getContext(), getLanguage());
            return callProcNode.execute(frame, func,
                            getArray.execute(arguments.getSequenceStorage()),
                            0, /* flags */
                            PythonUtils.EMPTY_OBJECT_ARRAY, /* self.argtypes */
                            PythonUtils.EMPTY_OBJECT_ARRAY, /* self.converters */
                            null, /* self.restype */
                            null,
                            ctypes,
                            factory(),
                            getContext(),
                            (CtypesModuleBuiltins) ctypesModule.getBuiltins());
        }
    }

    @Builtin(name = "addressof", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class AddressOfNode extends PythonUnaryBuiltinNode {

        @Specialization
        Object doit(CDataObject obj,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached AuditNode auditNode) {
            if (!pyTypeCheck.isCDataObject(obj)) {
                return error(null, obj);
            }
            auditNode.audit("ctypes.addressof", obj);
            return obj.b_ptr;
        }

        @SuppressWarnings("unused")
        @Fallback
        Object error(VirtualFrame frame, Object o) {
            throw raise(TypeError, INVALID_TYPE);
        }
    }

    @Builtin(name = "call_cdeclfunction", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
    @GenerateNodeFactory
    protected abstract static class CallCdeclfunctionNode extends PythonTernaryBuiltinNode {

        @Specialization
        Object doit(VirtualFrame frame, PythonModule ctypesModule, Object f, PTuple arguments,
                        @Cached AuditNode auditNode,
                        @Cached GetInternalObjectArrayNode getArray,
                        @Cached CallProcNode callProcNode,
                        @Cached PyLongAsVoidPtr asVoidPtr) {
            // Object func = _parse_voidp(tuple[0]);
            NativeFunction func = (NativeFunction) getObjectAt(getContext(), asVoidPtr.execute(f));
            auditNode.audit("ctypes.call_function", func, arguments);
            CtypesThreadState ctypes = CtypesThreadState.get(getContext(), getLanguage());
            return callProcNode.execute(frame, func,
                            getArray.execute(arguments.getSequenceStorage()),
                            FUNCFLAG_CDECL, /* flags */
                            PythonUtils.EMPTY_OBJECT_ARRAY, /* self.argtypes */
                            PythonUtils.EMPTY_OBJECT_ARRAY, /* self.converters */
                            null, /* self.restype */
                            null,
                            ctypes,
                            factory(),
                            getContext(),
                            (CtypesModuleBuiltins) ctypesModule.getBuiltins());
        }
    }

    protected static final class argument {
        FFIType ffi_type;
        Object keep;
        StgDictObject stgDict;
        Object value;
    }

    /**
     * Requirements, must be ensured by the caller: - argtuple is tuple of arguments - argtypes is
     * either NULL, or a tuple of the same size as argtuple
     *
     * - XXX various requirements for restype, not yet collected
     *
     * argtypes is amisleading name: This is a tuple of methods, not types: the .from_param class
     * methods of the types
     */
    protected abstract static class CallProcNode extends PNodeWithRaise {

        abstract Object execute(VirtualFrame frame, NativeFunction pProc, Object[] argtuple, int flags, Object[] argtypes, Object[] converters, Object restype, Object checker,
                        CtypesThreadState state,
                        PythonObjectFactory factory,
                        PythonContext context,
                        CtypesModuleBuiltins ctypesModuleBuiltins);

        /*
         * bpo-13097: Max number of arguments _ctypes_callproc will accept.
         *
         * This limit is enforced for the `alloca()` call in `_ctypes_callproc`, to avoid allocating
         * a massive buffer on the stack.
         */
        protected static final int CTYPES_MAX_ARGCOUNT = 1024;

        @Specialization(guards = "!pProc.isManaged() || pProc.isLLVM()")
        Object _ctypes_callproc(VirtualFrame frame,
                        NativeFunction pProc,
                        Object[] argarray,
                        @SuppressWarnings("unused") int flags,
                        Object[] argtypes, Object[] converters,
                        Object restype,
                        Object checker,
                        @SuppressWarnings("unused") CtypesThreadState state,
                        PythonObjectFactory factory,
                        PythonContext context,
                        CtypesModuleBuiltins ctypesModuleBuiltins,
                        @Cached ConvParamNode convParamNode,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Cached CallNode callNode,
                        @Cached GetResultNode getResultNode,
                        @CachedLibrary(limit = "1") InteropLibrary ilib) {
            int argcount = argarray.length;
            if (argcount > CTYPES_MAX_ARGCOUNT) {
                throw raise(ArgError, TOO_MANY_ARGUMENTS_D_MAXIMUM_IS_D, argcount, CTYPES_MAX_ARGCOUNT);
            }

            argument[] args = new argument[argcount];
            Object[] avalues = new Object[argcount];
            FFIType[] atypes = new FFIType[argcount];
            int argtype_count = argtypes != null ? argtypes.length : 0;

            /* Convert the arguments */
            final boolean isLLVM = pProc.isLLVM();
            for (int i = 0; i < argcount; ++i) {
                args[i] = new argument();
                Object arg = argarray[i]; /* borrowed ref */
                /*
                 * For cdecl functions, we allow more actual arguments than the length of the
                 * argtypes tuple. This is checked in _ctypes::PyCFuncPtr_Call
                 */
                if (converters != null && argtype_count > i) {
                    Object v;
                    try {
                        v = callNode.execute(frame, converters[i], argtypes[i], arg);
                    } catch (PException e) {
                        throw raise(ArgError, ARGUMENT_D, i + 1);
                    }

                    convParamNode.execute(frame, v, i + 1, args[i], factory, context);
                } else {
                    convParamNode.execute(frame, arg, i + 1, args[i], factory, context);
                }
                FFIType ffiType = args[i].ffi_type;
                atypes[i] = ffiType;
                Object value = args[i].value;
                if (ffiType.type.isArray()) {
                    if (!isLLVM) {
                        value = context.getEnv().asGuestValue(value);
                    } else {
                        if (ffiType.type == FFI_TYPE_STRUCT) {
                            assert value instanceof byte[] : "It should be byte[]!";
                            assert args[i].stgDict != null : "We need stgDict for Structs";
                            value = CDataObject.createWrapper(args[i].stgDict, (byte[]) value);
                        } else {
                            value = context.getEnv().asGuestValue(value);
                        }
                    }
                }
                avalues[i] = value;
            }

            FFIType rtype = FFIType.ffi_type_sint;
            if (restype != null) {
                StgDictObject dict = pyTypeStgDictNode.execute(restype);
                if (dict != null) {
                    rtype = dict.ffi_type_pointer;
                }
            }
            Object result;
            if (isLLVM) {
                result = callManagedFunction(pProc, avalues, ilib);
            } else {
                result = callNativeFunction(pProc, avalues, atypes, rtype, context, ilib);
            }
            if (rtype.type.isArray()) {
                if (ilib.hasArrayElements(result)) {
                    try {
                        long resSize = ilib.getArraySize(result);
                        byte[] bytes = new byte[(int) resSize];
                        for (int i = 0; i < resSize; i++) {
                            bytes[i] = (byte) ilib.readArrayElement(result, i);
                        }
                        result = bytes;
                    } catch (UnsupportedMessageException | InvalidArrayIndexException e) {
                        throw CompilerDirectives.shouldNotReachHere(e);
                    }
                } else if (!isLLVM && ilib.isPointer(result)) {
                    result = getNativeBytes(ctypesModuleBuiltins, getContext(), result, getRaiseNode());
                } else if (ilib.isNumber(result)) {
                    byte[] bytes = new byte[rtype.size];
                    CtypesNodes.setValue(rtype.type, bytes, 0, result);
                    result = bytes;
                } else if (ilib.isNull(result)) {
                    return PNone.NONE;
                } else {
                    throw raise(NotImplementedError, "Returned object is not supported.");
                }
            } else {
                try {
                    switch (rtype.type) {
                        case FFI_TYPE_UINT8:
                        case FFI_TYPE_SINT8:
                            result = ilib.asByte(result);
                            break;
                        case FFI_TYPE_UINT16:
                        case FFI_TYPE_SINT16:
                            result = ilib.asShort(result);
                            break;
                        case FFI_TYPE_UINT32:
                        case FFI_TYPE_SINT32:
                            result = ilib.asInt(result);
                            break;
                        case FFI_TYPE_SINT64:
                        case FFI_TYPE_UINT64:
                            result = ilib.asLong(result);
                            break;
                    }
                } catch (UnsupportedMessageException e) {
                    // pass through.
                }
            }

            if (!PGuards.isPNone(checker)) {
                if (rtype.type.isArray()) {
                    throw raise(NotImplementedError, "Array checker is not implemented.");
                }
                return callNode.execute(checker, result);
            }

            // return result;
            /*- TODO (mq) require more support from NFI.
            Object resbuf = alloca(max(rtype.size, sizeof(ffi_arg)));
            _call_function_pointer(flags, pProc, avalues, atypes, rtype, resbuf, argcount, state);
            */
            return getResultNode.execute(restype, rtype, result, checker, factory, getRaiseNode());
        }

        @Specialization(guards = "pProc.isManaged()")
        Object doManaged(NativeFunction pProc,
                        Object[] argarray,
                        @SuppressWarnings("unused") int flags,
                        @SuppressWarnings("unused") Object[] argtypes,
                        @SuppressWarnings("unused") Object[] converters,
                        @SuppressWarnings("unused") Object restype,
                        @SuppressWarnings("unused") Object checker,
                        @SuppressWarnings("unused") CtypesThreadState state,
                        @SuppressWarnings("unused") PythonObjectFactory factory,
                        @SuppressWarnings("unused") PythonContext context,
                        @SuppressWarnings("unused") CtypesModuleBuiltins ctypesModuleBuiltins,
                        @CachedLibrary(limit = "1") InteropLibrary ilib) {
            return callManagedFunction(pProc, argarray, ilib);
        }

        Object callManagedFunction(NativeFunction pProc, Object[] argarray, InteropLibrary ilib) {
            try {
                return ilib.execute(pProc.sym, argarray);
            } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                throw raise(RuntimeError, FFI_CALL_FAILED);
            } catch (UnsupportedSpecializationException ee) {
                throw raise(NotImplementedError, "require backend support."); // TODO: llvm/GR-???
            }
        }

        @TruffleBoundary
        protected static Object getFunction(NativeFunction pProc, String signature, PythonContext context) throws Exception {
            Source source = Source.newBuilder(NFI_LANGUAGE, signature, pProc.name).build();
            Object nfiSignature = context.getEnv().parseInternal(source).call();
            return SignatureLibrary.getUncached().bind(nfiSignature, pProc.sym);
        }

        @TruffleBoundary
        private static byte[] getNativeBytes(CtypesModuleBuiltins ctypesModuleBuiltins, PythonContext context, Object pointer, PRaiseNode raiseNode) {
            try {
                long size = (Long) InteropLibrary.getUncached().execute(ctypesModuleBuiltins.getStrlenFunction(), pointer);
                byte[] bytes = new byte[(int) size];
                InteropLibrary.getUncached().execute(ctypesModuleBuiltins.getMemcpyFunction(), context.getEnv().asGuestValue(bytes), pointer, size);
                return bytes;
            } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                throw raiseNode.raise(SystemError, e);
            }
        }

        /**
         * NFI compatible native function calls (temporary replacement)
         */
        Object callNativeFunction(NativeFunction pProc, Object[] avalues, FFIType[] atypes, FFIType restype,
                        PythonContext context,
                        InteropLibrary ilib) {
            if (pProc.function == null) {
                String signature = FFIType.buildNFISignature(atypes, restype);
                Object function;
                try {
                    function = getFunction(pProc, signature, context);
                } catch (Exception e) {
                    throw raise(RuntimeError, FFI_PREP_CIF_FAILED);
                }
                pProc.function = function;
                pProc.signature = signature;
            } else {
                assert PString.equals(pProc.signature, FFIType.buildNFISignature(atypes, restype));
            }
            try {
                return ilib.execute(pProc.function, avalues);
            } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                throw raise(RuntimeError, FFI_CALL_FAILED);
            }
        }

        /*
         * libffi uses:
         *
         * ffi_status ffi_prep_cif(ffi_cif *cif, ffi_abi abi, unsigned int nargs, ffi_type *rtype,
         * ffi_type **atypes);
         *
         * and then
         *
         * void ffi_call(ffi_cif *cif, void *fn, void *rvalue, void **avalues);
         */
        /*- TODO (mq) require more support from NFI.
        void _call_function_pointer(int flags,
                                    NativeFunction pProc,
                                    Object[] avalues,
                                    FFIType[] atypes,
                                    FFIType restype,
                                    Object resmem,
                                    int argcount,
                                    CtypesThreadState state) {
                // XXX check before here
                if (restype == null) {
                    throw raise(RuntimeError, NO_FFI_TYPE_FOR_RESULT);
                }
        
                int cc = FFI_DEFAULT_ABI;
                ffi_cif cif;
                if (FFI_OK != ffi_prep_cif(&cif, cc, argcount, restype, atypes)) {
                    throw raise(RuntimeError, FFI_PREP_CIF_FAILED);
                }
        
                Object error_object = null;
                if ((flags & (FUNCFLAG_USE_ERRNO | FUNCFLAG_USE_LASTERROR)) != 0) {
                    error_object = state.errno;
                }
                if ((flags & FUNCFLAG_PYTHONAPI) == 0)
                    Py_UNBLOCK_THREADS
                if ((flags & FUNCFLAG_USE_ERRNO) != 0) {
                    int temp = state.errno;
                    state.errno = errno;
                    errno = temp;
                }
                ffi_call(&cif, pProc, resmem, avalues);
                if ((flags & FUNCFLAG_USE_ERRNO) != 0) {
                    int temp = state.errno;
                    state.errno = errno;
                    errno = temp;
                }
                if ((flags & FUNCFLAG_PYTHONAPI) == 0)
                    Py_BLOCK_THREADS
                if ((flags & FUNCFLAG_PYTHONAPI) && PyErr_Occurred()) {
                }
        }
        */
    }

    abstract static class GetResultNode extends Node {

        abstract Object execute(Object restype, FFIType rtype, Object result, Object checker,
                        PythonObjectFactory factory,
                        PRaiseNode raiseNode);

        /*
         * Convert the C value in result into a Python object, depending on restype.
         *
         * - If restype is NULL, return a Python integer. - If restype is None, return None. - If
         * restype is a simple ctypes type (c_int, c_void_p), call the type's getfunc, pass the
         * result to checker and return the result. - If restype is another ctypes type, return an
         * instance of that. - Otherwise, call restype and return the result.
         */
        @Specialization(guards = "restype == null")
        static Object asIs(@SuppressWarnings("unused") Object restype, FFIType rtype, Object result, @SuppressWarnings("unused") Object checker,
                        @SuppressWarnings("unused") PythonObjectFactory factory,
                        @SuppressWarnings("unused") PRaiseNode raiseNode) {
            assert !rtype.type.isArray() : "need array conversion!";
            return result;
        }

        @Specialization(guards = {"restype != null", "dict == null"})
        static Object callResType(Object restype, FFIType rtype, Object result, @SuppressWarnings("unused") Object checker,
                        @SuppressWarnings("unused") PythonObjectFactory factory,
                        @SuppressWarnings("unused") PRaiseNode raiseNode,
                        @SuppressWarnings("unused") @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @SuppressWarnings("unused") @Bind("getStgDict(restype, pyTypeStgDictNode)") StgDictObject dict,
                        @Cached CallNode callNode) {
            assert !rtype.type.isArray() : "must be an int!";
            // return PyObject_CallFunction(restype, "i", *(int *)result);
            return callNode.execute(restype, result);
        }

        @Specialization(guards = {"restype != null", "dict != null"})
        static Object callGetFunc(Object restype, FFIType rtype, Object result, Object checker,
                        PythonObjectFactory factory,
                        PRaiseNode raiseNode,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Bind("getStgDict(restype, pyTypeStgDictNode)") StgDictObject dict,
                        @Cached CallNode callNode,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached GetBaseClassNode getBaseClassNode,
                        @Cached IsSameTypeNode isSameTypeNode,
                        @Cached GetFuncNode getFuncNode) {
            Object retval;
            PtrValue r;
            if (rtype.type.isArray()) {
                assert result instanceof byte[] : "byte[] should have been extracted after native call";
                r = PtrValue.bytes(rtype, (byte[]) result);
            } else {
                r = PtrValue.nil();
                r.toPrimitive(rtype, result);
            }
            if (dict.getfunc != FieldGet.nil && !pyTypeCheck.ctypesSimpleInstance(restype, getBaseClassNode, isSameTypeNode)) {
                retval = getFuncNode.execute(dict.getfunc, r, dict.size, factory);
            } else {
                retval = PyCData_FromBaseObj(restype, null, 0, r,
                                factory, raiseNode, pyTypeStgDictNode);
            }
            assert retval != null : "Should have raised an error earlier!";
            if (PGuards.isPNone(checker) || checker == null) {
                return retval;
            }
            return callNode.execute(checker, retval);
        }

        protected static StgDictObject getStgDict(Object restype, PyTypeStgDictNode pyTypeStgDictNode) {
            return pyTypeStgDictNode.execute(restype);
        }
    }

    /*
     * Convert a single Python object into a PyCArgObject and return it.
     */
    protected abstract static class ConvParamNode extends PNodeWithRaise {

        abstract void execute(VirtualFrame frame, Object obj, int index, argument pa, PythonObjectFactory factory, PythonContext context);

        @Specialization
        void ConvParam(VirtualFrame frame, Object obj, int index, argument pa, PythonObjectFactory factory, PythonContext context,
                        @CachedLibrary(limit = "1") PythonBufferAccessLibrary bufferLib,
                        @Cached PyLongCheckNode longCheckNode,
                        @Cached IsBuiltinClassProfile profile,
                        @Cached PyNumberAsSizeNode asInt,
                        @Cached LookupAttributeInMRONode.Dynamic lookupAttr,
                        @Cached PyObjectStgDictNode pyObjectStgDictNode) {
            pa.keep = null; /* so we cannot forget it later */

            StgDictObject dict = pyObjectStgDictNode.execute(obj);
            pa.stgDict = dict;
            if (dict != null) {
                assert dict.paramfunc != -1;
                /* If it has an stgdict, it is a CDataObject */
                PyCArgObject carg = paramFunc(dict.paramfunc, (CDataObject) obj, dict, factory);
                pa.ffi_type = carg.pffi_type;
                // memcpy(&pa.value, &carg.value, sizeof(pa.value)); TODO
                assert carg.value.offset == 0 : "TODO";
                pa.value = carg.value.ptr.getValue();
                pa.keep = carg;
                return;
            }

            if (PGuards.isPyCArg(obj)) {
                PyCArgObject carg = (PyCArgObject) obj;
                pa.ffi_type = carg.pffi_type;
                pa.keep = obj;
                pa.stgDict = pyObjectStgDictNode.execute(carg.obj); // helpful for llvm backend
                assert carg.value.offset == 0 : "TODO";
                // memcpy(&pa.value, &carg.value, sizeof(pa.value)); TODO
                pa.value = carg.value.ptr.getValue();
                return;
            }

            /* check for None, integer, string or unicode and use directly if successful */
            if (obj == PNone.NONE) {
                pa.ffi_type = FFIType.ffi_type_pointer;
                pa.value = context.getEnv().asGuestValue(null); // TODO check
                return;
            }

            if (longCheckNode.execute(obj)) {
                pa.ffi_type = FFIType.ffi_type_sint;
                try {
                    pa.value = asInt.executeExact(frame, obj);
                } catch (PException e) {
                    e.expectOverflowError(profile);
                    throw raise(OverflowError, INT_TOO_LONG_TO_CONVERT);
                }
                return;
            }

            if (obj instanceof PBytes) {
                // pa.ffi_type = FFIType.ffi_type_pointer;
                pa.ffi_type = FFIType.ffi_type_sint8_array;
                pa.value = bufferLib.getCopiedByteArray(obj); // PyBytes_AsString(obj);
                pa.keep = obj;
                return;
            }

            /*-
            if (PyUnicode_Check(obj)) { // CTYPES_UNICODE
                pa.ffi_type = ffi_type_pointer;
                pa.value = PyUnicode_AsWideCharString(obj, NULL);
                pa.keep = PyCapsule_New(pa.value.p, CTYPES_CAPSULE_NAME_PYMEM, pymem_destructor);
                return 0;
            }
            */

            Object arg = lookupAttr.execute(obj, CDataTypeBuiltins._as_parameter_);

            /*
             * Which types should we exactly allow here? integers are required for using Python
             * classes as parameters (they have to expose the '_as_parameter_' attribute)
             */
            if (arg != null) {
                ConvParam(frame, arg, index, pa, factory, context, bufferLib, longCheckNode, profile, asInt, lookupAttr, pyObjectStgDictNode);
                return;
            }
            throw raise(TypeError, DON_T_KNOW_HOW_TO_CONVERT_PARAMETER_D, index);
        }
    }

    @Builtin(name = "PyObj_FromPtr", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class PyObjFromPtrNode extends PythonUnaryBuiltinNode {

        static Object converter(Object obj) { // , Object[] address) TODO
            // *address = PyLong_AsVoidPtr(obj);
            // return *address != NULL;
            return obj;
        }

        @Specialization
        static Object doit(Object obj,
                        @Cached AuditNode auditNode) {
            Object ob = converter(obj);
            auditNode.audit("ctypes.PyObj_FromPtr", "(O)", ob);
            return ob;
        }
    }

    @ImportStatic(CApiGuards.class)
    @Builtin(name = "Py_INCREF", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class PyINCREFNode extends PythonUnaryBuiltinNode {

        @Specialization(guards = "isNativeWrapper(arg)")
        static Object doNative(Object arg,
                        @Shared("ref") @Cached AddRefCntNode incRefCntNode) {
            incRefCntNode.execute(arg, 1 /* that's what this function is for */ + //
                            1 /* that for returning it */);
            return arg;
        }

        @Specialization(guards = "!isNativeWrapper(arg)")
        static Object other(Object arg,
                        @Shared("ref") @Cached AddRefCntNode incRefCntNode,
                        @CachedLibrary(limit = "2") InteropLibrary ilib) {
            if (!ilib.isNull(arg) && ilib.isPointer(arg) && ilib.hasMembers(arg)) {
                return doNative(arg, incRefCntNode);
            }
            // do nothing and return object
            return arg;
        }
    }

    @ImportStatic(CApiGuards.class)
    @Builtin(name = "Py_DECREF", minNumOfPositionalArgs = 1)
    @GenerateNodeFactory
    protected abstract static class PyDECREFNode extends PythonUnaryBuiltinNode {

        @Specialization(guards = "isNativeWrapper(arg)")
        static Object doNative(Object arg,
                        @Shared("dec") @Cached SubRefCntNode decRefCntNode,
                        @Shared("inc") @Cached AddRefCntNode incRefCntNode) {
            decRefCntNode.execute(arg, 1 /* that's what this function is for */);
            incRefCntNode.execute(arg, 1 /* that for returning it */);
            return arg;
        }

        @Specialization(guards = "!isNativeWrapper(arg)")
        static Object other(Object arg,
                        @Shared("dec") @Cached SubRefCntNode decRefCntNode,
                        @Shared("inc") @Cached AddRefCntNode incRefCntNode,
                        @CachedLibrary(limit = "2") InteropLibrary ilib) {
            if (!ilib.isNull(arg) && ilib.isPointer(arg) && ilib.hasMembers(arg)) {
                return doNative(arg, decRefCntNode, incRefCntNode);
            }
            // do nothing and return object
            return arg;
        }
    }

    @ExportLibrary(value = InteropLibrary.class, delegateTo = "llvmSym")
    @SuppressWarnings("static-method")
    protected static final class CallLLVMFunction implements TruffleObject {

        final Object llvmSym;

        private CallLLVMFunction(Object llvmSym) {
            this.llvmSym = llvmSym;
        }

        protected static Object create(Object sym, InteropLibrary lib) {
            if (lib.isExecutable(sym)) {
                return new CallLLVMFunction(sym);
            }
            return sym;
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        Object execute(Object[] arguments,
                        @CachedLibrary("this.llvmSym") InteropLibrary ilib) {
            try {
                return ilib.execute(llvmSym, arguments);
            } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
                throw PRaiseNode.getUncached().raise(RuntimeError, e);
            }
        }
    }

    @ImportStatic(PGuards.class)
    @GenerateUncached
    protected abstract static class CastFunctionNode extends Node {

        abstract Object execute(Object ptr, Object src, Object ctype);

        @Specialization
        static Object cast(CDataObject ptr, CDataObject src, Object ctype,
                        @CachedLibrary(limit = "1") HashingStorageLibrary hlib,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached PythonObjectFactory factory,
                        @Cached CallNode callNode,
                        @Cached PRaiseNode raiseNode,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Cached IsTypeNode isTypeNode,
                        @Cached GetClassNode getClassNode,
                        @Cached GetNameNode getNameNode) {
            cast_check_pointertype(ctype, raiseNode, pyTypeCheck, pyTypeStgDictNode, isTypeNode, getClassNode, getNameNode);
            CDataObject result = (CDataObject) callNode.execute(ctype);

            /*
             * The casted objects '_objects' member:
             *
             * It must certainly contain the source objects one. It must contain the source object
             * itself.
             */
            if (pyTypeCheck.isCDataObject(src)) {
                /*
                 * PyCData_GetContainer will initialize src.b_objects, we need this so it can be
                 * shared
                 */
                PyCData_GetContainer(src, factory);

                /* But we need a dictionary! */
                if (src.b_objects == null) {
                    src.b_objects = factory.createDict();
                }
                result.b_objects = src.b_objects;
                if (PGuards.isDict(result.b_objects)) {
                    // PyLong_FromVoidPtr((void *)src);
                    PDict dict = (PDict) result.b_objects;
                    Object index = factory.createNativeVoidPtr(src);
                    hlib.setItem(dict.getDictStorage(), index, src);
                }
            }
            /* Should we assert that result is a pointer type? */
            // memcpy(result.b_ptr, &ptr, sizeof(void *));
            result.b_ptr = ptr.b_ptr.ref(0);
            return result;
        }

        @Specialization(guards = "!isCDataObject(src)")
        static Object cast(PtrValue ptr, @SuppressWarnings("unused") Object src, Object ctype,
                        @Cached PyTypeCheck pyTypeCheck,
                        @Cached CallNode callNode,
                        @Cached PRaiseNode raiseNode,
                        @Cached PyTypeStgDictNode pyTypeStgDictNode,
                        @Cached IsTypeNode isTypeNode,
                        @Cached GetClassNode getClassNode,
                        @Cached GetNameNode getNameNode) {
            cast_check_pointertype(ctype, raiseNode, pyTypeCheck, pyTypeStgDictNode, isTypeNode, getClassNode, getNameNode);
            CDataObject result = (CDataObject) callNode.execute(ctype);

            /* Should we assert that result is a pointer type? */
            // memcpy(result.b_ptr, &ptr, sizeof(void *));
            result.b_ptr = ptr.ref(0);
            return result;
        }

        static void cast_check_pointertype(Object arg,
                        PRaiseNode raiseNode,
                        PyTypeCheck pyTypeCheck,
                        PyTypeStgDictNode pyTypeStgDictNode,
                        IsTypeNode isTypeNode,
                        GetClassNode getClassNode,
                        GetNameNode getNameNode) {
            if (pyTypeCheck.isPyCPointerTypeObject(arg)) {
                return;
            }
            if (pyTypeCheck.isPyCFuncPtrTypeObject(arg)) {
                return;
            }
            StgDictObject dict = pyTypeStgDictNode.execute(arg);
            if (dict != null && dict.proto != null) {
                if (PGuards.isString(dict.proto) && (strchr("sPzUZXO", PString.charAt((String) dict.proto, 0)))) {
                    /* simple pointer types, c_void_p, c_wchar_p, BSTR, ... */
                    return;
                }
            }
            Object clazz = isTypeNode.execute(arg) ? arg : getClassNode.execute(arg);
            throw raiseNode.raise(TypeError, CAST_ARGUMENT_2_MUST_BE_A_POINTER_TYPE_NOT_S, getNameNode.execute(clazz));
        }
    }

    @ExportLibrary(InteropLibrary.class)
    @SuppressWarnings("static-method")
    protected static final class CastFunction implements TruffleObject {

        protected static NativeFunction create() {
            Object f = new CastFunction();
            return new NativeFunction(f, PyObjectHashNodeGen.getUncached().execute(null, f), "cast", true);
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        Object execute(Object[] arguments,
                        @Cached CastFunctionNode castFunctionNode) {
            return castFunctionNode.execute(arguments[0], arguments[1], arguments[2]);
        }
    }

    @GenerateUncached
    protected abstract static class MemmoveNode extends Node {

        abstract Object execute(Object dest, Object src, Object size);

        protected static boolean isManagedBytes(CDataObject cdata) {
            return cdata.b_ptr.isManagedBytes();
        }

        @Specialization(guards = "isManagedBytes(dest)")
        static Object memmove(CDataObject dest, PBytes src, int size,
                        @Cached GetInternalByteArrayNode getBytes) {
            byte[] srcBytes = getBytes.execute(src.getSequenceStorage());
            byte[] destBytes = ((ByteArrayStorage) dest.b_ptr.ptr).value;
            PythonUtils.arraycopy(srcBytes, 0, destBytes, dest.b_ptr.offset, size);
            return dest;
        }

        @Specialization(guards = {"isManagedBytes(dest)", "isManagedBytes(src)"})
        static Object memmove(CDataObject dest, CDataObject src, int size) {
            byte[] srcBytes = ((ByteArrayStorage) src.b_ptr.ptr).value;
            byte[] destBytes = ((ByteArrayStorage) dest.b_ptr.ptr).value;
            PythonUtils.arraycopy(srcBytes, src.b_ptr.offset, destBytes, dest.b_ptr.offset, size);
            return dest;
        }

    }

    @ExportLibrary(InteropLibrary.class)
    @SuppressWarnings("static-method")
    protected static final class MemMoveFunction implements TruffleObject {

        final Object nativeMemmove;

        MemMoveFunction(Object nativeMemmove) {
            this.nativeMemmove = nativeMemmove;
        }

        protected static NativeFunction create(DLHandler handle, PythonContext context) {
            String name = "memset";
            Object sym, f;
            long adr;
            try {
                sym = InteropLibrary.getUncached().readMember(handle.library, name);
                adr = InteropLibrary.getUncached().asPointer(sym);
                f = new MemMoveFunction(sym);
            } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                f = new MemMoveFunction(null);
                adr = PyObjectHashNodeGen.getUncached().execute(null, f);
                // Not supported.. carry on
            }
            NativeFunction func = new NativeFunction(f, adr, name, true);
            registerAddress(context, adr, func);
            return func;
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        static class Execute {

            @Specialization(guards = "!isNative(arguments)")
            static Object managed(@SuppressWarnings("unused") MemMoveFunction self, Object[] arguments,
                            @Cached MemmoveNode memmoveNode) {
                return memmoveNode.execute(arguments[0], arguments[1], arguments[2]);
            }

            @SuppressWarnings("unused")
            @Specialization(guards = "isNative(arguments)")
            static Object nativeMove(@SuppressWarnings("unused") MemMoveFunction self, Object[] arguments) {
                throw PRaiseNode.getUncached().raise(NotImplementedError, "memmove doesn't support native types yet.");
            }

            private static boolean isNativePtr(Object o) {
                return o instanceof PtrValue && ((PtrValue) o).isNativePointer();
            }

            protected static boolean isNative(Object[] arguments) {
                return arguments.length > 2 && (isNativePtr(arguments[0]) || isNativePtr(arguments[1]));
            }
        }
    }

    @GenerateUncached
    protected abstract static class MemsetNode extends Node {

        abstract Object execute(Object dest, Object src, Object size);

        protected static boolean isManagedBytes(CDataObject cdata) {
            return cdata.b_ptr.isManagedBytes();
        }

        @Specialization(guards = "isManagedBytes(ptr)")
        static Object memset(CDataObject ptr, int value, int num) {
            byte[] ptrBytes = ((ByteArrayStorage) ptr.b_ptr.ptr).value;
            for (int i = ptr.b_ptr.offset; i < (ptr.b_ptr.offset + num); i++) {
                ptrBytes[i] = (byte) value;
            }
            return ptr;
        }

    }

    @ExportLibrary(InteropLibrary.class)
    @SuppressWarnings("static-method")
    protected static final class MemSetFunction implements TruffleObject {

        final Object nativeMemset;

        MemSetFunction(Object nativeMemset) {
            this.nativeMemset = nativeMemset;
        }

        protected static NativeFunction create(DLHandler handle, PythonContext context) {
            String name = "memset";
            Object sym, f;
            long adr;
            try {
                sym = InteropLibrary.getUncached().readMember(handle.library, name);
                adr = InteropLibrary.getUncached().asPointer(sym);
                f = new MemSetFunction(sym);
            } catch (UnsupportedMessageException | UnknownIdentifierException e) {
                f = new MemSetFunction(null);
                adr = PyObjectHashNodeGen.getUncached().execute(null, f);
                // Not supported.. carry on
            }
            NativeFunction func = new NativeFunction(f, adr, name, true);
            registerAddress(context, adr, func);
            return func;
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        static class Execute {

            @Specialization(guards = "!isNative(arguments)")
            static Object managed(@SuppressWarnings("unused") MemSetFunction self, Object[] arguments,
                            @Cached MemsetNode memsetNode) {
                return memsetNode.execute(arguments[0], arguments[1], arguments[2]);
            }

            @SuppressWarnings("unused")
            @Specialization(guards = "isNative(arguments)")
            static Object nativeSet(@SuppressWarnings("unused") MemSetFunction self, Object[] arguments) {
                throw PRaiseNode.getUncached().raise(NotImplementedError, "memset doesn't support native types yet.");
            }

            private static boolean isNativePtr(Object o) {
                return o instanceof PtrValue && ((PtrValue) o).isNativePointer();
            }

            protected static boolean isNative(Object[] arguments) {
                return arguments.length > 2 && isNativePtr(arguments[0]);
            }
        }
    }

    @GenerateUncached
    protected abstract static class StringAtFunctionNode extends Node {

        abstract Object execute(Object ptr, Object size);

        @Specialization
        static Object string_at(String ptr, int size,
                        @Cached PythonObjectFactory factory,
                        @Cached AuditNode auditNode) {
            auditNode.audit("ctypes.string_at", ptr, size);
            byte[] bytes;
            if (size == -1) {
                // PyBytes_FromStringAndSize(ptr, strlen(ptr));
                bytes = BytesUtils.utf8StringToBytes(ptr);
            } else {
                bytes = BytesUtils.utf8StringToBytes(PString.substring(ptr, 0, size));
            }
            return factory.createBytes(bytes);
        }

        @Specialization
        static Object string_at(CDataObject ptr, int size,
                        @Cached PythonObjectFactory factory,
                        @Cached PRaiseNode raiseNode,
                        @Cached AuditNode auditNode) {
            auditNode.audit("ctypes.string_at", ptr, size);
            byte[] bytes;
            if (ptr.b_ptr.isManagedBytes()) {
                ByteArrayStorage storage = (ByteArrayStorage) ptr.b_ptr.ptr;
                bytes = storage.trim(ptr.b_ptr.offset);
                if (size != -1) {
                    bytes = PythonUtils.arrayCopyOf(bytes, size);
                }
                // else PyBytes_FromStringAndSize(ptr, strlen(ptr));
            } else {
                throw raiseNode.raise(NotImplementedError, "string_at doesn't support some storage types yet.");
            }
            return factory.createBytes(bytes);
        }

        @TruffleBoundary
        @Fallback
        static Object error(@SuppressWarnings("unused") Object ptr, @SuppressWarnings("unused") Object size) {
            throw PRaiseNode.getUncached().raise(NotImplementedError, "string_at doesn't support some storage types yet.");
        }
    }

    @ExportLibrary(InteropLibrary.class)
    @SuppressWarnings("static-method")
    protected static final class StringAtFunction implements TruffleObject {

        protected static NativeFunction create() {
            Object f = new StringAtFunction();
            return new NativeFunction(f, PyObjectHashNodeGen.getUncached().execute(null, f), "string_at", true);
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        Object execute(Object[] arguments,
                        @Cached StringAtFunctionNode stringAtFunctionNode) {
            return stringAtFunctionNode.execute(arguments[0], arguments[1]);
        }

    }

    @GenerateUncached
    protected abstract static class WStringAtFunctionNode extends Node {

        abstract Object execute(Object ptr, Object size);

        @Specialization
        static Object wstring_at(String ptr, int size,
                        @Cached AuditNode auditNode) {
            int ssize = size;
            auditNode.audit("ctypes.wstring_at", ptr, ssize);
            if (ssize == -1) {
                ssize = PString.length(ptr); // wcslen(ptr);
            }
            return PString.substring(ptr, 0, ssize); // PyUnicode_FromWideChar(ptr, ssize);
        }

        @Specialization
        static Object wstring_at(CDataObject ptr, int size,
                        @Cached PRaiseNode raiseNode,
                        @Cached AuditNode auditNode) {
            auditNode.audit("ctypes.wstring_at", ptr, size);
            byte[] bytes;
            if (ptr.b_ptr.isManagedBytes()) {
                ByteArrayStorage storage = (ByteArrayStorage) ptr.b_ptr.ptr;
                bytes = storage.trim(ptr.b_ptr.offset);
                if (size != -1) {
                    bytes = PythonUtils.arrayCopyOf(bytes, size);
                }
                // else wcslen(ptr);
            } else {
                throw raiseNode.raise(NotImplementedError, "wstring_at doesn't support some storage types yet.");
            }
            return BytesUtils.createUTF8String(bytes);
        }

        @TruffleBoundary
        @Fallback
        static Object error(@SuppressWarnings("unused") Object ptr, @SuppressWarnings("unused") Object size) {
            throw PRaiseNode.getUncached().raise(NotImplementedError, "wstring_at doesn't support some storage types yet.");
        }
    }

    @ExportLibrary(InteropLibrary.class)
    @SuppressWarnings("static-method")
    protected static final class WStringAtFunction implements TruffleObject {

        protected static NativeFunction create() {
            Object f = new WStringAtFunction();
            return new NativeFunction(f, PyObjectHashNodeGen.getUncached().execute(null, f), "wstring_at", true);
        }

        @ExportMessage
        boolean isExecutable() {
            return true;
        }

        @ExportMessage
        Object execute(Object[] arguments,
                        @Cached WStringAtFunctionNode wStringAtFunctionNode) {
            return wStringAtFunctionNode.execute(arguments[0], arguments[1]);
        }
    }

    /*
     * Allocate a memory block for a pep3118 format string, adding the given prefix (if non-null),
     * an additional shape prefix, and a suffix. Returns NULL on failure, with the error indicator
     * set. If called with a suffix of NULL the error indicator must already be set.
     */
    static String _ctypes_alloc_format_string_with_shape(int ndim, int[] shape, String prefix, String suffix) {
        StringBuilder buf = newStringBuilder();
        if (prefix != null) {
            append(buf, prefix);
        }
        if (ndim > 0) {
            /* Add the prefix "(shape[0],shape[1],...,shape[ndim-1])" */
            append(buf, "(");
            String fmt = "%d,";
            for (int k = 0; k < ndim; ++k) {
                if (k == ndim - 1) {
                    fmt = "%d)";
                }
                append(buf, PythonUtils.format(fmt, shape[k]));
            }
        }
        if (suffix != null) {
            append(buf, suffix);
        }
        return sbToString(buf);
    }
}
