Skip to content

Commit 60a44d7

Browse files
committed
Fixed devirtualized methods with extension overrides
1 parent e4e0f04 commit 60a44d7

14 files changed

Lines changed: 506 additions & 70 deletions

File tree

‎Build_BeefySysLib.bat‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ CALL bin\msbuild.bat BeefySysLib\BeefySysLib.vcxproj /p:Configuration=Release /p
1111
:SUCCESS
1212
@ECHO SUCCESS!
1313
@POPD
14+
pause
1415
@EXIT /b 0
1516

1617
:HADERROR
1718
@ECHO =================FAILED=================
1819
@POPD
19-
@EXIT /b %ERRORLEVEL%
20+
pause
21+
@EXIT /b %ERRORLEVEL%
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Tests:
2+
# Adding a type extension that overrides a base virtual method during a hot compile
3+
# Devirtualized 'base' calls picking up an override introduced by that extension
4+
5+
ShowFile("src/HotSwap_ExtensionOverride.bf")
6+
GotoText("//HotSwap_ExtensionOverride_Start")
7+
ToggleBreakpoint()
8+
RunWithCompiling()
9+
10+
# Before the extension exists
11+
StepOver()
12+
StepOver()
13+
AssertEvalEquals("a", "10")
14+
AssertEvalEquals("b", "11")
15+
16+
# Hot compile, adding an extension that overrides ClassA.MethodA
17+
ToggleCommentAt("ExtClassA_MethodA")
18+
Compile()
19+
20+
# The virtual call picks up the override, and so does the 'base' call inside ClassB.MethodA
21+
Continue()
22+
StepOver()
23+
StepOver()
24+
AssertEvalEquals("a", "100")
25+
AssertEvalEquals("b", "101")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma warning disable 168
2+
3+
namespace IDETest
4+
{
5+
class HotSwap_ExtensionOverride
6+
{
7+
public class ClassA
8+
{
9+
public virtual int MethodA()
10+
{
11+
return 10;
12+
}
13+
}
14+
15+
public class ClassB : ClassA
16+
{
17+
public override int MethodA()
18+
{
19+
// This is a devirtualized 'base' call. Once the extension below adds an override for
20+
// ClassA.MethodA it has to start resolving to that override instead.
21+
return base.MethodA() + 1;
22+
}
23+
}
24+
25+
/*ExtClassA_MethodA
26+
public extension ClassA
27+
{
28+
public override int MethodA()
29+
{
30+
return 100;
31+
}
32+
}
33+
*/
34+
35+
static void DoTest()
36+
{
37+
ClassA ca = scope ClassA();
38+
ClassB cb = scope ClassB();
39+
40+
//HotSwap_ExtensionOverride_Start
41+
int a = ca.MethodA();
42+
int b = cb.MethodA();
43+
}
44+
45+
public static void Test()
46+
{
47+
DoTest();
48+
DoTest();
49+
}
50+
}
51+
}

‎IDE/Tests/Test1/src/Program.bf‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace IDETest
1313
HotTester.Test();
1414
HotSwap_BaseChange.Test();
1515
HotSwap_Data.Test();
16+
HotSwap_ExtensionOverride.Test();
1617
HotSwap_GetUnusued.Test();
1718
HotSwap_Interfaces2.Test();
1819
HotSwap_Lambdas01.Test();

‎IDEHelper/Compiler/BfCompiler.cpp‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,18 @@ void BfCompiler::CreateVData(BfVDataModule* bfModule)
15541554
}
15551555
}
15561556

