private Func <TypeDefinition[]> DefineClrType(ClangFunctionInfoBase funcInfo)
        {
            var funcName = funcInfo.Name;

            Debug.WriteLine($"Defining function {funcName}");

            if (TypeRedirects.TryGetValue(funcName, out var rename))
            {
                funcName = rename;
            }

            var funcRef = Module.GetType(funcName, true);
            var funcDef = funcRef.Resolve();

            if (funcDef != null)
            {
                return(null);
            }

            funcDef = Module.DefineType(funcName,
                                        DelegateTypeAttributes,
                                        MulticastDelegateType);
            funcDef.SetCustomAttribute(() => new BinderGeneratedAttribute());

            var umfpDef = new TypeDefinition(funcDef.Namespace, funcDef.Name + "Unmanaged",
                                             TypeAttributes.Sealed
                                             | TypeAttributes.BeforeFieldInit
                                             | TypeAttributes.SequentialLayout
                                             | TypeAttributes.Public,
                                             Module.TypeSystem.ValueType);

            Module.Types.Add(umfpDef);

            var umfpRef  = Module.ImportReference(umfpDef);
            var iumfpGtd = IUnmanagedFunctionPointerGtd.MakeGenericInstanceType(funcDef);
            var iumfpRef = iumfpGtd.Import(Module);

            umfpDef.AddInterfaceImplementation(iumfpRef);

            // todo: add implicit conversion ops using Marshal

            var retParam = ResolveParameter(funcInfo.ReturnType);

            /* todo: figure out why the attribute is jacked up
             * if (!CallingConventionMap.TryGetValue(funcInfo.CallConvention, out var callConv))
             *      throw new NotImplementedException();
             * if (!ClrCallingConventionAttributeMap.TryGetValue(callConv, out var callConvAttr))
             *      throw new NotImplementedException();
             */
            var argParams = new LinkedList <ParameterInfo>(funcInfo.Parameters.Select(p => ResolveParameter(p.Type, p.Name, (int)p.Index)));

            return(() => {
                retParam.Complete(TypeRedirects, true);

                var retType = retParam.Type;

                foreach (var argParam in argParams)
                {
                    argParam.Complete(TypeRedirects, true);
                }

                Debug.WriteLine($"Completed dependencies for function {funcName}");

                var umfpValue = umfpDef.DefineField("Value", Module.TypeSystem.IntPtr,
                                                    FieldAttributes.Public | FieldAttributes.InitOnly);
                var umfpValueRef = Module.ImportReference(umfpValue);


                var getDelegateForFpMethodDef = Module.ImportReference(
                    new GenericInstanceMethod(GetDelegateForFpMethodGtd)
                {
                    GenericArguments = { funcDef }
                });

                var getFpForDelegateMethodDef = Module.ImportReference(
                    new GenericInstanceMethod(GetFpForDelegateMethodGtd)
                {
                    GenericArguments = { funcDef }
                });

                var umfpDlgt = umfpDef.DefineMethod("op_Implicit",
                                                    PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                    funcDef, umfpRef);

                umfpDlgt.GenerateIL(il => {
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, umfpValueRef);
                    il.Emit(OpCodes.Call, getDelegateForFpMethodDef);
                    il.Emit(OpCodes.Ret);
                });

                var dlgtUmfp = umfpDef.DefineMethod("op_Implicit",
                                                    PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                    umfpRef, funcDef);

                var dlgtUmfpV0 = new VariableDefinition(umfpRef);
                dlgtUmfp.Body.Variables.Add(dlgtUmfpV0);
                dlgtUmfp.Body.InitLocals = true;

                dlgtUmfp.GenerateIL(il => {
                    il.Emit(OpCodes.Ldloca_S, dlgtUmfpV0);
                    il.Emit(OpCodes.Initobj, umfpRef);

                    il.Emit(OpCodes.Ldloca_S, dlgtUmfpV0);

                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Call, getFpForDelegateMethodDef);
                    il.Emit(OpCodes.Stfld, umfpValueRef);

                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                });

                var umfpPtr = umfpDef.DefineMethod("op_Implicit",
                                                   PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                   Module.TypeSystem.IntPtr, umfpRef);

                umfpPtr.GenerateIL(il => {
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, umfpValueRef);
                    il.Emit(OpCodes.Ret);
                });

                var ptrUmfp = umfpDef.DefineMethod("op_Implicit",
                                                   PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                   umfpRef, Module.TypeSystem.IntPtr);

                var ptrUmfpV0 = new VariableDefinition(umfpRef);
                ptrUmfp.Body.Variables.Add(ptrUmfpV0);
                ptrUmfp.Body.InitLocals = true;

                ptrUmfp.GenerateIL(il => {
                    var argNull = default(CecilLabel);
                    if (EmitNullChecks)
                    {
                        argNull = il.DefineLabel();
                    }
                    il.Emit(OpCodes.Ldloca_S, ptrUmfpV0);
                    il.Emit(OpCodes.Initobj, umfpRef);

                    il.Emit(OpCodes.Ldloca_S, ptrUmfpV0);
                    il.Emit(OpCodes.Ldarg_0);

                    if (EmitNullChecks)
                    {
                        il.Emit(OpCodes.Dup);
                        il.Emit(OpCodes.Brfalse, argNull);
                    }
                    il.Emit(OpCodes.Stfld, umfpValueRef);

                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);

                    if (EmitNullChecks)
                    {
                        il.MarkLabel(argNull);
                        il.Emit(OpCodes.Newobj, ArgumentNullCtor);
                        il.Emit(OpCodes.Throw);

                        // ReSharper disable once PossibleNullReferenceException
                        argNull.Cleanup();
                    }
                });


                var argTypes = argParams.Select(p => p.Type).ToArray();


                var retTypeDef = retType.Resolve();
                if (retTypeDef.BaseType != null && retTypeDef.BaseType.Is(MulticastDelegateType))
                {
                    // todo: marshalas umfp marshaller
                    var ufpSpecTypeRef = Module.GetType(retTypeDef.FullName + "Unmanaged");
                    Debug.Assert(ufpSpecTypeRef != null);
                    retType = ufpSpecTypeRef;
                }

                for (var i = 0; i < argTypes.Length; i++)
                {
                    var argType = argTypes[i];
                    var argTypeDef = argType.Resolve();
                    if (argType.IsPointer)
                    {
                        var interiorArgType = argType.GetInteriorType(out var transforms);
                        if (interiorArgType.Resolve().IsInterface)
                        {
                            if (!argType.Name.StartsWith("I"))
                            {
                                throw new NotImplementedException();
                            }
                            var argTypeNameBase = interiorArgType.Name.Substring(1);
                            if (!MarshallableSplitPointers.TryGetValue(argTypeNameBase, out var marshallable))
                            {
                                TypeReference splitPtrTypeRef;
                                if (_splitPointerDefs.TryGetValue(interiorArgType.FullName, out var splitPtrType))
                                {
                                    splitPtrTypeRef = splitPtrType;
                                }
                                else
                                {
                                    var argType32 = Module.GetType(interiorArgType.Namespace, argTypeNameBase + "32");
                                    Debug.Assert(argType32 != null);
                                    var argType64 = Module.GetType(interiorArgType.Namespace, argTypeNameBase + "64");
                                    Debug.Assert(argType64 != null);
                                    splitPtrType = SplitPointerGtd.MakeGenericInstanceType(interiorArgType, argType32, argType64);
                                    if (!_splitPointerDefs.TryAdd(interiorArgType.FullName, splitPtrType))
                                    {
                                        throw new NotImplementedException();
                                    }
                                    splitPtrTypeRef = splitPtrType.Import(Module);
                                }

                                marshallable = Module.DefineType(argTypeNameBase + "Ptr", PublicSealedStructTypeAttributes);
                                var valueField = marshallable.DefineField("Value", splitPtrTypeRef,
                                                                          FieldAttributes.Public | FieldAttributes.InitOnly);

                                // implicit conversion from split pointer to marshallable for use as params
                                var asSplitPtrOp = marshallable.DefineMethod("op_Implicit",
                                                                             PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                                             splitPtrTypeRef, marshallable);
                                asSplitPtrOp.GenerateIL(il => {
                                    il.Emit(OpCodes.Ldarg_0);
                                    il.Emit(OpCodes.Ldfld, valueField);
                                    il.Emit(OpCodes.Ret);
                                });

                                // implicit conversion from marshallable to split pointer for use as returns
                                var toSplitPtrOp = marshallable.DefineMethod("op_Implicit",
                                                                             PublicStaticMethodAttributes | MethodAttributes.SpecialName,
                                                                             marshallable, splitPtrTypeRef);
                                var toSplitPtrOpV0 = new VariableDefinition(marshallable);
                                toSplitPtrOp.Body.Variables.Add(toSplitPtrOpV0);
                                toSplitPtrOp.Body.InitLocals = true;
                                toSplitPtrOp.GenerateIL(il => {
                                    il.Emit(OpCodes.Ldloca_S, ptrUmfpV0);
                                    il.Emit(OpCodes.Initobj, marshallable);
                                    il.Emit(OpCodes.Ldloca_S, ptrUmfpV0);
                                    il.Emit(OpCodes.Ldarg_0);
                                    il.Emit(OpCodes.Stfld, valueField);
                                    il.Emit(OpCodes.Ldloc_0);
                                    il.Emit(OpCodes.Ret);
                                });

                                if (!MarshallableSplitPointers.TryAdd(argTypeNameBase, marshallable))
                                {
                                    throw new NotImplementedException();
                                }
                            }
                            Debug.Assert(marshallable != null);
                            argTypes[i] = marshallable.Import(Module)
                                          .ApplyTransforms(transforms.Skip(1));



                            continue;
                        }
                    }
                    if (argTypeDef.BaseType == null)
                    {
                        continue;
                    }
                    if (!argTypeDef.BaseType.Is(MulticastDelegateType))
                    {
                        continue;
                    }

                    // todo: marshalas umfp marshaller
                    var ufpSpecTypeRef = Module.GetType(argTypeDef.FullName + "Unmanaged");
                    Debug.Assert(ufpSpecTypeRef != null);
                    argTypes[i] = ufpSpecTypeRef;
                }


                try {
                    var ctor = funcDef.DefineConstructor(DelegateConstructorAttributes,
                                                         Module.TypeSystem.Object, Module.TypeSystem.IntPtr);
                    ctor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);

                    var method = funcDef.DefineMethod("Invoke",
                                                      DelegateInvokeMethodAttributes,
                                                      retType, argTypes);

                    method.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);

                    // todo: figure out why the attribute is jacked up
                    //method.SetCustomAttribute(callConvAttr);


                    argParams.ConsumeLinkedList((argParam, i) => {
                        var param = method.DefineParameter(i + 1, argParam.Attributes, argParam.Name);
                    });

                    return new[] { funcDef.CreateType() };
                }
                catch (Exception ex) {
                    throw new InvalidProgramException("Critical function type definition failure.", ex);
                }
            });
        }