Skip to content

Commit

Permalink
Finish implementing function pointers
Browse files Browse the repository at this point in the history
Allow using function literals with function pointers, allow narrowing classes parameter types, and widening classes in return types, allow pointers to non-static, non-virtual functions, add null checking to function calls
  • Loading branch information
RicardoLuis0 committed Oct 11, 2023
1 parent 5590a22 commit 8b5f0c7
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 20 deletions.
229 changes: 209 additions & 20 deletions src/common/scripting/backend/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ PFunction *FindBuiltinFunction(FName funcname)
//
//==========================================================================

static bool AreCompatibleFnPtrTypes(PPrototype *to, PPrototype *from);

bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare)
{
if (dest->isPointer() && source->isPointer())
Expand Down Expand Up @@ -301,8 +303,9 @@ bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare)
{
auto from = static_cast<PFunctionPointer*>(source);
auto to = static_cast<PFunctionPointer*>(dest);
return to->PointedType == TypeVoid || (from->PointedType == to->PointedType && from->ArgFlags == to->ArgFlags && FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope));
// TODO allow narrowing argument types and widening return types via cast, ex.: Function<Actor(Object or Class<Object>)> to Function<Object(Actor or Class<Actor>)>
if(from->PointedType == TypeVoid) return false;

return to->PointedType == TypeVoid || (AreCompatibleFnPtrTypes((PPrototype *)to->PointedType, (PPrototype *)from->PointedType) && from->ArgFlags == to->ArgFlags && FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope));
}
}
return false;
Expand Down Expand Up @@ -1615,6 +1618,35 @@ FxTypeCast::~FxTypeCast()
//
//==========================================================================

FxConstant * FxTypeCast::convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition)
{
assert(in->isConstant() && in->ValueType == TypeRawFunction);
FxConstant *val = static_cast<FxConstant*>(in);
PFunction * fn = static_cast<PFunction*>(val->value.pointer);
if(fn && (fn->Variants[0].Flags & (VARF_Virtual | VARF_Action | VARF_Method)) == 0)
{
val->ValueType = val->value.Type = NewFunctionPointer(fn->Variants[0].Proto, TArray<uint32_t>(fn->Variants[0].ArgFlags), FScopeBarrier::SideFromFlags(fn->Variants[0].Flags));
return val;
}
else if(fn && (fn->Variants[0].Flags & (VARF_Virtual | VARF_Action | VARF_Method)) == VARF_Method)
{
TArray<uint32_t> flags(fn->Variants[0].ArgFlags);
flags[0] = 0;
val->ValueType = val->value.Type = NewFunctionPointer(fn->Variants[0].Proto, std::move(flags), FScopeBarrier::SideFromFlags(fn->Variants[0].Flags));
return val;
}
else if(!fn)
{
val->ValueType = val->value.Type = NewFunctionPointer(nullptr, {}, -1); // Function<void>
return val;
}
else
{
ScriptPosition.Message(MSG_ERROR, "virtual/action function pointers are not allowed");
return nullptr;
}
}

FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
Expand All @@ -1626,6 +1658,22 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
if (result != this) return result;
}

if (basex->isConstant() && basex->ValueType == TypeRawFunction && ValueType->isFunctionPointer())
{
FxConstant *val = convertRawFunctionToFunctionPointer(basex, ScriptPosition);
if(!val)
{
delete this;
return nullptr;
}
}
else if (basex->isConstant() && basex->ValueType == TypeRawFunction && ValueType == TypeVMFunction)
{
FxConstant *val = static_cast<FxConstant*>(basex);
val->ValueType = val->value.Type = TypeVMFunction;
val->value.pointer = static_cast<PFunction*>(val->value.pointer)->Variants[0].Implementation;
}

