private void MarkExternalMethod(MethodReference methodReference) { // If a method contains generic parameters, skip it if (ResolveGenericsVisitor.ContainsGenericParameters(methodReference)) { return; } // Handle circular assembly references if (methodReference.Module == assembly.MainModule) { return; } var methodDefinition = methodReference.Resolve(); if (methodDefinition == null || methodDefinition.Module == assembly.MainModule) { return; } // Register method if (referencedMethods.Add(methodReference)) { markedMethods.Add(methodReference); } }
void PrepareClassMethods(Type type) { var @class = GetClass(type); // Already processed? if (@class == null || @class.MethodCompiled) { return; } @class.MethodCompiled = true; // Array: no need to do anything (its type definition, Array, has already been processed) if (@class.Type.TypeReference is ArrayType) { return; } var typeDefinition = GetMethodTypeDefinition(@class.Type.TypeReference); bool isInterface = typeDefinition.IsInterface; // Process methods, Virtual first, then non virtual, then static foreach (var method in typeDefinition.Methods.OrderBy(x => x.IsVirtual ? 0 : (!x.IsStatic ? 1 : 2))) { var methodReference = ResolveGenericMethod(@class.Type.TypeReference, method); // If a method contains generic parameters, skip it // Its closed instantiations (with generic arguments) is what needs to be generated. // (except interface methods) // Using ResolveGenericsVisitor.ContainsGenericParameters because Cecil one's doesn't seem to match what .NET Type does. // TODO: Might need a more robust generic resolver/analyzer system soon. if (ResolveGenericsVisitor.ContainsGenericParameters(methodReference)) { continue; } var function = CreateFunction(methodReference); @class.Functions.Add(function); if (method.IsSpecialName && method.Name == ".cctor") { @class.StaticCtor = function; } if (method.IsVirtual) { if (isInterface) { // Store IMT slot function.VirtualSlot = (int)(GetMethodId(methodReference) % InterfaceMethodTableSize); } else if (method.IsNewSlot) { // New slot function.VirtualSlot = @class.VirtualTable.Count; @class.VirtualTable.Add(function); } else { // Find slot in base types var baseType = @class.BaseType; Function matchedMethod = null; while (baseType != null) { matchedMethod = CecilExtensions.TryMatchMethod(baseType, methodReference); if (matchedMethod != null) { break; } baseType = baseType.BaseType; } if (matchedMethod == null) { throw new InvalidOperationException(string.Format("Could not find a slot for virtual function {0} in parents of class {1}", method, @class.Type.TypeReference)); } function.VirtualSlot = matchedMethod.VirtualSlot; @class.VirtualTable[function.VirtualSlot] = function; } } else { // New slot function.VirtualSlot = @class.VirtualTable.Count; @class.VirtualTable.Add(function); } } }
private void BuildRuntimeType(Class @class) { if (@class.IsEmitted) { return; } //Console.WriteLine("Build type {0}", @class); @class.IsEmitted = true; var zero = LLVM.ConstInt(int32LLVM, 0, false); bool isConcreteType = IsConcreteType(@class.Type); // Prepare metadata info var sharpLangModule = TestMode ? LLVM.ConstNull(intPtrLLVM) : metadataPerModule[@class.Type.TypeDefinitionCecil.Module]; var extraTypeInfo = LLVM.ConstNull(intPtrLLVM); var elementTypeSize = zero; // Build RTTI var runtimeTypeInfoGlobal = @class.GeneratedEETypeTokenLLVM; var runtimeTypeInfoType = LLVM.GetElementType(LLVM.TypeOf(runtimeTypeInfoGlobal)); var runtimeTypeInfoTypeElements = new TypeRef[LLVM.CountStructElementTypes(runtimeTypeInfoType)]; LLVM.GetStructElementTypes(runtimeTypeInfoType, runtimeTypeInfoTypeElements); var typeSpecification = @class.Type.TypeReferenceCecil as TypeSpecification; if (typeSpecification != null) { if (typeSpecification is ArrayType || typeSpecification is ByReferenceType || typeSpecification is PointerType) { // Get element type var elementType = GetType(typeSpecification.ElementType, TypeState.VTableEmitted); elementTypeSize = LLVM.ConstIntCast(LLVM.SizeOf(elementType.DefaultTypeLLVM), int32LLVM, false); extraTypeInfo = LLVM.ConstPtrToInt(elementType.Class.GeneratedEETypeRuntimeLLVM, nativeIntLLVM); if (typeSpecification is ArrayType) { extraTypeInfo = LLVM.ConstAdd(extraTypeInfo, LLVM.ConstInt(nativeIntLLVM, (int)ExtraTypeKind.Array, false)); } else if (typeSpecification is ByReferenceType) { extraTypeInfo = LLVM.ConstAdd(extraTypeInfo, LLVM.ConstInt(nativeIntLLVM, (int)ExtraTypeKind.ByRef, false)); } else if (typeSpecification is PointerType) { extraTypeInfo = LLVM.ConstAdd(extraTypeInfo, LLVM.ConstInt(nativeIntLLVM, (int)ExtraTypeKind.Pointer, false)); } extraTypeInfo = LLVM.ConstIntToPtr(extraTypeInfo, intPtrLLVM); } var genericInstanceType = typeSpecification as GenericInstanceType; if (genericInstanceType != null) { // Build global with array of VTable (one for each generic argument) var genericArgumentsTypes = genericInstanceType.GenericArguments.Select(x => GetType(x, TypeState.VTableEmitted)).ToArray(); var genericArgumentesTypeGlobalType = LLVM.ArrayType(intPtrLLVM, (uint)genericArgumentsTypes.Length + 1); var genericArgumentsTypesGlobal = LLVM.AddGlobal(module, genericArgumentesTypeGlobalType, @class.Type.TypeReferenceCecil.MangledName() + ".genericarguments"); LLVM.SetLinkage(genericArgumentsTypesGlobal, Linkage.PrivateLinkage); LLVM.SetInitializer(genericArgumentsTypesGlobal, LLVM.ConstArray(intPtrLLVM, genericArgumentsTypes .Select(x => LLVM.ConstPointerCast(x.Class.GeneratedEETypeTokenLLVM, intPtrLLVM)) .Concat(new[] { LLVM.ConstPointerNull(intPtrLLVM) }) .ToArray())); extraTypeInfo = LLVM.ConstInBoundsGEP(genericArgumentsTypesGlobal, new[] { zero, zero }); extraTypeInfo = LLVM.ConstPointerCast(extraTypeInfo, intPtrLLVM); } } var runtimeTypeFields = new List <ValueRef> { @class.BaseType != null ? @class.BaseType.GeneratedEETypeTokenLLVM : LLVM.ConstPointerNull(intPtrLLVM), LLVM.ConstInt(LLVM.Int8TypeInContext(context), isConcreteType ? 1U : 0U, false), LLVM.ConstNamedStruct(typeDefLLVM, new[] { sharpLangModule, LLVM.ConstInt(int32LLVM, @class.Type.TypeDefinitionCecil.MetadataToken.ToUInt32(), false), }), extraTypeInfo, LLVM.ConstNull(sharpLangTypeType.DefaultTypeLLVM), }; // Prepare the runtime type object var mangledTypeName = Regex.Replace(@class.Type.TypeReferenceCecil.MangledName() + ".sharplangtype", @"(\W)", "_"); var sharpLangTypeGlobal = LLVM.AddGlobal(module, sharpLangTypeType.ObjectTypeLLVM, mangledTypeName); LLVM.SetLinkage(sharpLangTypeGlobal, Linkage.PrivateLinkage); LLVM.SetInitializer(sharpLangTypeGlobal, LLVM.ConstNull(sharpLangTypeType.ObjectTypeLLVM)); if (isConcreteType) { // Build IMT var interfaceMethodTable = new LinkedList <InterfaceMethodTableEntry> [InterfaceMethodTableSize]; foreach (var @interface in @class.Interfaces) { foreach (var interfaceMethod in @interface.Type.TypeReferenceCecil.Resolve().Methods) { var resolvedInterfaceMethod = ResolveGenericMethod(@interface.Type.TypeReferenceCecil, interfaceMethod); // If method is not fully resolved (generic method in interface), ignore it // We are waiting for actual closed uses. if (ResolveGenericsVisitor.ContainsGenericParameters(resolvedInterfaceMethod)) { continue; } var resolvedFunction = CecilExtensions.TryMatchMethod(@class, resolvedInterfaceMethod); if (resolvedFunction == null && @class.Type.TypeReferenceCecil is ArrayType) { var arrayType = corlib.MainModule.GetType(typeof(Array).FullName); var matchingMethod = (MethodReference)arrayType.Methods.First(x => x.Name.StartsWith("InternalArray_") && x.Name.EndsWith(resolvedInterfaceMethod.Name)); if (matchingMethod != null) { if (matchingMethod.HasGenericParameters) { matchingMethod = matchingMethod.MakeGenericMethod(((ArrayType)@class.Type.TypeReferenceCecil).ElementType); } resolvedFunction = GetFunction(matchingMethod); // Manually emit Array functions locally (until proper mscorlib + generic instantiation exists). EmitFunction(resolvedFunction); LLVM.SetLinkage(resolvedFunction.GeneratedValue, Linkage.LinkOnceAnyLinkage); } } if (resolvedFunction == null) { throw new InvalidOperationException(string.Format("Could not find matching method for {0} in {1}", resolvedInterfaceMethod, @class)); } var isInterface = resolvedFunction.DeclaringType.TypeReferenceCecil.Resolve().IsInterface; if (!isInterface && resolvedFunction.MethodReference.Resolve().IsVirtual&& resolvedFunction.VirtualSlot != -1) { // We might have found a base virtual method matching this interface method. // Let's get the actual method override for this virtual slot. resolvedFunction = @class.VirtualTable[resolvedFunction.VirtualSlot]; } // If method is not found, it could be due to covariance/contravariance if (resolvedFunction == null) { throw new InvalidOperationException("Interface method not found"); } var methodId = GetMethodId(resolvedInterfaceMethod); var imtSlotIndex = (int)(methodId % InterfaceMethodTableSize); var imtSlot = interfaceMethodTable[imtSlotIndex]; if (imtSlot == null) { interfaceMethodTable[imtSlotIndex] = imtSlot = new LinkedList <InterfaceMethodTableEntry>(); } imtSlot.AddLast(new InterfaceMethodTableEntry { Function = resolvedFunction, MethodId = GetFunction(resolvedInterfaceMethod).GeneratedValue, // Should be a fake global, that we use as IMT key }); } } var interfaceMethodTableConstant = LLVM.ConstArray(intPtrLLVM, interfaceMethodTable.Select(imtSlot => { if (imtSlot == null) { // No entries: null slot return(LLVM.ConstNull(intPtrLLVM)); } if (imtSlot.Count == 1) { // Single entry var imtEntry = imtSlot.First.Value; return(LLVM.ConstPointerCast(GetVirtualMethod(imtEntry.Function), intPtrLLVM)); } else { // Multiple entries, create IMT array with all entries // TODO: Support covariance/contravariance? var imtEntries = LLVM.ConstArray(imtEntryLLVM, imtSlot.Select(imtEntry => { return(LLVM.ConstNamedStruct(imtEntryLLVM, new[] { imtEntry.MethodId, // i8* functionId LLVM.ConstPointerCast(GetVirtualMethod(imtEntry.Function), intPtrLLVM), // i8* functionPtr })); }) .Concat(Enumerable.Repeat(LLVM.ConstNull(imtEntryLLVM), 1)).ToArray()); // Append { 0, 0 } terminator var imtEntryGlobal = LLVM.AddGlobal(module, LLVM.TypeOf(imtEntries), @class.Type.TypeReferenceCecil.MangledName() + ".imt"); LLVM.SetLinkage(imtEntryGlobal, Linkage.PrivateLinkage); LLVM.SetInitializer(imtEntryGlobal, imtEntries); // Add 1 to differentiate between single entry and IMT array return(LLVM.ConstIntToPtr( LLVM.ConstAdd( LLVM.ConstPtrToInt(imtEntryGlobal, nativeIntLLVM), LLVM.ConstInt(nativeIntLLVM, 1, false)), intPtrLLVM)); } }).ToArray()); // Build list of super types var superTypes = new List <Class>(@class.Depth); var currentClass = @class; while (currentClass != null) { superTypes.Add(currentClass); currentClass = currentClass.BaseType; } // Reverse so that the list start with most inherited object // (allows faster type checking since a given type will always be at a given index) superTypes.Reverse(); // Build super types // Helpful for fast is/as checks on class hierarchy var superTypeCount = LLVM.ConstInt(int32LLVM, (ulong)@class.Depth + 1, false); var interfacesCount = LLVM.ConstInt(int32LLVM, (ulong)@class.Interfaces.Count, false); // Super types global var superTypesConstantGlobal = LLVM.AddGlobal(module, LLVM.ArrayType(intPtrLLVM, (uint)superTypes.Count), @class.Type.TypeReferenceCecil.MangledName() + ".supertypes"); LLVM.SetLinkage(superTypesConstantGlobal, Linkage.PrivateLinkage); var superTypesGlobal = LLVM.ConstInBoundsGEP(superTypesConstantGlobal, new[] { zero, zero }); // Interface map global var interfacesConstantGlobal = LLVM.AddGlobal(module, LLVM.ArrayType(intPtrLLVM, (uint)@class.Interfaces.Count), @class.Type.TypeReferenceCecil.MangledName() + ".interfaces"); LLVM.SetLinkage(interfacesConstantGlobal, Linkage.PrivateLinkage); var interfacesGlobal = LLVM.ConstInBoundsGEP(interfacesConstantGlobal, new[] { zero, zero }); // Build VTable var vtableConstant = LLVM.ConstNamedStruct(@class.VTableTypeLLVM, @class.VirtualTable.Select(x => GetVirtualMethod(x)).ToArray()); var staticFieldsInitializer = LLVM.ConstNamedStruct(runtimeTypeInfoTypeElements[(int)RuntimeTypeInfoFields.StaticFields], @class.StaticFields.Select(field => { var fieldType = field.Value.Type; if ((field.Key.Attributes & FieldAttributes.HasFieldRVA) != 0) { var initialValue = field.Key.InitialValue; // Seems like if type size is 8, it uses int64 as backing type // Maybe at some point it might be better to encode static fields in a big byte array and use casts instead? if (LLVM.GetTypeKind(fieldType.DefaultTypeLLVM) == TypeKind.IntegerTypeKind) { unsafe { fixed(byte *initalValueStart = initialValue) { if (LLVM.GetIntTypeWidth(fieldType.DefaultTypeLLVM) == 64) { return(LLVM.ConstInt(fieldType.DefaultTypeLLVM, *(ulong *)initalValueStart, false)); } if (LLVM.GetIntTypeWidth(fieldType.DefaultTypeLLVM) == 32) { return(LLVM.ConstInt(fieldType.DefaultTypeLLVM, *(uint *)initalValueStart, false)); } } } } // Otherwise, for now we assume that if there was a RVA, it was a type with custom layout (default type is i8[]), // as currently generated by compiler in <PrivateImplementationDetails> class. if (LLVM.GetTypeKind(fieldType.DefaultTypeLLVM) != TypeKind.ArrayTypeKind) { throw new NotSupportedException(); } var arrayElementType = LLVM.Int8TypeInContext(context); return(LLVM.ConstArray(arrayElementType, initialValue.Select(x => LLVM.ConstInt(arrayElementType, x, false)).ToArray())); } return(LLVM.ConstNull(fieldType.DefaultTypeLLVM)); }).ToArray()); runtimeTypeFields.AddRange(new[] { superTypeCount, interfacesCount, superTypesGlobal, interfacesGlobal, LLVM.ConstInt(LLVM.Int8TypeInContext(context), 0, false), // Class initialized? LLVM.ConstIntCast(LLVM.SizeOf(@class.Type.ObjectTypeLLVM), int32LLVM, false), elementTypeSize, interfaceMethodTableConstant, LLVM.ConstInt(int32LLVM, (ulong)@class.VirtualTable.Count, false), vtableConstant, staticFieldsInitializer, }); var runtimeTypeInfoConstant = LLVM.ConstNamedStruct(runtimeTypeInfoType, runtimeTypeFields.ToArray()); LLVM.SetInitializer(runtimeTypeInfoGlobal, runtimeTypeInfoConstant); // Build super type list (after RTTI since we need pointer to RTTI) var superTypesConstant = LLVM.ConstArray(intPtrLLVM, superTypes.Select(superType => LLVM.ConstPointerCast(superType.GeneratedEETypeTokenLLVM, intPtrLLVM)) .ToArray()); LLVM.SetInitializer(superTypesConstantGlobal, superTypesConstant); // Build interface map var interfacesConstant = LLVM.ConstArray(intPtrLLVM, @class.Interfaces.Select( @interface => LLVM.ConstPointerCast(@interface.GeneratedEETypeTokenLLVM, intPtrLLVM)).ToArray()); LLVM.SetInitializer(interfacesConstantGlobal, interfacesConstant); } else { // Non-concrete type, create type with current fields var runtimeTypeInfoConstant = LLVM.ConstNamedStruct(runtimeTypeInfoType, runtimeTypeFields.ToArray()); LLVM.SetInitializer(runtimeTypeInfoGlobal, runtimeTypeInfoConstant); } // Mark RTTI as external LLVM.SetLinkage(runtimeTypeInfoGlobal, Linkage.ExternalLinkage); }
private void BuildRuntimeType(Class @class) { if (@class.IsEmitted) { return; } @class.IsEmitted = true; // Build IMT var interfaceMethodTable = new LinkedList <InterfaceMethodTableEntry> [InterfaceMethodTableSize]; foreach (var @interface in @class.Interfaces) { foreach (var interfaceMethod in @interface.Type.TypeReference.Resolve().Methods) { var resolvedInterfaceMethod = ResolveGenericMethod(@interface.Type.TypeReference, interfaceMethod); // If method is not fully resolved (generic method in interface), ignore it // We are waiting for actual closed uses. if (ResolveGenericsVisitor.ContainsGenericParameters(resolvedInterfaceMethod)) { continue; } var resolvedFunction = CecilExtensions.TryMatchMethod(@class, resolvedInterfaceMethod); if (resolvedFunction == null && @class.Type.TypeReference is ArrayType) { var arrayType = corlib.MainModule.GetType(typeof(Array).FullName); var matchingMethod = (MethodReference)arrayType.Methods.First(x => x.Name.StartsWith("InternalArray_") && x.Name.EndsWith(resolvedInterfaceMethod.Name)); if (matchingMethod != null) { if (matchingMethod.HasGenericParameters) { matchingMethod = matchingMethod.MakeGenericMethod(((ArrayType)@class.Type.TypeReference).ElementType); } resolvedFunction = GetFunction(matchingMethod); // Manually emit Array functions locally (until proper mscorlib + generic instantiation exists). EmitFunction(resolvedFunction); } } if (resolvedFunction == null) { throw new InvalidOperationException(string.Format("Could not find matching method for {0} in {1}", resolvedInterfaceMethod, @class)); } var isInterface = resolvedFunction.DeclaringType.TypeReference.Resolve().IsInterface; if (!isInterface && resolvedFunction.MethodReference.Resolve().IsVirtual&& resolvedFunction.VirtualSlot != -1) { // We might have found a base virtual method matching this interface method. // Let's get the actual method override for this virtual slot. resolvedFunction = @class.VirtualTable[resolvedFunction.VirtualSlot]; } // If method is not found, it could be due to covariance/contravariance if (resolvedFunction == null) { throw new InvalidOperationException("Interface method not found"); } var methodId = GetMethodId(resolvedInterfaceMethod); var imtSlotIndex = (int)(methodId % interfaceMethodTable.Length); var imtSlot = interfaceMethodTable[imtSlotIndex]; if (imtSlot == null) { interfaceMethodTable[imtSlotIndex] = imtSlot = new LinkedList <InterfaceMethodTableEntry>(); } imtSlot.AddLast(new InterfaceMethodTableEntry { Function = resolvedFunction, MethodId = methodId, SlotIndex = imtSlotIndex }); } } var interfaceMethodTableConstant = LLVM.ConstArray(intPtrType, interfaceMethodTable.Select(imtSlot => { if (imtSlot == null) { // No entries: null slot return(LLVM.ConstNull(intPtrType)); } if (imtSlot.Count == 1) { // Single entry var imtEntry = imtSlot.First.Value; return(LLVM.ConstPointerCast(imtEntry.Function.GeneratedValue, intPtrType)); } else { // Multiple entries, create IMT array with all entries // TODO: Support covariance/contravariance? var imtEntries = LLVM.ConstArray(imtEntryType, imtSlot.Select(imtEntry => { return(LLVM.ConstNamedStruct(imtEntryType, new[] { LLVM.ConstInt(int32Type, (ulong)imtEntry.MethodId, false), // i32 functionId LLVM.ConstPointerCast(imtEntry.Function.GeneratedValue, intPtrType), // i8* functionPtr })); }) .Concat(Enumerable.Repeat(LLVM.ConstNull(imtEntryType), 1)).ToArray()); // Append { 0, 0 } terminator var imtEntryGlobal = LLVM.AddGlobal(module, LLVM.TypeOf(imtEntries), @class.Type.TypeReference.MangledName() + ".imt"); LLVM.SetInitializer(imtEntryGlobal, imtEntries); // Add 1 to differentiate between single entry and IMT array return(LLVM.ConstIntToPtr( LLVM.ConstAdd( LLVM.ConstPtrToInt(imtEntryGlobal, nativeIntType), LLVM.ConstInt(nativeIntType, 1, false)), intPtrType)); } }).ToArray()); // Build list of super types var superTypes = new List <Class>(@class.Depth); var currentClass = @class; while (currentClass != null) { superTypes.Add(currentClass); currentClass = currentClass.BaseType; } // Reverse so that the list start with most inherited object // (allows faster type checking since a given type will always be at a given index) superTypes.Reverse(); // Build super types // Helpful for fast is/as checks on class hierarchy var superTypeCount = LLVM.ConstInt(int32Type, (ulong)@class.Depth + 1, false); var interfacesCount = LLVM.ConstInt(int32Type, (ulong)@class.Interfaces.Count, false); var zero = LLVM.ConstInt(int32Type, 0, false); // Super types global var superTypesConstantGlobal = LLVM.AddGlobal(module, LLVM.ArrayType(intPtrType, (uint)superTypes.Count), @class.Type.TypeReference.MangledName() + ".supertypes"); var superTypesGlobal = LLVM.ConstInBoundsGEP(superTypesConstantGlobal, new[] { zero, zero }); // Interface map global var interfacesConstantGlobal = LLVM.AddGlobal(module, LLVM.ArrayType(intPtrType, (uint)@class.Interfaces.Count), @class.Type.TypeReference.MangledName() + ".interfaces"); var interfacesGlobal = LLVM.ConstInBoundsGEP(interfacesConstantGlobal, new[] { zero, zero }); // Build VTable var vtableConstant = LLVM.ConstStructInContext(context, @class.VirtualTable.Select(x => x.GeneratedValue).ToArray(), false); // Build RTTI var runtimeTypeInfoGlobal = @class.GeneratedRuntimeTypeInfoGlobal; var runtimeTypeInfoType = LLVM.GetElementType(LLVM.TypeOf(runtimeTypeInfoGlobal)); var runtimeTypeInfoTypeElements = new TypeRef[LLVM.CountStructElementTypes(runtimeTypeInfoType)]; LLVM.GetStructElementTypes(runtimeTypeInfoType, runtimeTypeInfoTypeElements); var runtimeTypeInfoConstant = LLVM.ConstNamedStruct(runtimeTypeInfoType, new[] { @class.BaseType != null ? @class.BaseType.GeneratedRuntimeTypeInfoGlobal : LLVM.ConstPointerNull(intPtrType), superTypeCount, interfacesCount, superTypesGlobal, interfacesGlobal, LLVM.ConstInt(LLVM.Int1TypeInContext(context), 0, false), // Class initialized? interfaceMethodTableConstant, vtableConstant, LLVM.ConstNull(runtimeTypeInfoTypeElements[(int)RuntimeTypeInfoFields.StaticFields]), }); LLVM.SetInitializer(runtimeTypeInfoGlobal, runtimeTypeInfoConstant); // Build super type list (after RTTI since we need pointer to RTTI) var superTypesConstant = LLVM.ConstArray(intPtrType, superTypes.Select(superType => LLVM.ConstPointerCast(superType.GeneratedRuntimeTypeInfoGlobal, intPtrType)) .ToArray()); LLVM.SetInitializer(superTypesConstantGlobal, superTypesConstant); // Build interface map var interfacesConstant = LLVM.ConstArray(intPtrType, @class.Interfaces.Select( @interface => LLVM.ConstPointerCast(@interface.GeneratedRuntimeTypeInfoGlobal, intPtrType)).ToArray()); LLVM.SetInitializer(interfacesConstantGlobal, interfacesConstant); // Mark RTTI as external LLVM.SetLinkage(runtimeTypeInfoGlobal, Linkage.ExternalLinkage); }