- implemented handling for virtual function.

Syntax-wise I chose to make it as strict as possible to reduce the chance of errors: Virtual base functions must be declared with the 'virtual' keyword, and overrides in child classes with the 'override' keyword. This way any mismatch in parameters that otherwise would cause silent failure will outright produce a compile error.
This commit is contained in:
Christoph Oelckers 2016-11-11 20:05:07 +01:00
commit 72e77a6c65
15 changed files with 222 additions and 47 deletions

View file

@ -6693,12 +6693,16 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
}
}
VMFunction *vmfunc = Function->Variants[0].Implementation;
bool staticcall = (vmfunc->Final || vmfunc->VirtualIndex == -1 || NoVirtual);
count = 0;
// Emit code to pass implied parameters
ExpEmit selfemit;
if (Function->Variants[0].Flags & VARF_Method)
{
assert(Self != nullptr);
ExpEmit selfemit = Self->Emit(build);
selfemit = Self->Emit(build);
assert(selfemit.RegType == REGT_POINTER);
build->Emit(OP_PARAM, 0, selfemit.RegType, selfemit.RegNum);
count += 1;
@ -6718,8 +6722,9 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
}
count += 2;
}
selfemit.Free(build);
if (staticcall) selfemit.Free(build);
}
else staticcall = true;
// Emit code to pass explicit parameters
for (unsigned i = 0; i < ArgList.Size(); ++i)
{
@ -6729,27 +6734,56 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
ArgList.ShrinkToFit();
// Get a constant register for this function
VMFunction *vmfunc = Function->Variants[0].Implementation;
int funcaddr = build->GetConstantAddress(vmfunc, ATAG_OBJECT);
// Emit the call
if (EmitTail)
{ // Tail call
build->Emit(OP_TAIL_K, funcaddr, count, 0);
ExpEmit call;
call.Final = true;
return call;
}
else if (vmfunc->Proto->ReturnTypes.Size() > 0)
{ // Call, expecting one result
ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount());
build->Emit(OP_CALL_K, funcaddr, count, 1);
build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum);
return reg;
if (staticcall)
{
int funcaddr = build->GetConstantAddress(vmfunc, ATAG_OBJECT);
// Emit the call
if (EmitTail)
{ // Tail call
build->Emit(OP_TAIL_K, funcaddr, count, 0);
ExpEmit call;
call.Final = true;
return call;
}
else if (vmfunc->Proto->ReturnTypes.Size() > 0)
{ // Call, expecting one result
ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount());
build->Emit(OP_CALL_K, funcaddr, count, 1);
build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum);
return reg;
}
else
{ // Call, expecting no results
build->Emit(OP_CALL_K, funcaddr, count, 0);
return ExpEmit();
}
}
else
{ // Call, expecting no results
build->Emit(OP_CALL_K, funcaddr, count, 0);
return ExpEmit();
{
selfemit.Free(build);
ExpEmit funcreg(build, REGT_POINTER);
build->Emit(OP_VTBL, funcreg.RegNum, selfemit.RegNum, vmfunc->VirtualIndex);
if (EmitTail)
{ // Tail call
build->Emit(OP_TAIL, funcreg.RegNum, count, 0);
ExpEmit call;
call.Final = true;
return call;
}
else if (vmfunc->Proto->ReturnTypes.Size() > 0)
{ // Call, expecting one result
ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount());
build->Emit(OP_CALL, funcreg.RegNum, count, 1);
build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum);
return reg;
}
else
{ // Call, expecting no results
build->Emit(OP_CALL, funcreg.RegNum, count, 0);
return ExpEmit();
}
}
}

View file

@ -655,7 +655,9 @@ class VMFunction : public DObject
HAS_OBJECT_POINTERS;
public:
bool Native;
bool Final = false; // cannot be overridden
BYTE ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
int VirtualIndex = -1;
FName Name;
TArray<VMValue> DefaultArgs;

View file