// first deal with the simple types
if (ValueType == TypeError || basex->ValueType == TypeError || basex->ValueType == nullptr)
{
Expand Down Expand Up @@ -6370,9 +6418,8 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
if (ctx.Version >= MakeVersion(4, 11, 100))
{
// VMFunction is only supported since 4.12 and Raze 1.8.
newex = new FxConstant(static_cast<PFunction*>(sym)->Variants[0].Implementation, ScriptPosition);
newex = new FxConstant(static_cast<PFunction*>(sym), ScriptPosition);
goto foundit;

}
}
}
Expand All @@ -6391,7 +6438,7 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
if (ctx.Version >= MakeVersion(4, 11, 100))
{
// VMFunction is only supported since 4.12 and Raze 1.8.
newex = new FxConstant(static_cast<PFunction*>(sym)->Variants[0].Implementation, ScriptPosition);
newex = new FxConstant(static_cast<PFunction*>(sym), ScriptPosition);
goto foundit;
}
}
Expand Down Expand Up @@ -6539,7 +6586,6 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType *
if (result != this) return result;
}


if (objtype != nullptr && (sym = objtype->Symbols.FindSymbolInTable(Identifier, symtbl)) != nullptr)
{
if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConst)))
Expand Down Expand Up @@ -6755,7 +6801,7 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx)

SAFE_RESOLVE(Object, ctx);

// check for class or struct constants if the left side is a type name.
// check for class or struct constants/functions if the left side is a type name.
if (Object->ValueType == TypeError)
{
if (ccls != nullptr)
Expand All @@ -6769,6 +6815,16 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx)
delete this;
return FxConstant::MakeConstant(sym, ScriptPosition);
}
else if(sym->IsKindOf(RUNTIME_CLASS(PFunction)))
{
if (ctx.Version >= MakeVersion(4, 11, 100))
{
// VMFunction is only supported since 4.12 and Raze 1.8.
auto x = new FxConstant(static_cast<PFunction*>(sym), ScriptPosition);
delete this;
return x->Resolve(ctx);
}
}
else
{
auto f = dyn_cast<PField>(sym);
Expand Down Expand Up @@ -9604,6 +9660,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
assert(Self != nullptr);
selfemit = Self->Emit(build);
assert(selfemit.RegType == REGT_POINTER);
build->Emit(OP_NULLCHECK, selfemit.RegNum, 0, 0);
staticcall = false;
}
else staticcall = true;
Expand Down Expand Up @@ -11704,11 +11761,118 @@ FxFunctionPtrCast::~FxFunctionPtrCast()
//
//==========================================================================

static bool AreCompatibleFnPtrs(PFunctionPointer * to, PFunctionPointer * from);

bool CanNarrowTo(PClass * from, PClass * to)
{
return from->IsAncestorOf(to);
}

bool CanWidenTo(PClass * from, PClass * to)
{
return to->IsAncestorOf(from);
}

