mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-08 19:48:20 +02:00
Refactored always-include
This commit is contained in:
parent
c0ae4bb8f7
commit
d11348a4e4
7 changed files with 276 additions and 174 deletions
|
@ -4260,18 +4260,7 @@ void BfCompiler::ProcessAutocompleteTempType()
|
||||||
|
|
||||||
if (tempTypeDef->mTypeDeclaration->mAttributes != NULL)
|
if (tempTypeDef->mTypeDeclaration->mAttributes != NULL)
|
||||||
{
|
{
|
||||||
BfAttributeTargets attrTarget;
|
auto customAttrs = module->GetCustomAttributes(tempTypeDef);
|
||||||
if (tempTypeDef->mIsDelegate)
|
|
||||||
attrTarget = BfAttributeTargets_Delegate;
|
|
||||||
else if (typeInst->IsEnum())
|
|
||||||
attrTarget = BfAttributeTargets_Enum;
|
|
||||||
else if (typeInst->IsInterface())
|
|
||||||
attrTarget = BfAttributeTargets_Interface;
|
|
||||||
else if (typeInst->IsStruct())
|
|
||||||
attrTarget = BfAttributeTargets_Struct;
|
|
||||||
else
|
|
||||||
attrTarget = BfAttributeTargets_Class;
|
|
||||||
auto customAttrs = module->GetCustomAttributes(tempTypeDef->mTypeDeclaration->mAttributes, attrTarget);
|
|
||||||
delete customAttrs;
|
delete customAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6293,6 +6282,8 @@ void BfCompiler::CompileReified()
|
||||||
{
|
{
|
||||||
BP_ZONE("Compile_ResolveTypeDefs");
|
BP_ZONE("Compile_ResolveTypeDefs");
|
||||||
|
|
||||||
|
Array<BfTypeDef*> deferTypeDefs;
|
||||||
|
|
||||||
for (auto typeDef : mSystem->mTypeDefs)
|
for (auto typeDef : mSystem->mTypeDefs)
|
||||||
{
|
{
|
||||||
mSystem->CheckLockYield();
|
mSystem->CheckLockYield();
|
||||||
|
@ -6328,11 +6319,46 @@ void BfCompiler::CompileReified()
|
||||||
|
|
||||||
//TODO: Just because the type is required doesn't mean we want to reify it. Why did we have that check?
|
//TODO: Just because the type is required doesn't mean we want to reify it. Why did we have that check?
|
||||||
if ((mOptions.mCompileOnDemandKind != BfCompileOnDemandKind_AlwaysInclude) && (!isAlwaysInclude))
|
if ((mOptions.mCompileOnDemandKind != BfCompileOnDemandKind_AlwaysInclude) && (!isAlwaysInclude))
|
||||||
|
{
|
||||||
|
if (typeDef->mGenericParamDefs.IsEmpty())
|
||||||
|
deferTypeDefs.Add(typeDef);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
scratchModule->ResolveTypeDef(typeDef, BfPopulateType_Full);
|
scratchModule->ResolveTypeDef(typeDef, BfPopulateType_Full);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve remaining typedefs as unreified so we can check their attributes
|
||||||
|
for (auto typeDef : deferTypeDefs)
|
||||||
|
{
|
||||||
|
auto type = mContext->mUnreifiedModule->ResolveTypeDef(typeDef, BfPopulateType_Identity);
|
||||||
|
if (type == NULL)
|
||||||
|
continue;
|
||||||
|
auto typeInst = type->ToTypeInstance();
|
||||||
|
if (typeInst == NULL)
|
||||||
|
continue;
|
||||||
|
if (typeInst->mIsReified)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mContext->mUnreifiedModule->PopulateType(typeInst, BfPopulateType_Interfaces);
|
||||||
|
if (typeInst->mCustomAttributes == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool alwaysInclude = false;
|
||||||
|
for (auto& customAttribute : typeInst->mCustomAttributes->mAttributes)
|
||||||
|
{
|
||||||
|
if (customAttribute.mType->mAttributeData != NULL)
|
||||||
|
{
|
||||||
|
if (customAttribute.mType->mAttributeData->mAlwaysIncludeUser != 0)
|
||||||
|
alwaysInclude = true;
|
||||||
|
if ((customAttribute.mType->mAttributeData->mFlags & BfAttributeFlag_AlwaysIncludeTarget) != 0)
|
||||||
|
alwaysInclude = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alwaysInclude)
|
||||||
|
mContext->mScratchModule->PopulateType(typeInst, BfPopulateType_Full);
|
||||||
|
}
|
||||||
|
|
||||||
if (mOptions.mCompileOnDemandKind != BfCompileOnDemandKind_AlwaysInclude)
|
if (mOptions.mCompileOnDemandKind != BfCompileOnDemandKind_AlwaysInclude)
|
||||||
{
|
{
|
||||||
for (auto project : mSystem->mProjects)
|
for (auto project : mSystem->mProjects)
|
||||||
|
@ -6850,26 +6876,16 @@ bool BfCompiler::DoCompile(const StringImpl& outputDirectory)
|
||||||
{
|
{
|
||||||
SizedArray<BfModule*, 32> requiredModules;
|
SizedArray<BfModule*, 32> requiredModules;
|
||||||
|
|
||||||
for (auto typeDef : mSystem->mTypeDefs)
|
for (auto type : mContext->mResolvedTypes)
|
||||||
{
|
{
|
||||||
if (typeDef->mIsPartial)
|
auto typeInst = type->ToTypeInstance();
|
||||||
|
if (typeInst == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
if (typeInst->mAlwaysIncludeFlags == BfAlwaysIncludeFlag_None)
|
||||||
bool isAlwaysInclude = (typeDef->mIsAlwaysInclude) || (typeDef->mProject->mAlwaysIncludeAll);
|
continue;
|
||||||
auto typeOptions = mContext->mScratchModule->GetTypeOptions(typeDef);
|
auto requiredModule = typeInst->GetModule();
|
||||||
if (typeOptions != NULL)
|
if (requiredModule != NULL)
|
||||||
isAlwaysInclude = typeOptions->Apply(isAlwaysInclude, BfOptionFlags_ReflectAlwaysIncludeType);
|
requiredModules.push_back(requiredModule);
|
||||||
|
|
||||||
if (isAlwaysInclude)
|
|
||||||
{
|
|
||||||
auto requiredType = mContext->mScratchModule->ResolveTypeDef(typeDef);
|
|
||||||
if (requiredType != NULL)
|
|
||||||
{
|
|
||||||
auto requiredModule = requiredType->GetModule();
|
|
||||||
if (requiredModule != NULL)
|
|
||||||
requiredModules.push_back(requiredModule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mContext->mReferencedIFaceSlots.Clear();
|
mContext->mReferencedIFaceSlots.Clear();
|
||||||
|
|
|
@ -1688,6 +1688,19 @@ void BfIRBuilder::ClearConstData()
|
||||||
BF_ASSERT(mStream.GetSize() == 0);
|
BF_ASSERT(mStream.GetSize() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BfIRBuilder::ClearNonConstData()
|
||||||
|
{
|
||||||
|
mMethodTypeMap.Clear();
|
||||||
|
mFunctionMap.Clear();
|
||||||
|
mTypeMap.Clear();
|
||||||
|
mConstMemMap.Clear();
|
||||||
|
mDITemporaryTypes.Clear();
|
||||||
|
mSavedDebugLocs.Clear();
|
||||||
|
mDeferredDbgTypeDefs.Clear();
|
||||||
|
mActiveFunction = BfIRFunction();
|
||||||
|
mInsertBlock = BfIRValue();
|
||||||
|
}
|
||||||
|
|
||||||
void BfIRBuilder::Start(const StringImpl& moduleName, int ptrSize, bool isOptimized)
|
void BfIRBuilder::Start(const StringImpl& moduleName, int ptrSize, bool isOptimized)
|
||||||
{
|
{
|
||||||
mHasStarted = true;
|
mHasStarted = true;
|
||||||
|
|
|
@ -38,6 +38,7 @@ class BfIRCodeGen;
|
||||||
class BeIRCodeGen;
|
class BeIRCodeGen;
|
||||||
class BfMethodInstance;
|
class BfMethodInstance;
|
||||||
class BfFieldInstance;
|
class BfFieldInstance;
|
||||||
|
class BfMethodRef;
|
||||||
|
|
||||||
class BfFileInstance;
|
class BfFileInstance;
|
||||||
class BfParser;
|
class BfParser;
|
||||||
|
@ -974,7 +975,7 @@ public:
|
||||||
bool mHasDebugLoc;
|
bool mHasDebugLoc;
|
||||||
bool mHasDebugInfo;
|
bool mHasDebugInfo;
|
||||||
bool mHasDebugLineInfo;
|
bool mHasDebugLineInfo;
|
||||||
Dictionary<BfMethodInstance*, BfIRFunctionType> mMethodTypeMap;
|
Dictionary<BfMethodRef, BfIRFunctionType> mMethodTypeMap;
|
||||||
Dictionary<String, BfIRFunction> mFunctionMap;
|
Dictionary<String, BfIRFunction> mFunctionMap;
|
||||||
Dictionary<BfType*, BfIRPopulateType> mTypeMap;
|
Dictionary<BfType*, BfIRPopulateType> mTypeMap;
|
||||||
Dictionary<int, BfIRValue> mConstMemMap;
|
Dictionary<int, BfIRValue> mConstMemMap;
|
||||||
|
@ -1134,6 +1135,7 @@ public:
|
||||||
|
|
||||||
void GetBufferData(Array<uint8>& outBuffer);
|
void GetBufferData(Array<uint8>& outBuffer);
|
||||||
void ClearConstData();
|
void ClearConstData();
|
||||||
|
void ClearNonConstData();
|
||||||
|
|
||||||
void Start(const StringImpl& moduleName, int ptrSize, bool isOptimized);
|
void Start(const StringImpl& moduleName, int ptrSize, bool isOptimized);
|
||||||
void SetBackend(bool isBeefBackend);
|
void SetBackend(bool isBeefBackend);
|
||||||
|
|
|
@ -1103,6 +1103,67 @@ void BfModule::PrepareForIRWriting(BfTypeInstance* typeInst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BfModule::SetupIRBuilder(bool dbgVerifyCodeGen)
|
||||||
|
{
|
||||||
|
if (mIsScratchModule)
|
||||||
|
{
|
||||||
|
mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
BF_ASSERT(!dbgVerifyCodeGen);
|
||||||
|
}
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (mCompiler->mIsResolveOnly)
|
||||||
|
{
|
||||||
|
// For "deep" verification testing
|
||||||
|
/*if (!mIsSpecialModule)
|
||||||
|
dbgVerifyCodeGen = true;*/
|
||||||
|
|
||||||
|
// We only want to turn this off on smaller builds for testing
|
||||||
|
mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
if (dbgVerifyCodeGen)
|
||||||
|
mBfIRBuilder->mIgnoreWrites = false;
|
||||||
|
if (!mBfIRBuilder->mIgnoreWrites)
|
||||||
|
{
|
||||||
|
// The only purpose of not ignoring writes is so we can verify the codegen one instruction at a time
|
||||||
|
mBfIRBuilder->mDbgVerifyCodeGen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!mIsReified)
|
||||||
|
{
|
||||||
|
mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We almost always want this to be 'false' unless we need need to be able to inspect the generated LLVM
|
||||||
|
// code as we walk the AST
|
||||||
|
//mBfIRBuilder->mDbgVerifyCodeGen = true;
|
||||||
|
if (
|
||||||
|
(mModuleName == "-")
|
||||||
|
//|| (mModuleName == "BeefTest2_ClearColorValue")
|
||||||
|
//|| (mModuleName == "Tests_FuncRefs")
|
||||||
|
)
|
||||||
|
mBfIRBuilder->mDbgVerifyCodeGen = true;
|
||||||
|
|
||||||
|
// Just for testing!
|
||||||
|
//mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (mCompiler->mIsResolveOnly)
|
||||||
|
{
|
||||||
|
// Always ignore writes in resolveOnly for release builds
|
||||||
|
mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Just for memory testing! This breaks everything.
|
||||||
|
//mBfIRBuilder->mIgnoreWrites = true;
|
||||||
|
|
||||||
|
// For "deep" verification testing
|
||||||
|
//mBfIRBuilder->mDbgVerifyCodeGen = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
mWantsIRIgnoreWrites = mBfIRBuilder->mIgnoreWrites;
|
||||||
|
}
|
||||||
|
|
||||||
void BfModule::EnsureIRBuilder(bool dbgVerifyCodeGen)
|
void BfModule::EnsureIRBuilder(bool dbgVerifyCodeGen)
|
||||||
{
|
{
|
||||||
BF_ASSERT(!mIsDeleting);
|
BF_ASSERT(!mIsDeleting);
|
||||||
|
@ -1121,64 +1182,7 @@ void BfModule::EnsureIRBuilder(bool dbgVerifyCodeGen)
|
||||||
|
|
||||||
mBfIRBuilder = new BfIRBuilder(this);
|
mBfIRBuilder = new BfIRBuilder(this);
|
||||||
BfLogSysM("Created mBfIRBuilder %p in %p %s Reified: %d\n", mBfIRBuilder, this, mModuleName.c_str(), mIsReified);
|
BfLogSysM("Created mBfIRBuilder %p in %p %s Reified: %d\n", mBfIRBuilder, this, mModuleName.c_str(), mIsReified);
|
||||||
|
SetupIRBuilder(dbgVerifyCodeGen);
|
||||||
if (mIsScratchModule)
|
|
||||||
{
|
|
||||||
mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
BF_ASSERT(!dbgVerifyCodeGen);
|
|
||||||
}
|
|
||||||
#ifdef _DEBUG
|
|
||||||
if (mCompiler->mIsResolveOnly)
|
|
||||||
{
|
|
||||||
// For "deep" verification testing
|
|
||||||
/*if (!mIsSpecialModule)
|
|
||||||
dbgVerifyCodeGen = true;*/
|
|
||||||
|
|
||||||
// We only want to turn this off on smaller builds for testing
|
|
||||||
mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
if (dbgVerifyCodeGen)
|
|
||||||
mBfIRBuilder->mIgnoreWrites = false;
|
|
||||||
if (!mBfIRBuilder->mIgnoreWrites)
|
|
||||||
{
|
|
||||||
// The only purpose of not ignoring writes is so we can verify the codegen one instruction at a time
|
|
||||||
mBfIRBuilder->mDbgVerifyCodeGen = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!mIsReified)
|
|
||||||
{
|
|
||||||
mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We almost always want this to be 'false' unless we need need to be able to inspect the generated LLVM
|
|
||||||
// code as we walk the AST
|
|
||||||
//mBfIRBuilder->mDbgVerifyCodeGen = true;
|
|
||||||
if (
|
|
||||||
(mModuleName == "-")
|
|
||||||
//|| (mModuleName == "BeefTest2_ClearColorValue")
|
|
||||||
//|| (mModuleName == "Tests_FuncRefs")
|
|
||||||
)
|
|
||||||
mBfIRBuilder->mDbgVerifyCodeGen = true;
|
|
||||||
|
|
||||||
// Just for testing!
|
|
||||||
//mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (mCompiler->mIsResolveOnly)
|
|
||||||
{
|
|
||||||
// Always ignore writes in resolveOnly for release builds
|
|
||||||
mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Just for memory testing! This breaks everything.
|
|
||||||
//mBfIRBuilder->mIgnoreWrites = true;
|
|
||||||
|
|
||||||
// For "deep" verification testing
|
|
||||||
//mBfIRBuilder->mDbgVerifyCodeGen = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
mWantsIRIgnoreWrites = mBfIRBuilder->mIgnoreWrites;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11230,7 +11234,7 @@ void BfModule::GetCustomAttributes(BfCustomAttributes* customAttributes, BfAttri
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
if (!attrTypeInst->mAttributeData->mAllowMultiple)
|
if ((attrTypeInst->mAttributeData->mFlags & BfAttributeFlag_DisallowAllowMultiple) != 0)
|
||||||
{
|
{
|
||||||
for (auto& prevCustomAttribute : customAttributes->mAttributes)
|
for (auto& prevCustomAttribute : customAttributes->mAttributes)
|
||||||
{
|
{
|
||||||
|
@ -11258,6 +11262,22 @@ BfCustomAttributes* BfModule::GetCustomAttributes(BfAttributeDirective* attribut
|
||||||
return customAttributes;
|
return customAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BfCustomAttributes* BfModule::GetCustomAttributes(BfTypeDef* typeDef)
|
||||||
|
{
|
||||||
|
BfAttributeTargets attrTarget;
|
||||||
|
if (typeDef->mIsDelegate)
|
||||||
|
attrTarget = BfAttributeTargets_Delegate;
|
||||||
|
else if (typeDef->mTypeCode == BfTypeCode_Enum)
|
||||||
|
attrTarget = BfAttributeTargets_Enum;
|
||||||
|
else if (typeDef->mTypeCode == BfTypeCode_Interface)
|
||||||
|
attrTarget = BfAttributeTargets_Interface;
|
||||||
|
else if (typeDef->mTypeCode == BfTypeCode_Struct)
|
||||||
|
attrTarget = BfAttributeTargets_Struct;
|
||||||
|
else
|
||||||
|
attrTarget = BfAttributeTargets_Class;
|
||||||
|
return GetCustomAttributes(typeDef->mTypeDeclaration->mAttributes, attrTarget);
|
||||||
|
}
|
||||||
|
|
||||||
void BfModule::FinishAttributeState(BfAttributeState* attributeState)
|
void BfModule::FinishAttributeState(BfAttributeState* attributeState)
|
||||||
{
|
{
|
||||||
if ((!attributeState->mUsed) && (attributeState->mSrc != NULL))
|
if ((!attributeState->mUsed) && (attributeState->mSrc != NULL))
|
||||||
|
@ -11290,6 +11310,8 @@ void BfModule::ProcessTypeInstCustomAttributes(bool& isPacked, bool& isUnion, bo
|
||||||
}
|
}
|
||||||
else if (typeName == "System.AlwaysIncludeAttribute")
|
else if (typeName == "System.AlwaysIncludeAttribute")
|
||||||
{
|
{
|
||||||
|
mCurTypeInstance->mAlwaysIncludeFlags = (BfAlwaysIncludeFlags)(mCurTypeInstance->mAlwaysIncludeFlags | BfAlwaysIncludeFlag_Type);
|
||||||
|
|
||||||
for (auto setProp : customAttribute.mSetProperties)
|
for (auto setProp : customAttribute.mSetProperties)
|
||||||
{
|
{
|
||||||
BfPropertyDef* propertyDef = setProp.mPropertyRef;
|
BfPropertyDef* propertyDef = setProp.mPropertyRef;
|
||||||
|
@ -11368,6 +11390,15 @@ void BfModule::ProcessCustomAttributeData()
|
||||||
attributeData->mAttributeTargets = (BfAttributeTargets)constant->mInt32;
|
attributeData->mAttributeTargets = (BfAttributeTargets)constant->mInt32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (customAttribute.mCtorArgs.size() == 2)
|
||||||
|
{
|
||||||
|
auto constant = mCurTypeInstance->mConstHolder->GetConstant(customAttribute.mCtorArgs[0]);
|
||||||
|
if ((constant != NULL) && (mBfIRBuilder->IsInt(constant->mTypeCode)))
|
||||||
|
{
|
||||||
|
attributeData->mFlags = (BfAttributeFlags)constant->mInt32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& setProp : customAttribute.mSetProperties)
|
for (auto& setProp : customAttribute.mSetProperties)
|
||||||
{
|
{
|
||||||
BfPropertyDef* propDef = setProp.mPropertyRef;
|
BfPropertyDef* propDef = setProp.mPropertyRef;
|
||||||
|
@ -11375,14 +11406,18 @@ void BfModule::ProcessCustomAttributeData()
|
||||||
if (propDef->mName == "AllowMultiple")
|
if (propDef->mName == "AllowMultiple")
|
||||||
{
|
{
|
||||||
auto constant = mCurTypeInstance->mConstHolder->GetConstant(setProp.mParam.mValue);
|
auto constant = mCurTypeInstance->mConstHolder->GetConstant(setProp.mParam.mValue);
|
||||||
if (constant != NULL)
|
if ((constant != NULL) && (constant->mBool))
|
||||||
attributeData->mAllowMultiple = constant->mBool;
|
attributeData->mFlags = (BfAttributeFlags)(attributeData->mFlags & ~BfAttributeFlag_DisallowAllowMultiple);
|
||||||
|
else
|
||||||
|
attributeData->mFlags = (BfAttributeFlags)(attributeData->mFlags | BfAttributeFlag_DisallowAllowMultiple);
|
||||||
}
|
}
|
||||||
else if (propDef->mName == "Inherited")
|
else if (propDef->mName == "Inherited")
|
||||||
{
|
{
|
||||||
auto constant = mCurTypeInstance->mConstHolder->GetConstant(setProp.mParam.mValue);
|
auto constant = mCurTypeInstance->mConstHolder->GetConstant(setProp.mParam.mValue);
|
||||||
if (constant != NULL)
|
if ((constant != NULL) && (constant->mBool))
|
||||||
attributeData->mInherited = constant->mBool;
|
attributeData->mFlags = (BfAttributeFlags)(attributeData->mFlags & ~BfAttributeFlag_NotInherited);
|
||||||
|
else
|
||||||
|
attributeData->mFlags = (BfAttributeFlags)(attributeData->mFlags | BfAttributeFlag_NotInherited);
|
||||||
}
|
}
|
||||||
else if (propDef->mName == "ValidOn")
|
else if (propDef->mName == "ValidOn")
|
||||||
{
|
{
|
||||||
|
@ -11406,8 +11441,7 @@ void BfModule::ProcessCustomAttributeData()
|
||||||
if ((!hasCustomAttribute) && (mCurTypeInstance->mBaseType->mAttributeData != NULL))
|
if ((!hasCustomAttribute) && (mCurTypeInstance->mBaseType->mAttributeData != NULL))
|
||||||
{
|
{
|
||||||
attributeData->mAttributeTargets = mCurTypeInstance->mBaseType->mAttributeData->mAttributeTargets;
|
attributeData->mAttributeTargets = mCurTypeInstance->mBaseType->mAttributeData->mAttributeTargets;
|
||||||
attributeData->mInherited = mCurTypeInstance->mBaseType->mAttributeData->mInherited;
|
attributeData->mFlags = mCurTypeInstance->mBaseType->mAttributeData->mFlags;
|
||||||
attributeData->mAllowMultiple = mCurTypeInstance->mBaseType->mAttributeData->mAllowMultiple;
|
|
||||||
attributeData->mAlwaysIncludeUser = mCurTypeInstance->mBaseType->mAttributeData->mAlwaysIncludeUser;
|
attributeData->mAlwaysIncludeUser = mCurTypeInstance->mBaseType->mAttributeData->mAlwaysIncludeUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21027,30 +21061,6 @@ void BfModule::SetupIRFunction(BfMethodInstance* methodInstance, StringImpl& man
|
||||||
|
|
||||||
BfIRFunctionType funcType = mBfIRBuilder->MapMethod(methodInstance);
|
BfIRFunctionType funcType = mBfIRBuilder->MapMethod(methodInstance);
|
||||||
|
|
||||||
// Don't set these pointers during resolve pass because they may become invalid if it's just a temporary autocomplete method
|
|
||||||
if (mCompiler->mResolvePassData == NULL)
|
|
||||||
{
|
|
||||||
if ((methodDef->mMethodType == BfMethodType_Ctor) && (methodDef->mIsStatic))
|
|
||||||
{
|
|
||||||
typeInstance->mHasStaticInitMethod = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((methodDef->mMethodType == BfMethodType_Dtor) && (methodDef->mIsStatic))
|
|
||||||
{
|
|
||||||
typeInstance->mHasStaticDtorMethod = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((methodDef->mMethodType == BfMethodType_Normal) && (methodDef->mIsStatic) && (methodDef->mName == BF_METHODNAME_MARKMEMBERS_STATIC))
|
|
||||||
{
|
|
||||||
typeInstance->mHasStaticMarkMethod = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((methodDef->mMethodType == BfMethodType_Normal) && (methodDef->mIsStatic) && (methodDef->mName == BF_METHODNAME_FIND_TLS_MEMBERS))
|
|
||||||
{
|
|
||||||
typeInstance->mHasTLSFindMethod = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTemporaryFunc)
|
if (isTemporaryFunc)
|
||||||
{
|
{
|
||||||
BF_ASSERT(((mCompiler->mIsResolveOnly) && (mCompiler->mResolvePassData->mAutoComplete != NULL)) ||
|
BF_ASSERT(((mCompiler->mIsResolveOnly) && (mCompiler->mResolvePassData->mAutoComplete != NULL)) ||
|
||||||
|
@ -23634,6 +23644,8 @@ bool BfModule::Finish()
|
||||||
|
|
||||||
if (HasCompiledOutput())
|
if (HasCompiledOutput())
|
||||||
{
|
{
|
||||||
|
BF_ASSERT(!mBfIRBuilder->mIgnoreWrites);
|
||||||
|
|
||||||
DbgFinish();
|
DbgFinish();
|
||||||
|
|
||||||
if (mAwaitingInitFinish)
|
if (mAwaitingInitFinish)
|
||||||
|
|
|
@ -1557,6 +1557,7 @@ public:
|
||||||
void ValidateCustomAttributes(BfCustomAttributes* customAttributes, BfAttributeTargets attrTarget);
|
void ValidateCustomAttributes(BfCustomAttributes* customAttributes, BfAttributeTargets attrTarget);
|
||||||
void GetCustomAttributes(BfCustomAttributes* customAttributes, BfAttributeDirective* attributesDirective, BfAttributeTargets attrType, bool allowNonConstArgs = false, BfCaptureInfo* captureInfo = NULL);
|
void GetCustomAttributes(BfCustomAttributes* customAttributes, BfAttributeDirective* attributesDirective, BfAttributeTargets attrType, bool allowNonConstArgs = false, BfCaptureInfo* captureInfo = NULL);
|
||||||
BfCustomAttributes* GetCustomAttributes(BfAttributeDirective* attributesDirective, BfAttributeTargets attrType, bool allowNonConstArgs = false, BfCaptureInfo* captureInfo = NULL);
|
BfCustomAttributes* GetCustomAttributes(BfAttributeDirective* attributesDirective, BfAttributeTargets attrType, bool allowNonConstArgs = false, BfCaptureInfo* captureInfo = NULL);
|
||||||
|
BfCustomAttributes* GetCustomAttributes(BfTypeDef* typeDef);
|
||||||
void FinishAttributeState(BfAttributeState* attributeState);
|
void FinishAttributeState(BfAttributeState* attributeState);
|
||||||
void ProcessTypeInstCustomAttributes(bool& isPacked, bool& isUnion, bool& isCRepr, bool& isOrdered, int& alignOverride, BfType*& underlyingArrayType, int& underlyingArraySize);
|
void ProcessTypeInstCustomAttributes(bool& isPacked, bool& isUnion, bool& isCRepr, bool& isOrdered, int& alignOverride, BfType*& underlyingArrayType, int& underlyingArraySize);
|
||||||
void ProcessCustomAttributeData();
|
void ProcessCustomAttributeData();
|
||||||
|
@ -1946,6 +1947,7 @@ public:
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
void StartNewRevision(RebuildKind rebuildKind = RebuildKind_All, bool force = false);
|
void StartNewRevision(RebuildKind rebuildKind = RebuildKind_All, bool force = false);
|
||||||
void PrepareForIRWriting(BfTypeInstance* typeInst);
|
void PrepareForIRWriting(BfTypeInstance* typeInst);
|
||||||
|
void SetupIRBuilder(bool dbgVerifyCodeGen);
|
||||||
void EnsureIRBuilder(bool dbgVerifyCodeGen = false);
|
void EnsureIRBuilder(bool dbgVerifyCodeGen = false);
|
||||||
void DbgFinish();
|
void DbgFinish();
|
||||||
BfIRValue CreateForceLinkMarker(BfModule* module, String* outName);
|
BfIRValue CreateForceLinkMarker(BfModule* module, String* outName);
|
||||||
|
|
|
@ -1014,17 +1014,46 @@ void BfModule::PopulateType(BfType* resolvedTypeRef, BfPopulateType populateType
|
||||||
{
|
{
|
||||||
if ((typeModule != NULL) && (!typeModule->mIsReified) && (!typeModule->mReifyQueued))
|
if ((typeModule != NULL) && (!typeModule->mIsReified) && (!typeModule->mReifyQueued))
|
||||||
{
|
{
|
||||||
BF_ASSERT((mCompiler->mCompileState != BfCompiler::CompileState_Unreified) && (mCompiler->mCompileState != BfCompiler::CompileState_VData));
|
bool canFastReify = false;
|
||||||
|
if (typeModule->mAwaitingInitFinish)
|
||||||
|
{
|
||||||
|
canFastReify = true;
|
||||||
|
for (auto ownedTypes : typeModule->mOwnedTypeInstances)
|
||||||
|
if (ownedTypes->mDefineState > BfTypeDefineState_HasInterfaces)
|
||||||
|
canFastReify = false;
|
||||||
|
}
|
||||||
|
|
||||||
BfLogSysM("Queued reification of type %p in module %p in PopulateType\n", resolvedTypeRef, typeModule);
|
if (canFastReify)
|
||||||
|
{
|
||||||
|
BfLogSysM("Setting reified type %p in module %p in PopulateType on module awaiting finish\n", resolvedTypeRef, typeModule);
|
||||||
|
typeModule->mIsReified = true;
|
||||||
|
typeModule->mWantsIRIgnoreWrites = false;
|
||||||
|
for (auto ownedTypes : typeModule->mOwnedTypeInstances)
|
||||||
|
ownedTypes->mIsReified = true;
|
||||||
|
mCompiler->mStats.mReifiedModuleCount++;
|
||||||
|
if (typeModule->mBfIRBuilder != NULL)
|
||||||
|
{
|
||||||
|
typeModule->mBfIRBuilder->ClearNonConstData();
|
||||||
|
typeModule->mBfIRBuilder->mIgnoreWrites = false;
|
||||||
|
typeModule->SetupIRBuilder(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
typeModule->PrepareForIRWriting(resolvedTypeRef->ToTypeInstance());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BF_ASSERT((mCompiler->mCompileState != BfCompiler::CompileState_Unreified) && (mCompiler->mCompileState != BfCompiler::CompileState_VData));
|
||||||
|
|
||||||
BF_ASSERT((typeModule != mContext->mUnreifiedModule) && (typeModule != mContext->mScratchModule));
|
BfLogSysM("Queued reification of type %p in module %p in PopulateType\n", resolvedTypeRef, typeModule);
|
||||||
|
|
||||||
BF_ASSERT(!typeModule->mIsSpecialModule);
|
BF_ASSERT((typeModule != mContext->mUnreifiedModule) && (typeModule != mContext->mScratchModule));
|
||||||
// This caused issues - we may need to reify a type and then request a method
|
|
||||||
typeModule->mReifyQueued = true;
|
BF_ASSERT(!typeModule->mIsSpecialModule);
|
||||||
mContext->mReifyModuleWorkList.Add(typeModule);
|
// This caused issues - we may need to reify a type and then request a method
|
||||||
//typeModule->ReifyModule();
|
typeModule->mReifyQueued = true;
|
||||||
|
mContext->mReifyModuleWorkList.Add(typeModule);
|
||||||
|
//typeModule->ReifyModule();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3342,28 +3371,9 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeInstance->mTypeOptionsIdx == -2)
|
if (typeInstance->mTypeOptionsIdx == -2)
|
||||||
|
{
|
||||||
SetTypeOptions(typeInstance);
|
SetTypeOptions(typeInstance);
|
||||||
|
}
|
||||||
// if (typeInstance->mDefineState == BfTypeDefineState_CETypeInit)
|
|
||||||
// {
|
|
||||||
// if (populateType <= BfPopulateType_AllowStaticMethods)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// auto refNode = typeDef->GetRefNode();
|
|
||||||
// Fail("OnCompile const evaluation creates a data dependency during TypeInit", refNode);
|
|
||||||
// mCompiler->mCEMachine->Fail("OnCompile const evaluation creates a data dependency during TypeInit");
|
|
||||||
// }
|
|
||||||
// else if (typeInstance->mDefineState < BfTypeDefineState_CEPostTypeInit)
|
|
||||||
// {
|
|
||||||
// typeInstance->mDefineState = BfTypeDefineState_CETypeInit;
|
|
||||||
// ExecuteCEOnCompile(typeInstance, BfCEOnCompileKind_TypeInit);
|
|
||||||
//
|
|
||||||
// if (_CheckTypeDone())
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// if (typeInstance->mDefineState < BfTypeDefineState_CEPostTypeInit)
|
|
||||||
// typeInstance->mDefineState = BfTypeDefineState_CEPostTypeInit;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (populateType <= BfPopulateType_AllowStaticMethods)
|
if (populateType <= BfPopulateType_AllowStaticMethods)
|
||||||
return;
|
return;
|
||||||
|
@ -3400,6 +3410,17 @@ void BfModule::DoPopulateType(BfType* resolvedTypeRef, BfPopulateType populateTy
|
||||||
if (typeOptions != NULL)
|
if (typeOptions != NULL)
|
||||||
{
|
{
|
||||||
typeInstance->mHasBeenInstantiated = typeOptions->Apply(typeInstance->HasBeenInstantiated(), BfOptionFlags_ReflectAssumeInstantiated);
|
typeInstance->mHasBeenInstantiated = typeOptions->Apply(typeInstance->HasBeenInstantiated(), BfOptionFlags_ReflectAssumeInstantiated);
|
||||||
|
|
||||||
|
bool alwaysInclude = typeInstance->mAlwaysIncludeFlags != 0;
|
||||||
|
if ((typeOptions->Apply(alwaysInclude, BfOptionFlags_ReflectAlwaysIncludeType)) ||
|
||||||
|
(typeOptions->Apply(alwaysInclude, BfOptionFlags_ReflectAlwaysIncludeAll)))
|
||||||
|
{
|
||||||
|
typeInstance->mAlwaysIncludeFlags = (BfAlwaysIncludeFlags)(typeInstance->mAlwaysIncludeFlags | BfAlwaysIncludeFlag_Type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeInstance->mAlwaysIncludeFlags = BfAlwaysIncludeFlag_None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4632,6 +4653,30 @@ void BfModule::DoTypeInstanceMethodProcessing(BfTypeInstance* typeInstance)
|
||||||
{
|
{
|
||||||
auto methodInstanceGroup = &typeInstance->mMethodInstanceGroups[methodDef->mIdx];
|
auto methodInstanceGroup = &typeInstance->mMethodInstanceGroups[methodDef->mIdx];
|
||||||
|
|
||||||
|
// Don't set these pointers during resolve pass because they may become invalid if it's just a temporary autocomplete method
|
||||||
|
if (mCompiler->mResolvePassData == NULL)
|
||||||
|
{
|
||||||
|
if ((methodDef->mMethodType == BfMethodType_Ctor) && (methodDef->mIsStatic))
|
||||||
|
{
|
||||||
|
typeInstance->mHasStaticInitMethod = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((methodDef->mMethodType == BfMethodType_Dtor) && (methodDef->mIsStatic))
|
||||||
|
{
|
||||||
|
typeInstance->mHasStaticDtorMethod = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((methodDef->mMethodType == BfMethodType_Normal) && (methodDef->mIsStatic) && (methodDef->mName == BF_METHODNAME_MARKMEMBERS_STATIC))
|
||||||
|
{
|
||||||
|
typeInstance->mHasStaticMarkMethod = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((methodDef->mMethodType == BfMethodType_Normal) && (methodDef->mIsStatic) && (methodDef->mName == BF_METHODNAME_FIND_TLS_MEMBERS))
|
||||||
|
{
|
||||||
|
typeInstance->mHasTLSFindMethod = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Thsi MAY be generated already
|
// Thsi MAY be generated already
|
||||||
// This should still be set to the default value
|
// This should still be set to the default value
|
||||||
//BF_ASSERT((methodInstanceGroup->mOnDemandKind == BfMethodOnDemandKind_NotSet) || (methodInstanceGroup->mOnDemandKind == BfMethodOnDemandKind_AlwaysInclude));
|
//BF_ASSERT((methodInstanceGroup->mOnDemandKind == BfMethodOnDemandKind_NotSet) || (methodInstanceGroup->mOnDemandKind == BfMethodOnDemandKind_AlwaysInclude));
|
||||||
|
@ -5728,6 +5773,8 @@ void BfModule::AddMethodToWorkList(BfMethodInstance* methodInstance)
|
||||||
if (!mIsModuleMutable)
|
if (!mIsModuleMutable)
|
||||||
PrepareForIRWriting(methodInstance->GetOwner());
|
PrepareForIRWriting(methodInstance->GetOwner());
|
||||||
|
|
||||||
|
SetAndRestoreValue<bool> prevIgnoreWrites(mBfIRBuilder->mIgnoreWrites, mWantsIRIgnoreWrites);
|
||||||
|
|
||||||
BfIRValue func = CreateFunctionFrom(methodInstance, false, methodInstance->mAlwaysInline);
|
BfIRValue func = CreateFunctionFrom(methodInstance, false, methodInstance->mAlwaysInline);
|
||||||
if (func)
|
if (func)
|
||||||
{
|
{
|
||||||
|
@ -8181,9 +8228,7 @@ BfTypeDef* BfModule::FindTypeDefRaw(const BfAtomComposite& findName, int numGene
|
||||||
BfTypeDef* protErrorTypeDef = NULL;
|
BfTypeDef* protErrorTypeDef = NULL;
|
||||||
BfTypeInstance* protErrorOuterType = NULL;
|
BfTypeInstance* protErrorOuterType = NULL;
|
||||||
|
|
||||||
|
if ((!lookupCtx.HasValidMatch()) && (typeInstance != NULL))
|
||||||
|
|
||||||
if (!lookupCtx.HasValidMatch())
|
|
||||||
{
|
{
|
||||||
std::function<bool(BfTypeInstance*)> _CheckType = [&](BfTypeInstance* typeInstance)
|
std::function<bool(BfTypeInstance*)> _CheckType = [&](BfTypeInstance* typeInstance)
|
||||||
{
|
{
|
||||||
|
@ -8271,6 +8316,12 @@ BfTypeDef* BfModule::FindTypeDefRaw(const BfAtomComposite& findName, int numGene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((!lookupCtx.HasValidMatch()) && (typeInstance == NULL))
|
||||||
|
{
|
||||||
|
if (useTypeDef->mOuterType != NULL)
|
||||||
|
return FindTypeDefRaw(findName, numGenericArgs, typeInstance, useTypeDef->mOuterType, error);
|
||||||
|
}
|
||||||
|
|
||||||
if ((error != NULL) && (lookupCtx.mAmbiguousTypeDef != NULL))
|
if ((error != NULL) && (lookupCtx.mAmbiguousTypeDef != NULL))
|
||||||
{
|
{
|
||||||
if (error->mErrorKind == BfTypeLookupError::BfErrorKind_None)
|
if (error->mErrorKind == BfTypeLookupError::BfErrorKind_None)
|
||||||
|
@ -8292,7 +8343,9 @@ BfTypeDef* BfModule::FindTypeDef(const BfAtomComposite& findName, int numGeneric
|
||||||
BP_ZONE("BfModule::FindTypeDef_1");
|
BP_ZONE("BfModule::FindTypeDef_1");
|
||||||
|
|
||||||
BfTypeInstance* typeInstance = (typeInstanceOverride != NULL) ? typeInstanceOverride : mCurTypeInstance;
|
BfTypeInstance* typeInstance = (typeInstanceOverride != NULL) ? typeInstanceOverride : mCurTypeInstance;
|
||||||
if (typeInstance == NULL)
|
auto useTypeDef = GetActiveTypeDef(typeInstanceOverride, true);
|
||||||
|
|
||||||
|
if ((typeInstance == NULL) && (useTypeDef == NULL))
|
||||||
{
|
{
|
||||||
BfProject* project = NULL;
|
BfProject* project = NULL;
|
||||||
if ((mCompiler->mResolvePassData != NULL) && (mCompiler->mResolvePassData->mParser != NULL))
|
if ((mCompiler->mResolvePassData != NULL) && (mCompiler->mResolvePassData->mParser != NULL))
|
||||||
|
@ -8312,9 +8365,7 @@ BfTypeDef* BfModule::FindTypeDef(const BfAtomComposite& findName, int numGeneric
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto useTypeDef = GetActiveTypeDef(typeInstanceOverride, true);
|
if ((mCompiler->mResolvePassData != NULL) && (mCompiler->mResolvePassData->mAutoComplete != NULL) && (typeInstance != NULL))
|
||||||
|
|
||||||
if ((mCompiler->mResolvePassData != NULL) && (mCompiler->mResolvePassData->mAutoComplete != NULL))
|
|
||||||
{
|
{
|
||||||
if (mCompiler->mResolvePassData->mAutoCompleteTempTypes.Contains(useTypeDef))
|
if (mCompiler->mResolvePassData->mAutoCompleteTempTypes.Contains(useTypeDef))
|
||||||
return FindTypeDefRaw(findName, numGenericArgs, typeInstance, useTypeDef, error);
|
return FindTypeDefRaw(findName, numGenericArgs, typeInstance, useTypeDef, error);
|
||||||
|
@ -8327,7 +8378,7 @@ BfTypeDef* BfModule::FindTypeDef(const BfAtomComposite& findName, int numGeneric
|
||||||
|
|
||||||
BfTypeLookupEntry* typeLookupEntryPtr = NULL;
|
BfTypeLookupEntry* typeLookupEntryPtr = NULL;
|
||||||
BfTypeLookupResult* resultPtr = NULL;
|
BfTypeLookupResult* resultPtr = NULL;
|
||||||
if (typeInstance->mLookupResults.TryAdd(typeLookupEntry, &typeLookupEntryPtr, &resultPtr))
|
if ((typeInstance != NULL) && (typeInstance->mLookupResults.TryAdd(typeLookupEntry, &typeLookupEntryPtr, &resultPtr)))
|
||||||
{
|
{
|
||||||
typeLookupEntryPtr->mAtomUpdateIdx = typeLookupEntry.mName.GetAtomUpdateIdx();
|
typeLookupEntryPtr->mAtomUpdateIdx = typeLookupEntry.mName.GetAtomUpdateIdx();
|
||||||
|
|
||||||
|
@ -8353,7 +8404,7 @@ BfTypeDef* BfModule::FindTypeDef(const BfAtomComposite& findName, int numGeneric
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (resultPtr->mForceLookup)
|
if ((resultPtr == NULL) || (resultPtr->mForceLookup))
|
||||||
return FindTypeDefRaw(findName, numGenericArgs, typeInstance, useTypeDef, error);
|
return FindTypeDefRaw(findName, numGenericArgs, typeInstance, useTypeDef, error);
|
||||||
else
|
else
|
||||||
return resultPtr->mTypeDef;
|
return resultPtr->mTypeDef;
|
||||||
|
|
|
@ -1571,21 +1571,27 @@ enum BfAttributeTargets : int32
|
||||||
BfAttributeTargets_All = 0x3FFFFF
|
BfAttributeTargets_All = 0x3FFFFF
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum BfAttributeFlags : int8
|
||||||
|
{
|
||||||
|
BfAttributeFlag_None,
|
||||||
|
BfAttributeFlag_DisallowAllowMultiple = 1,
|
||||||
|
BfAttributeFlag_NotInherited = 2,
|
||||||
|
BfAttributeFlag_ReflectAttribute = 4,
|
||||||
|
BfAttributeFlag_AlwaysIncludeTarget = 8
|
||||||
|
};
|
||||||
|
|
||||||
class BfAttributeData
|
class BfAttributeData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BfAttributeTargets mAttributeTargets;
|
BfAttributeTargets mAttributeTargets;
|
||||||
BfAlwaysIncludeFlags mAlwaysIncludeUser;
|
BfAlwaysIncludeFlags mAlwaysIncludeUser;
|
||||||
bool mInherited;
|
BfAttributeFlags mFlags;
|
||||||
bool mAllowMultiple;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BfAttributeData()
|
BfAttributeData()
|
||||||
{
|
{
|
||||||
mAttributeTargets = BfAttributeTargets_All;
|
mAttributeTargets = BfAttributeTargets_All;
|
||||||
mAlwaysIncludeUser = BfAlwaysIncludeFlag_None;
|
mAlwaysIncludeUser = BfAlwaysIncludeFlag_None;
|
||||||
mInherited = true;
|
mFlags = BfAttributeFlag_None;
|
||||||
mAllowMultiple = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue