diff --git a/BeefLibs/corlib/src/Delegate.bf b/BeefLibs/corlib/src/Delegate.bf index 7b016fe9..c84ef780 100644 --- a/BeefLibs/corlib/src/Delegate.bf +++ b/BeefLibs/corlib/src/Delegate.bf @@ -1,17 +1,24 @@ namespace System { - class Delegate + class Delegate : IHashable { void* mFuncPtr; void* mTarget; public static bool Equals(Delegate a, Delegate b) { - if ((Object)a == (Object)b) + if (a === null) + return b === null; + return a.Equals(b); + } + + public virtual bool Equals(Delegate val) + { + if (this === val) return true; - if ((Object)a == null || (Object)b == null) + if (val == null) return false; - return (a.mFuncPtr == b.mFuncPtr) && (a.mTarget == b.mTarget); + return (mFuncPtr == val.mFuncPtr) && (mTarget == val.mTarget); } public Result GetFuncPtr() @@ -37,6 +44,19 @@ namespace System // Note- this is safe even if mTarget is not an object, because the GC does object address validation GC.Mark(Internal.UnsafeCastToObject(mTarget)); } + + public int GetHashCode() + { + return (int)mFuncPtr; + } + + [Commutable] + public static bool operator==(Delegate a, Delegate b) + { + if (a === null) + return b === null; + return a.Equals(b); + } } delegate void Action(); diff --git a/BeefLibs/corlib/src/Runtime.bf b/BeefLibs/corlib/src/Runtime.bf index 87845164..200f521a 100644 --- a/BeefLibs/corlib/src/Runtime.bf +++ b/BeefLibs/corlib/src/Runtime.bf @@ -418,7 +418,7 @@ namespace System { using (sMonitor.Val.Enter()) { - if (sErrorHandlers.Remove(handler)) + if (sErrorHandlers.RemoveStrict(handler)) return .Ok; } return .Err; diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index ed6d216a..140bd9c4 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -11881,7 +11881,7 @@ namespace IDE { defer { - if (mPendingDebugExprHandler != pendingHandler) + if (mPendingDebugExprHandler !== pendingHandler) delete pendingHandler; } diff --git a/IDEHelper/Compiler/BfDefBuilder.h b/IDEHelper/Compiler/BfDefBuilder.h index feeea107..3aa8d454 100644 --- a/IDEHelper/Compiler/BfDefBuilder.h +++ b/IDEHelper/Compiler/BfDefBuilder.h @@ -38,7 +38,7 @@ public: static BfMethodDef* AddMethod(BfTypeDef* typeDef, BfMethodType methodType, BfProtection protection, bool isStatic, const StringImpl& name, bool addedAfterEmit = false); static BfMethodDef* AddDtor(BfTypeDef* typeDef); static void AddDynamicCastMethods(BfTypeDef* typeDef); - void AddParam(BfMethodDef* methodDef, BfTypeReference* typeRef, const StringImpl& paramName); + static void AddParam(BfMethodDef* methodDef, BfTypeReference* typeRef, const StringImpl& paramName); BfTypeDef* ComparePrevTypeDef(BfTypeDef* prevTypeDef, BfTypeDef* checkTypeDef); void FinishTypeDef(bool wantsToString); void ParseAttributes(BfAttributeDirective* attributes, BfMethodDef* methodDef); diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index d1de412b..afb5dc85 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/IDEHelper/Compiler/BfExprEvaluator.cpp @@ -20,6 +20,7 @@ #include "BfVarDeclChecker.h" #include "BfFixits.h" #include "CeMachine.h" +#include "BfDefBuilder.h" #pragma warning(pop) #pragma warning(disable:4996) @@ -13551,26 +13552,24 @@ BfLambdaInstance* BfExprEvaluator::GetLambdaInstance(BfLambdaBindExpression* lam BfTypeInstance* useTypeInstance = delegateTypeInstance; BfClosureType* closureTypeInst = NULL; - if ((capturedEntries.size() != 0) || (lambdaBindExpr->mDtor != NULL) || (copyOuterCaptures)) + + // If we are allowing hot swapping we may add a capture later. We also need an equal method that ignores 'target' even when we're capturing ourself + if ((capturedEntries.size() != 0) || (lambdaBindExpr->mDtor != NULL) || (copyOuterCaptures) || (mModule->mCompiler->mOptions.mAllowHotSwapping) || (closureState.mCapturedDelegateSelf)) { hashCtx.MixinStr(curProject->mName); if (copyOuterCaptures) { -// String typeName = mModule->DoTypeToString(outerClosure, BfTypeNameFlag_DisambiguateDups); -// hashCtx.MixinStr(typeName); hashCtx.Mixin(outerClosure->mTypeId); } for (auto& capturedEntry : capturedEntries) { -// String typeName = mModule->DoTypeToString(capturedEntry.mType, BfTypeNameFlag_DisambiguateDups); -// hashCtx.MixinStr(typeName); hashCtx.Mixin(capturedEntry.mType->mTypeId); hashCtx.MixinStr(capturedEntry.mName); hashCtx.Mixin(capturedEntry.mExplicitlyByReference); } - + if (lambdaBindExpr->mDtor != NULL) { // Has DTOR thunk @@ -13590,6 +13589,16 @@ BfLambdaInstance* BfExprEvaluator::GetLambdaInstance(BfLambdaBindExpression* lam closureTypeInst->Init(curProject); closureTypeInst->mTypeDef->mProject = curProject; + auto delegateDirectTypeRef = BfAstNode::ZeroedAlloc(); + delegateDirectTypeRef->Init(mModule->mCompiler->mDelegateTypeDef); + closureTypeInst->mDirectAllocNodes.push_back(delegateDirectTypeRef); + + BfMethodDef* methodDef = BfDefBuilder::AddMethod(closureTypeInst->mTypeDef, BfMethodType_Normal, BfProtection_Public, false, "Equals"); + methodDef->mReturnTypeRef = mModule->mSystem->mDirectBoolTypeRef; + BfDefBuilder::AddParam(methodDef, delegateDirectTypeRef, "val"); + methodDef->mIsVirtual = true; + methodDef->mIsOverride = true; + if (copyOuterCaptures) { for (auto& fieldInstance : outerClosure->mFieldInstances) @@ -13627,9 +13636,8 @@ BfLambdaInstance* BfExprEvaluator::GetLambdaInstance(BfLambdaBindExpression* lam mModule->mBfIRBuilder->PopulateType(useTypeInstance); mModule->PopulateType(useTypeInstance); - // If we are allowing hot swapping, we need to always mangle the name to non-static because if we add a capture - // later then we need to have the mangled names match - methodDef->mIsStatic = (closureTypeInst == NULL) && (!mModule->mCompiler->mOptions.mAllowHotSwapping) && (!closureState.mCapturedDelegateSelf); + + methodDef->mIsStatic = closureTypeInst == NULL; SizedArray origParamTypes; BfIRType origReturnType; diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index d5355276..2b802789 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/IDEHelper/Compiler/BfModule.cpp @@ -3294,7 +3294,7 @@ void BfModule::CheckErrorAttributes(BfTypeInstance* typeInstance, BfMethodInstan BfIRConstHolder* constHolder = typeInstance->mConstHolder; auto customAttribute = customAttributes->Get(mCompiler->mObsoleteAttributeTypeDef); - if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty())) + if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty()) && (targetSrc != NULL)) { String err; if (methodInstance != NULL) @@ -3349,7 +3349,7 @@ void BfModule::CheckErrorAttributes(BfTypeInstance* typeInstance, BfMethodInstan } customAttribute = customAttributes->Get(mCompiler->mWarnAttributeTypeDef); - if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty())) + if ((customAttribute != NULL) && (!customAttribute->mCtorArgs.IsEmpty()) && (targetSrc != NULL)) { String err; if (methodInstance != NULL) @@ -4716,6 +4716,87 @@ void BfModule::CreateFakeCallerMethod(const String& funcName) mBfIRBuilder->CreateRetVoid(); } +void BfModule::CreateDelegateEqualsMethod() +{ + if (mBfIRBuilder->mIgnoreWrites) + return; + + auto refNode = mCurTypeInstance->mTypeDef->GetRefNode(); + if (refNode == NULL) + refNode = mCompiler->mValueTypeTypeDef->GetRefNode(); + UpdateSrcPos(refNode); + SetIllegalSrcPos(); + + auto boolType = GetPrimitiveType(BfTypeCode_Boolean); + auto resultVal = CreateAlloca(boolType); + mBfIRBuilder->CreateStore(GetConstValue(0, boolType), resultVal); + + auto exitBB = mBfIRBuilder->CreateBlock("exit"); + + auto delegateType = ResolveTypeDef(mCompiler->mDelegateTypeDef)->ToTypeInstance(); + mBfIRBuilder->PopulateType(delegateType); + + BfExprEvaluator exprEvaluator(this); + BfTypedValue leftTypedVal = exprEvaluator.LoadLocal(mCurMethodState->mLocals[0]); + BfTypedValue lhsDelegate = BfTypedValue(mBfIRBuilder->CreateBitCast(leftTypedVal.mValue, mBfIRBuilder->MapType(delegateType)), delegateType); + + BfTypedValue rhsDelegate = exprEvaluator.LoadLocal(mCurMethodState->mLocals[1]); + rhsDelegate = LoadValue(rhsDelegate); + BfTypedValue rightTypedVal = BfTypedValue(mBfIRBuilder->CreateBitCast(rhsDelegate.mValue, mBfIRBuilder->MapType(mCurTypeInstance)), mCurTypeInstance); + + auto& targetFieldInstance = delegateType->mFieldInstances[0]; + + BfTypedValue leftValue = BfTypedValue(mBfIRBuilder->CreateInBoundsGEP(lhsDelegate.mValue, 0, targetFieldInstance.mDataIdx), targetFieldInstance.mResolvedType, true); + BfTypedValue rightValue = BfTypedValue(mBfIRBuilder->CreateInBoundsGEP(rhsDelegate.mValue, 0, targetFieldInstance.mDataIdx), targetFieldInstance.mResolvedType, true); + leftValue = LoadValue(leftValue); + rightValue = LoadValue(rightValue); + EmitEquals(leftValue, rightValue, exitBB, false); + + bool hadComparison = false; + for (auto& fieldRef : mCurTypeInstance->mFieldInstances) + { + BfFieldInstance* fieldInstance = &fieldRef; + if (fieldInstance->mDataOffset == -1) + continue; + + auto fieldType = fieldInstance->mResolvedType; + if (fieldType->IsValuelessType()) + continue; + if (fieldType->IsVar()) + continue; + if (fieldType->IsMethodRef()) + continue; + + if (fieldType->IsRef()) + fieldType = CreatePointerType(fieldType->GetUnderlyingType()); + + BfTypedValue leftValue = BfTypedValue(mBfIRBuilder->CreateInBoundsGEP(leftTypedVal.mValue, 0, fieldInstance->mDataIdx), fieldType, true); + BfTypedValue rightValue = BfTypedValue(mBfIRBuilder->CreateInBoundsGEP(rightTypedVal.mValue, 0, fieldInstance->mDataIdx), fieldType, true); + + if (!fieldInstance->mResolvedType->IsComposite()) + { + leftValue = LoadValue(leftValue); + rightValue = LoadValue(rightValue); + } + + EmitEquals(leftValue, rightValue, exitBB, false); + } + + mBfIRBuilder->CreateStore(GetConstValue(1, boolType), resultVal); + mBfIRBuilder->CreateBr(exitBB); + + mBfIRBuilder->AddBlock(exitBB); + mBfIRBuilder->SetInsertPoint(exitBB); + + auto loadedResult = mBfIRBuilder->CreateLoad(resultVal); + + ClearLifetimeEnds(); + + mBfIRBuilder->CreateRet(loadedResult); + + mCurMethodState->mHadReturn = true; +} + void BfModule::CreateValueTypeEqualsMethod(bool strictEquals) { if (mCurMethodInstance->mIsUnspecialized) @@ -20512,6 +20593,12 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup, skipBody = true; skipEndChecks = true; } + else if ((methodDef->mName == BF_METHODNAME_EQUALS) && (mCurTypeInstance->IsDelegate())) + { + CreateDelegateEqualsMethod(); + skipBody = true; + skipEndChecks = true; + } else { auto propertyDeclaration = methodDef->GetPropertyDeclaration(); diff --git a/IDEHelper/Compiler/BfModule.h b/IDEHelper/Compiler/BfModule.h index 3c35175b..068b424e 100644 --- a/IDEHelper/Compiler/BfModule.h +++ b/IDEHelper/Compiler/BfModule.h @@ -1937,6 +1937,7 @@ public: void ProcessMethod_ProcessDeferredLocals(int startIdx = 0); void ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup = false, bool forceIRWrites = false); void CreateDynamicCastMethod(); + void CreateDelegateEqualsMethod(); void CreateValueTypeEqualsMethod(bool strictEquals); BfIRFunction GetIntrinsic(BfMethodInstance* methodInstance, bool reportFailure = false); BfIRFunction GetBuiltInFunc(BfBuiltInFuncType funcType); diff --git a/IDEHelper/Tests/src/Delegates.bf b/IDEHelper/Tests/src/Delegates.bf index 639fadb0..260bec7c 100644 --- a/IDEHelper/Tests/src/Delegates.bf +++ b/IDEHelper/Tests/src/Delegates.bf @@ -182,6 +182,29 @@ namespace Tests delegate Vector3f() vecDlg = scope => GetVector3f; Test.Assert(vecDlg().mX == 101); + + int allocCount = 0; + + Action act = null; + for (int i = 0; i < 10; i++) + { + int j = i / 3; + Action newAct = scope:: () => + { + Console.WriteLine($"act {j}"); + }; + + if (act == null || act != newAct) + { + act = newAct; + allocCount++; + } + else + { + Test.Assert(act.GetHashCode() == newAct.GetHashCode()); + } + } + Test.Assert(allocCount == 4); } public static void Modify(ref int a, ref Splattable b)