@ -84,6 +84,7 @@
#define RPI8 MODE_AP | MODE_BIMMZ | MODE_CUNUSED
#define KPI8 MODE_AKP | MODE_BIMMZ | MODE_CUNUSED
#define RPI8I8 MODE_AP | MODE_BIMMZ | MODE_CIMMZ
#define RPRPI8 MODE_AP | MODE_BP | MODE_CIMMZ
#define KPI8I8 MODE_AKP | MODE_BIMMZ | MODE_CIMMZ
#define I8BCP MODE_AIMMZ | MODE_BCJOINT | MODE_BCPARAM
#define THROW MODE_AIMMZ | MODE_BCTHROW

View file

@ -525,6 +525,15 @@ begin:
}
}
NEXTOP;
OP(VTBL):
ASSERTA(a); ASSERTA(B);
{
auto o = (DObject*)reg.a[B];
auto p = o->GetClass();
assert(C < p->Virtuals.Size());
reg.a[a] = p->Virtuals[C];
}
NEXTOP;
OP(CALL_K):
ASSERTKA(a);
assert(konstatag[a] == ATAG_OBJECT);

View file

@ -82,6 +82,7 @@ xx(PARAM, param, __BCP), // push parameter encoded in BC for function call (B=r
xx(PARAMI, parami, I24), // push immediate, signed integer for function call
xx(CALL, call, RPI8I8), // Call function pkA with parameter count B and expected result count C
xx(CALL_K, call, KPI8I8),
xx(VTBL, vtbl, RPRPI8), // dereferences a virtual method table.
xx(TAIL, tail, RPI8), // Call+Ret in a single instruction
xx(TAIL_K, tail, KPI8),
xx(RESULT, result, __BCP), // Result should go in register encoded in BC (in caller, after CALL)

View file

@ -905,6 +905,8 @@ decl_flags(X) ::= decl_flags(A) META(T). { X.Int = A.Int | ZCC_Meta; X.SourceLo
decl_flags(X) ::= decl_flags(A) ACTION(T). { X.Int = A.Int | ZCC_Action; X.SourceLoc = A.SourceLoc ? A.SourceLoc : T.SourceLoc; }
decl_flags(X) ::= decl_flags(A) READONLY(T). { X.Int = A.Int | ZCC_ReadOnly; X.SourceLoc = A.SourceLoc ? A.SourceLoc : T.SourceLoc; }
decl_flags(X) ::= decl_flags(A) DEPRECATED(T). { X.Int = A.Int | ZCC_Deprecated; X.SourceLoc = A.SourceLoc ? A.SourceLoc : T.SourceLoc; }
decl_flags(X) ::= decl_flags(A) VIRTUAL(T). { X.Int = A.Int | ZCC_Virtual; X.SourceLoc = A.SourceLoc ? A.SourceLoc : T.SourceLoc; }
decl_flags(X) ::= decl_flags(A) OVERRIDE(T). { X.Int = A.Int | ZCC_Override; X.SourceLoc = A.SourceLoc ? A.SourceLoc : T.SourceLoc; }
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }

View file