static bool AreCompatibleFnPtrTypes(PPrototype *to, PPrototype *from)
{
if(to->ArgumentTypes.Size() != from->ArgumentTypes.Size()
|| to->ReturnTypes.Size() != from->ReturnTypes.Size()) return false;
int n = to->ArgumentTypes.Size();

//allow narrowing of arguments
for(int i = 0; i < n; i++)
{
PType * fromType = from->ArgumentTypes[i];
PType * toType = to->ArgumentTypes[i];
if(fromType->isFunctionPointer() && toType->isFunctionPointer())
{
if(!AreCompatibleFnPtrs(static_cast<PFunctionPointer *>(toType), static_cast<PFunctionPointer *>(fromType))) return false;
}
else if(fromType->isClassPointer() && toType->isClassPointer())
{
PClassPointer * fromClass = static_cast<PClassPointer *>(fromType);
PClassPointer * toClass = static_cast<PClassPointer *>(toType);
//allow narrowing parameters
if(!CanNarrowTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false;
}
else if(fromType->isObjectPointer() && toType->isObjectPointer())
{
PObjectPointer * fromObj = static_cast<PObjectPointer *>(fromType);
PObjectPointer * toObj = static_cast<PObjectPointer *>(toType);
//allow narrowing parameters
if(!CanNarrowTo(fromObj->PointedClass(), toObj->PointedClass())) return false;
}
else if(fromType != toType)
{
return false;
}
}

n = to->ReturnTypes.Size();

for(int i = 0; i < n; i++)
{
PType * fromType = from->ReturnTypes[i];
PType * toType = to->ReturnTypes[i];
if(fromType->isFunctionPointer() && toType->isFunctionPointer())
{
if(!AreCompatibleFnPtrs(static_cast<PFunctionPointer *>(toType), static_cast<PFunctionPointer *>(fromType))) return false;
}
else if(fromType->isClassPointer() && toType->isClassPointer())
{
PClassPointer * fromClass = static_cast<PClassPointer *>(fromType);
PClassPointer * toClass = static_cast<PClassPointer *>(toType);
//allow widening returns
if(!CanWidenTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false;
}
else if(fromType->isObjectPointer() && toType->isObjectPointer())
{
PObjectPointer * fromObj = static_cast<PObjectPointer *>(fromType);
PObjectPointer * toObj = static_cast<PObjectPointer *>(toType);
//allow widening returns
if(!CanWidenTo(fromObj->PointedClass(), toObj->PointedClass())) return false;
}
else if(fromType != toType)
{
return false;
}
}
return true;
}

static bool AreCompatibleFnPtrs(PFunctionPointer * to, PFunctionPointer * from)
{
if(to->PointedType == TypeVoid) return true;
else if(from->PointedType == TypeVoid) return false;

PPrototype * toProto = (PPrototype *)to->PointedType;
PPrototype * fromProto = (PPrototype *)from->PointedType;
return
( FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope)
/*
&& toProto->ArgumentTypes == fromProto->ArgumentTypes
&& toProto->ReturnTypes == fromProto->ReturnTypes
*/
&& AreCompatibleFnPtrTypes(toProto, fromProto)
&& to->ArgFlags == from->ArgFlags
);
}

FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(basex, ctx);

if (basex->isConstant() && basex->ValueType == TypeRawFunction)
{
FxConstant *val = FxTypeCast::convertRawFunctionToFunctionPointer(basex, ScriptPosition);
if(!val)
{
delete this;
return nullptr;
}
}

if (!(basex->ValueType && basex->ValueType->isFunctionPointer()))
{
delete this;
Expand All @@ -11717,21 +11881,20 @@ FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx)
auto to = static_cast<PFunctionPointer *>(ValueType);
auto from = static_cast<PFunctionPointer *>(basex->ValueType);

if(to->PointedType == TypeVoid)
{ // no need to do anything for (Function<void)(...) casts
if(from->PointedType == TypeVoid)
{ // nothing to check at compile-time for casts from Function<void>
return this;
}
else if(AreCompatibleFnPtrs(to, from))
{ // no need to do anything for (Function<void>)(...) or compatible casts
basex->ValueType = ValueType;
auto x = basex;
basex = nullptr;
delete this;
return x;
}
else if(from->PointedType == TypeVoid)
{ // nothing to check at compile-time for casts from Function<void>
return this;
}
else
{
// TODO allow narrowing argument types and widening return types via cast, ex.: Function<Actor(Object or Class<Object>)> to Function<Object(Actor or Class<Actor>)>
ScriptPosition.Message(MSG_ERROR, "Cannot cast %s to %s. The types are incompatible.", basex->ValueType->DescriptiveName(), to->DescriptiveName());
delete this;
return nullptr;
Expand All @@ -11746,12 +11909,27 @@ FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx)

PFunction *NativeFunctionPointerCast(PFunction *from, const PFunctionPointer *to)
{
// TODO allow narrowing argument types and widening return types via cast, ex.: Function<Actor(Object or Class<Object>)> to Function<Object(Actor or Class<Actor>)>
return (to->PointedType == TypeVoid || (from &&
( from->Variants[0].Proto == static_cast<PPrototype*>(to->PointedType)
&& from->Variants[0].ArgFlags == to->ArgFlags
&& FScopeBarrier::CheckSidesForFunctionPointer(FScopeBarrier::SideFromFlags(from->Variants[0].Flags), to->Scope)
))) ? from : nullptr;
if(to->PointedType == TypeVoid)
{
return from;
}
else if(from && ((from->Variants[0].Flags & (VARF_Virtual | VARF_Action)) == 0) && FScopeBarrier::CheckSidesForFunctionPointer(FScopeBarrier::SideFromFlags(from->Variants[0].Flags), to->Scope))
{
if(to->ArgFlags.Size() != from->Variants[0].ArgFlags.Size()) return nullptr;
int n = to->ArgFlags.Size();
for(int i = from->GetImplicitArgs(); i < n; i++) // skip checking flags for implicit self
{
if(from->Variants[0].ArgFlags[i] != to->ArgFlags[i])
{
return nullptr;
}
}
return AreCompatibleFnPtrTypes(static_cast<PPrototype*>(to->PointedType), from->Variants[0].Proto) ? from : nullptr;
}
else
{ // cannot cast virtual/action functions to anything
return nullptr;
}
}

DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinFunctionPtrCast, NativeFunctionPointerCast)
Expand Down Expand Up @@ -11841,6 +12019,17 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx)
return nullptr;
}
SAFE_RESOLVE(Init, ctx);