1557+
// Devirtualized calls (ie: 'base.Method()') route through a thunk when a type extension may
1558+
// supply a different override - each executable's vdata resolves that thunk for itself.
1559+
for (auto type : vdataTypeList)
1560+
{
1561+
if ((!type->IsReified()) || (type->IsUnspecializedType()) || (type->IsTypeAlias()))
1562+
continue;
1563+
auto typeInst = type->ToTypeInstance();
1564+
if (typeInst == NULL)
1565+
continue;
1566+
bfModule->CreateDevirtThunks(typeInst);
1567+
}
1568+
15571569
for (int typeId = 0; typeId < (int)typeDataVector.size(); typeId++)
15581570
{
15591571
if (!typeDataVector[typeId])
@@ -6054,7 +6066,8 @@ void BfCompiler::PopulateReified()
60546066
{
60556067
if ((methodDef->mIsOverride) && (methodDef->mParams.mSize == declaringMethod->mMethodDef->mParams.mSize))
60566068
{
6057-
auto implMethod = typeInst->mModule->GetRawMethodInstance(typeInst, methodDef);
6069+
// 'methodDef' is from checkTypeInst's method set, so mIdx indexes that type's methods
6070+
auto implMethod = typeInst->mModule->GetRawMethodInstance(checkTypeInst, methodDef);
60586071
if ((implMethod != NULL) && (typeInst->mModule->CompareMethodSignatures(declaringMethod, implMethod)))
60596072
{
60606073
if ((implMethod != NULL) && ((!implMethod->mMethodInstanceGroup->IsImplemented()) || (!implMethod->mIsReified)))
@@ -7437,11 +7450,7 @@ bool BfCompiler::DoCompile(const StringImpl& outputDirectory)
74377450
mPassInstance->Fail(StrFormat("Project '%s' must reference core library '%s'", bfProject->mName.c_str(), mBfObjectTypeDef->mProject->mName.c_str()));
74387451
}
74397452

7440-
if ((bfProject->mTargetType != BfTargetType_BeefConsoleApplication) && (bfProject->mTargetType != BfTargetType_BeefWindowsApplication) &&
7441-
(bfProject->mTargetType != BfTargetType_BeefLib_DynamicLib) && (bfProject->mTargetType != BfTargetType_BeefLib_StaticLib) &&
7442-
(bfProject->mTargetType != BfTargetType_C_ConsoleApplication) && (bfProject->mTargetType != BfTargetType_C_WindowsApplication) &&
7443-
(bfProject->mTargetType != BfTargetType_BeefTest) &&
7444-
(bfProject->mTargetType != BfTargetType_BeefApplication_StaticLib) && (bfProject->mTargetType != BfTargetType_BeefApplication_DynamicLib))
7453+
if (!bfProject->IsFinalProgramTarget())
74457454
continue;
74467455

74477456
if (bfProject->mTargetType == BfTargetType_BeefTest)

‎IDEHelper/Compiler/BfExprEvaluator.cpp‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6660,6 +6660,28 @@ BfTypedValue BfExprEvaluator::CreateCall(BfAstNode* targetSrc, BfMethodInstance*
66606660
auto methodDef = methodInstance->mMethodDef;
66616661
BfIRValue funcCallInst = func;
66626662

6663+
if ((bypassVirtual) && (funcCallInst) && (!isDelegateThunk) && (origTargetType != NULL) && (mFunctionBindResult == NULL))
6664+
{
6665+
// We resolved this against only the extensions our own project can see, but this module is
6666+
// shared by every executable that links it - route through a per-executable thunk instead.
6667+
auto origTargetTypeInst = origTargetType->ToTypeInstance();
6668+
auto thunkFunc = mModule->TryGetDevirtThunk(origTargetTypeInst, methodInstance);
6669+
if (thunkFunc)
6670+
{
6671+
// Thunk is typed from the declaring method, which may be owned further up the chain
6672+
auto& declaringMethodRef = origTargetTypeInst->mVirtualMethodTable[methodInstance->mVirtualTableIdx].mDeclaringMethod;
6673+
BfMethodInstance* declaringMethodInstance = (BfMethodInstance*)declaringMethodRef;
6674+
if ((!methodDef->mIsStatic) && (!irArgs.IsEmpty()) && (declaringMethodInstance->GetOwner() != methodInstance->GetOwner()))
6675+
{
6676+
// GetStructRetIdx is the sret param index, so 'this' shifts only when sret comes first
6677+
int thisIdx = (methodInstance->GetStructRetIdx() == 0) ? 1 : 0;
6678+
if (thisIdx < (int)irArgs.size())
6679+
irArgs[thisIdx] = mModule->mBfIRBuilder->CreateBitCast(irArgs[thisIdx], mModule->mBfIRBuilder->MapTypeInstPtr(declaringMethodInstance->GetOwner()));
6680+
}
6681+
funcCallInst = thunkFunc;
6682+
}
6683+
}
6684+
66636685
auto importCallKind = methodInstance->GetImportCallKind();
66646686
if ((funcCallInst) && (importCallKind != BfImportCallKind_None))
66656687
{

0 commit comments

Comments
 (0)