@ -1258,7 +1258,7 @@ bool ZCCCompiler::CompileFields(PStruct *type, TArray<ZCC_VarDeclarator *> &Fiel
PType *fieldtype = DetermineType(type, field, field->Names->Name, field->Type, true, true);
// For structs only allow 'deprecated', for classes exclude function qualifiers.
int notallowed = forstruct? ~ZCC_Deprecated : ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract;
int notallowed = forstruct? ~ZCC_Deprecated : ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override;
if (field->Flags & notallowed)
{
@ -1894,6 +1894,7 @@ void ZCCCompiler::InitDefaults()
if (!c->Type()->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
if (c->Defaults.Size()) Error(c->cls, "%s: Non-actor classes may not have defaults", c->Type()->TypeName.GetChars());
if (c->Type()->ParentClass) c->Type()->ParentClass->DeriveData(c->Type());
}
else
{
@ -1989,6 +1990,16 @@ void ZCCCompiler::InitFunctions()
for (auto c : Classes)
{
// cannot be done earlier because it requires the parent class to be processed by this code, too.
if (c->Type()->ParentClass != nullptr)
{
if (c->Type()->ParentClass->Virtuals.Size() == 0)
{
// This a VMClass which didn't get processed here.
c->Type()->ParentClass->Virtuals = c->Type()->ParentClass->ParentClass->Virtuals;
}
c->Type()->Virtuals = c->Type()->ParentClass->Virtuals;
}
for (auto f : c->Functions)
{
rets.Clear();
@ -2030,13 +2041,32 @@ void ZCCCompiler::InitFunctions()
if (f->Flags & ZCC_Private) varflags |= VARF_Private;
if (f->Flags & ZCC_Protected) varflags |= VARF_Protected;
if (f->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated;
if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual;
if (f->Flags & ZCC_Override) varflags |= VARF_Override;
if (f->Flags & ZCC_Action) varflags |= VARF_Action|VARF_Final, implicitargs = 3; // Action implies Final.
if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final.
if ((f->Flags & (ZCC_Action | ZCC_Static)) == (ZCC_Action | ZCC_Static))
if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'.
// Only one of these flags may be used.
static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static };
static const char * print[] = { "virtual", "override", "action", "static" };
int fc = 0;
FString build;
for (int i = 0; i < 4; i++)
{
Error(f, "%s: Action and Static on the same function is not allowed.", FName(f->Name).GetChars());
if (f->Flags & exclude[i])
{
fc++;
if (build.Len() > 0) build += ", ";
build += print[i];
}
}
if (fc > 1)
{
Error(f, "Invalid combination of qualifiers %s on function %s.", FName(f->Name).GetChars(), build.GetChars() );
varflags |= VARF_Method;
}
if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well.
if (f->Flags & ZCC_Native)
{
@ -2193,7 +2223,41 @@ void ZCCCompiler::InitFunctions()
{
sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults);
}
// todo: Check inheritance.
if (varflags & VARF_Virtual)
{
if (varflags & VARF_Final)
{
sym->Variants[0].Implementation->Final = true;
}
int vindex = c->Type()->FindVirtualIndex(sym->SymbolName, sym->Variants[0].Proto);
// specifying 'override' is necessary to prevent one of the biggest problem spots with virtual inheritance: Mismatching argument types.
if (varflags & VARF_Override)
{
if (vindex == -1)
{
Error(p, "Attempt to override non-existent virtual function %s", FName(f->Name).GetChars());
}
else
{
auto oldfunc = c->Type()->Virtuals[vindex];
if (oldfunc->Final)
{
Error(p, "Attempt to override final function %s", FName(f->Name).GetChars());
}
c->Type()->Virtuals[vindex] = sym->Variants[0].Implementation;
sym->Variants[0].Implementation->VirtualIndex = vindex;
}
}
else
{
if (vindex != -1)
{
Error(p, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars());
}
sym->Variants[0].Implementation->VirtualIndex = c->Type()->Virtuals.Push(sym->Variants[0].Implementation);
}
}
}
}
}

View file

@ -123,6 +123,8 @@ static void InitTokenMap()
TOKENDEF (TK_Private, ZCC_PRIVATE);
TOKENDEF (TK_Protected, ZCC_PROTECTED);
TOKENDEF (TK_Latent, ZCC_LATENT);
TOKENDEF (TK_Virtual, ZCC_VIRTUAL);
TOKENDEF (TK_Override, ZCC_OVERRIDE);
TOKENDEF (TK_Final, ZCC_FINAL);
TOKENDEF (TK_Meta, ZCC_META);
TOKENDEF (TK_Deprecated, ZCC_DEPRECATED);

View file

@ -33,6 +33,8 @@ enum
ZCC_FuncConst = 1 << 10,
ZCC_Abstract = 1 << 11,
ZCC_Extension = 1 << 12,
ZCC_Virtual = 1 << 13,
ZCC_Override = 1 << 14,
};
// Function parameter modifiers