if(Init->isConstant() && Init->ValueType == TypeRawFunction)
{
FxConstant *val = FxTypeCast::convertRawFunctionToFunctionPointer(Init, ScriptPosition);
if(!val)
{
delete this;
return nullptr;
}
}

ValueType = Init->ValueType;
if (ValueType->RegType == REGT_NIL)
{
Expand Down
11 changes: 11 additions & 0 deletions src/common/scripting/backend/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,13 @@ class FxConstant : public FxExpression
ValueType = value.Type = TypeVMFunction;
isresolved = true;
}

FxConstant(PFunction* rawptr, const FScriptPosition& pos) : FxExpression(EFX_Constant, pos)
{
value.pointer = rawptr;
ValueType = value.Type = TypeRawFunction;
isresolved = true;
}

FxConstant(const FScriptPosition &pos) : FxExpression(EFX_Constant, pos)
{
Expand Down Expand Up @@ -558,6 +565,8 @@ class FxConstant : public FxExpression
return value;
}
ExpEmit Emit(VMFunctionBuilder *build);

friend class FxTypeCast;
};

//==========================================================================
Expand Down Expand Up @@ -736,6 +745,8 @@ class FxTypeCast : public FxExpression
FxExpression *Resolve(FCompileContext&);

ExpEmit Emit(VMFunctionBuilder *build);

static FxConstant * convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition);
};

//==========================================================================
Expand Down
3 changes: 3 additions & 0 deletions src/common/scripting/core/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ PStruct *TypeStringStruct;
PStruct* TypeQuaternionStruct;
PPointer *TypeNullPtr;
PPointer *TypeVoidPtr;
PPointer *TypeRawFunction;
PPointer* TypeVMFunction;


Expand Down Expand Up @@ -323,6 +324,8 @@ void PType::StaticInit()
TypeTable.AddType(TypeTextureID = new PTextureID, NAME_TextureID);

TypeVoidPtr = NewPointer(TypeVoid, false);
TypeRawFunction = new PPointer;
TypeRawFunction->mDescriptiveName = "Raw Function Pointer";
TypeVMFunction = NewPointer(NewStruct("VMFunction", nullptr, true));
TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value.
TypeStringStruct = NewStruct("Stringstruct", nullptr, true);
Expand Down
1 change: 1 addition & 0 deletions src/common/scripting/core/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ extern PPointer *TypeFont;
extern PStateLabel *TypeStateLabel;
extern PPointer *TypeNullPtr;
extern PPointer *TypeVoidPtr;
extern PPointer* TypeRawFunction;
extern PPointer* TypeVMFunction;


Expand Down
Loading

0 comments on commit 8b5f0c7

Please sign in to comment.