diff --git a/source/common/engine/namedef.h b/source/common/engine/namedef.h index 948228cb91..70080a9651 100644 --- a/source/common/engine/namedef.h +++ b/source/common/engine/namedef.h @@ -120,6 +120,7 @@ xx(Vector) xx(Map) xx(MapIterator) xx(Array) +xx(Function) xx(Include) xx(Sound) xx(State) @@ -190,6 +191,7 @@ xx(SetNull) xx(Key) xx(Index) xx(Find) +xx(Call) // color channels xx(a) @@ -269,6 +271,7 @@ xx(BuiltinRandom2) xx(BuiltinFRandom) xx(BuiltinNameToClass) xx(BuiltinClassCast) +xx(BuiltinFunctionPtrCast) xx(ScreenJobRunner) xx(Action) diff --git a/source/common/engine/sc_man_scanner.re b/source/common/engine/sc_man_scanner.re index dfc3f050f0..8bca2ccc65 100644 --- a/source/common/engine/sc_man_scanner.re +++ b/source/common/engine/sc_man_scanner.re @@ -169,6 +169,7 @@ std2: 'map' { RET(TK_Map); } 'mapiterator' { RET(TK_MapIterator); } 'array' { RET(TK_Array); } + 'function' { RET(ParseVersion >= MakeVersion(4, 12, 0)? TK_FunctionType : TK_Identifier); } 'in' { RET(TK_In); } 'sizeof' { RET(TK_SizeOf); } 'alignof' { RET(TK_AlignOf); } diff --git a/source/common/engine/sc_man_tokens.h b/source/common/engine/sc_man_tokens.h index 8fb2f88e3e..4870031251 100644 --- a/source/common/engine/sc_man_tokens.h +++ b/source/common/engine/sc_man_tokens.h @@ -131,6 +131,7 @@ xx(TK_Vector3, "'vector3'") xx(TK_Map, "'map'") xx(TK_MapIterator, "'mapiterator'") xx(TK_Array, "'array'") +xx(TK_FunctionType, "'function'") xx(TK_In, "'in'") xx(TK_SizeOf, "'sizeof'") xx(TK_AlignOf, "'alignof'") diff --git a/source/common/engine/serializer.cpp b/source/common/engine/serializer.cpp index bb54c0d2be..34fd6956b5 100644 --- a/source/common/engine/serializer.cpp +++ b/source/common/engine/serializer.cpp @@ -1656,6 +1656,84 @@ FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &value, N return arc; } +//========================================================================== +// +// PFunctionPointer +// +//========================================================================== + +void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p) +{ + if (arc.isWriting()) + { + if(p) + { + arc.BeginObject(key); + arc("Class",p->ClassName); + arc("Function",p->FunctionName); + arc.EndObject(); + } + else + { + arc.WriteKey(key); + arc.w->Null(); + } + } + else + { + assert(p); + auto v = arc.r->FindKey(key); + if(!v || v->IsNull()) + { + p = nullptr; + } + else if(v->IsObject()) + { + arc.r->mObjects.Push(FJSONObject(v)); // BeginObject + + const char * cstr; + arc.StringPtr("Class", cstr); + + if(!cstr) + { + arc.StringPtr("Function", cstr); + if(!cstr) + { + Printf(TEXTCOLOR_RED "Function Pointer missing Class and Function Fields in Object\n"); + } + else + { + Printf(TEXTCOLOR_RED "Function Pointer missing Class Field in Object\n"); + } + arc.mErrors++; + arc.EndObject(); + p = nullptr; + return; + } + + p->ClassName = FString(cstr); + arc.StringPtr("Function", cstr); + + if(!cstr) + { + Printf(TEXTCOLOR_RED "Function Pointer missing Function Field in Object\n"); + arc.mErrors++; + arc.EndObject(); + p = nullptr; + return; + } + p->FunctionName = FString(cstr); + arc.EndObject(); + } + else + { + Printf(TEXTCOLOR_RED "Function Pointer is not an Object\n"); + arc.mErrors++; + p = nullptr; + } + } +} + #include "renderstyle.h" FSerializer& Serialize(FSerializer& arc, const char* key, FRenderStyle& style, FRenderStyle* def) { diff --git a/source/common/engine/serializer.h b/source/common/engine/serializer.h index a6f74c0371..9328f2f223 100644 --- a/source/common/engine/serializer.h +++ b/source/common/engine/serializer.h @@ -54,6 +54,12 @@ struct NumericValue } }; +struct FunctionPointerValue +{ + FString ClassName; + FString FunctionName; +}; + class FSerializer { @@ -235,6 +241,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &sid, struct ModelOverride *def); +void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p); + template >*/> FSerializer &Serialize(FSerializer &arc, const char *key, T *&value, T **) { diff --git a/source/common/models/modelrenderer.h b/source/common/models/modelrenderer.h index 18c49f641a..d921e7100f 100644 --- a/source/common/models/modelrenderer.h +++ b/source/common/models/modelrenderer.h @@ -17,8 +17,8 @@ class FModelRenderer virtual VSMatrix GetViewToWorldMatrix() = 0; - virtual void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0; - virtual void EndDrawHUDModel(FRenderStyle style) = 0; + virtual void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored, FSpriteModelFrame *smf) = 0; + virtual void EndDrawHUDModel(FRenderStyle style, FSpriteModelFrame *smf) = 0; virtual void SetInterpolation(double interpolation) = 0; virtual void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) = 0; diff --git a/source/common/objects/dobject.h b/source/common/objects/dobject.h index a7d857dc6f..aed2230716 100644 --- a/source/common/objects/dobject.h +++ b/source/common/objects/dobject.h @@ -243,6 +243,7 @@ class DObject inline DAngle &AngleVar(FName field); inline FString &StringVar(FName field); template T*& PointerVar(FName field); + inline int* IntArray(FName field); // This is only needed for swapping out PlayerPawns and absolutely nothing else! virtual size_t PointerSubstitution (DObject *old, DObject *notOld); @@ -433,6 +434,11 @@ inline int &DObject::IntVar(FName field) return *(int*)ScriptVar(field, nullptr); } +inline int* DObject::IntArray(FName field) +{ + return (int*)ScriptVar(field, nullptr); +} + inline FTextureID &DObject::TextureIDVar(FName field) { return *(FTextureID*)ScriptVar(field, nullptr); diff --git a/source/common/objects/dobjgc.h b/source/common/objects/dobjgc.h index a844a16db4..25e27bfb3b 100644 --- a/source/common/objects/dobjgc.h +++ b/source/common/objects/dobjgc.h @@ -282,6 +282,11 @@ class TObjPtr return GC::ReadBarrier(o) == u; } + constexpr bool operator==(TObjPtr u) noexcept + { + return ForceGet() == u.ForceGet(); + } + template friend inline void GC::Mark(TObjPtr &obj); template friend FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, TObjPtr *); template friend FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, U *); diff --git a/source/common/rendering/gles/gles_system.cpp b/source/common/rendering/gles/gles_system.cpp index bb7d61687e..d9bedafdd7 100644 --- a/source/common/rendering/gles/gles_system.cpp +++ b/source/common/rendering/gles/gles_system.cpp @@ -158,7 +158,7 @@ namespace OpenGLESRenderer Printf(PRINT_LOG, "GL_EXTENSIONS:\n"); for (unsigned i = 0; i < m_Extensions.Size(); i++) { - Printf(" %s\n", m_Extensions[i].GetChars()); + Printf(PRINT_LOG, " %s\n", m_Extensions[i].GetChars()); } const char* glVersionStr = (const char*)glGetString(GL_VERSION); double glVersion = strtod(glVersionStr, NULL); diff --git a/source/common/scripting/backend/codegen.cpp b/source/common/scripting/backend/codegen.cpp index 1e30148c7c..3ff45c3353 100644 --- a/source/common/scripting/backend/codegen.cpp +++ b/source/common/scripting/backend/codegen.cpp @@ -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()) @@ -297,6 +299,14 @@ bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare) if (forcompare && tocls->IsDescendantOf(fromcls)) return true; return (fromcls->IsDescendantOf(tocls)); } + if(source->isFunctionPointer() && dest->isFunctionPointer()) + { + auto from = static_cast(source); + auto to = static_cast(dest); + 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; } @@ -906,6 +916,17 @@ FxExpression *FxBoolCast::Resolve(FCompileContext &ctx) ExpEmit FxBoolCast::Emit(VMFunctionBuilder *build) { ExpEmit from = basex->Emit(build); + + if(from.Konst && from.RegType == REGT_INT) + { // this is needed here because the int const assign optimization returns a constant + ExpEmit to; + to.Konst = true; + to.RegType = REGT_INT; + to.RegNum = build->GetConstantInt(!!build->FindConstantInt(from.RegNum)); + return to; + } + + assert(!from.Konst); assert(basex->ValueType->GetRegType() == REGT_INT || basex->ValueType->GetRegType() == REGT_FLOAT || basex->ValueType->GetRegType() == REGT_POINTER); @@ -1130,7 +1151,15 @@ FxExpression *FxFloatCast::Resolve(FCompileContext &ctx) ExpEmit FxFloatCast::Emit(VMFunctionBuilder *build) { ExpEmit from = basex->Emit(build); - //assert(!from.Konst); + if(from.Konst && from.RegType == REGT_INT) + { // this is needed here because the int const assign optimization returns a constant + ExpEmit to; + to.Konst = true; + to.RegType = REGT_FLOAT; + to.RegNum = build->GetConstantFloat(build->FindConstantInt(from.RegNum)); + return to; + } + assert(basex->ValueType->GetRegType() == REGT_INT); from.Free(build); ExpEmit to(build, REGT_FLOAT); @@ -1608,6 +1637,35 @@ FxTypeCast::~FxTypeCast() // //========================================================================== +FxConstant * FxTypeCast::convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition) +{ + assert(in->isConstant() && in->ValueType == TypeRawFunction); + FxConstant *val = static_cast(in); + PFunction * fn = static_cast(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(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 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 + return val; + } + else + { + ScriptPosition.Message(MSG_ERROR, "virtual/action function pointers are not allowed"); + return nullptr; + } +} + FxExpression *FxTypeCast::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); @@ -1619,6 +1677,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(basex); + val->ValueType = val->value.Type = TypeVMFunction; + val->value.pointer = static_cast(val->value.pointer)->Variants[0].Implementation; + } + // first deal with the simple types if (ValueType == TypeError || basex->ValueType == TypeError || basex->ValueType == nullptr) { @@ -1918,7 +1992,17 @@ ExpEmit FxMinusSign::Emit(VMFunctionBuilder *build) { //assert(ValueType == Operand->ValueType); ExpEmit from = Operand->Emit(build); + if(from.Konst && from.RegType == REGT_INT) + { // this is needed here because the int const assign optimization returns a constant + ExpEmit to; + to.Konst = true; + to.RegType = REGT_INT; + to.RegNum = build->GetConstantInt(-build->FindConstantInt(from.RegNum)); + return to; + } + ExpEmit to; + assert(from.Konst == 0); assert(ValueType->GetRegCount() == from.RegCount); // Do it in-place, unless a local variable @@ -2038,6 +2122,16 @@ ExpEmit FxUnaryNotBitwise::Emit(VMFunctionBuilder *build) { assert(Operand->ValueType->GetRegType() == REGT_INT); ExpEmit from = Operand->Emit(build); + + if(from.Konst && from.RegType == REGT_INT) + { // this is needed here because the int const assign optimization returns a constant + ExpEmit to; + to.Konst = true; + to.RegType = REGT_INT; + to.RegNum = build->GetConstantInt(~build->FindConstantInt(from.RegNum)); + return to; + } + from.Free(build); ExpEmit to(build, REGT_INT); assert(!from.Konst); @@ -2109,6 +2203,16 @@ ExpEmit FxUnaryNotBoolean::Emit(VMFunctionBuilder *build) assert(Operand->ValueType == TypeBool); assert(ValueType == TypeBool || IsInteger()); // this may have been changed by an int cast. ExpEmit from = Operand->Emit(build); + + if(from.Konst && from.RegType == REGT_INT) + { // this is needed here because the int const assign optimization returns a constant + ExpEmit to; + to.Konst = true; + to.RegType = REGT_INT; + to.RegNum = build->GetConstantInt(!build->FindConstantInt(from.RegNum)); + return to; + } + from.Free(build); ExpEmit to(build, REGT_INT); assert(!from.Konst); @@ -6363,9 +6467,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(sym)->Variants[0].Implementation, ScriptPosition); + newex = new FxConstant(static_cast(sym), ScriptPosition); goto foundit; - } } } @@ -6384,7 +6487,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(sym)->Variants[0].Implementation, ScriptPosition); + newex = new FxConstant(static_cast(sym), ScriptPosition); goto foundit; } } @@ -6532,7 +6635,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))) @@ -6748,7 +6850,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) @@ -6762,6 +6864,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(sym), ScriptPosition); + delete this; + return x->Resolve(ctx); + } + } else { auto f = dyn_cast(sym); @@ -8379,6 +8491,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) PContainerType *cls = nullptr; bool staticonly = false; bool novirtual = false; + bool fnptr = false; bool isreadonly = false; PContainerType *ccls = nullptr; @@ -8996,6 +9109,22 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) cls = static_cast(Self->ValueType); Self->ValueType = NewPointer(Self->ValueType); } + else if (Self->ValueType->isFunctionPointer()) + { + auto fn = static_cast(Self->ValueType); + + if(MethodName == NAME_Call && fn->PointedType != TypeVoid) + { // calling a Function pointer isn't allowed + fnptr = true; + afd_override = fn->FakeFunction; + } + else + { + ScriptPosition.Message(MSG_ERROR, "Unknown function %s", MethodName.GetChars()); + delete this; + return nullptr; + } + } else { ScriptPosition.Message(MSG_ERROR, "Invalid expression on left hand side of %s", MethodName.GetChars()); @@ -9115,8 +9244,8 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) } // do not pass the self pointer to static functions. - auto self = (afd->Variants[0].Flags & VARF_Method) ? Self : nullptr; - auto x = new FxVMFunctionCall(self, afd, ArgList, ScriptPosition, staticonly|novirtual); + auto self = ( fnptr || (afd->Variants[0].Flags & VARF_Method)) ? Self : nullptr; + auto x = new FxVMFunctionCall(self, afd, ArgList, ScriptPosition, (staticonly || novirtual) && !fnptr); if (Self == self) Self = nullptr; delete this; return x->Resolve(ctx); @@ -9130,7 +9259,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) //========================================================================== FxVMFunctionCall::FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual) -: FxExpression(EFX_VMFunctionCall, pos) +: FxExpression(EFX_VMFunctionCall, pos) , FnPtrCall((self && self->ValueType) ? self->ValueType->isFunctionPointer() : false) { Self = self; Function = func; @@ -9204,7 +9333,7 @@ VMFunction *FxVMFunctionCall::GetDirectFunction(PFunction *callingfunc, const Ve // definition can call that function directly without wrapping // it inside VM code. - if (ArgList.Size() == 0 && !(Function->Variants[0].Flags & VARF_Virtual) && CheckAccessibility(ver) && CheckFunctionCompatiblity(ScriptPosition, callingfunc, Function)) + if (ArgList.Size() == 0 && !(Function->Variants[0].Flags & VARF_Virtual) && !FnPtrCall && CheckAccessibility(ver) && CheckFunctionCompatiblity(ScriptPosition, callingfunc, Function)) { unsigned imp = Function->GetImplicitArgs(); if (Function->Variants[0].ArgFlags.Size() > imp && !(Function->Variants[0].ArgFlags[imp] & VARF_Optional)) return nullptr; @@ -9229,7 +9358,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) auto &argtypes = proto->ArgumentTypes; auto &argnames = Function->Variants[0].ArgNames; auto &argflags = Function->Variants[0].ArgFlags; - auto &defaults = Function->Variants[0].Implementation->DefaultArgs; + auto *defaults = FnPtrCall ? nullptr : &Function->Variants[0].Implementation->DefaultArgs; int implicit = Function->GetImplicitArgs(); @@ -9297,6 +9426,12 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) if (ArgList[i]->ExprType == EFX_NamedNode) { + if(FnPtrCall) + { + ScriptPosition.Message(MSG_ERROR, "Named arguments not supported in function pointer calls"); + delete this; + return nullptr; + } if (!(flag & VARF_Optional)) { ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed."); @@ -9346,7 +9481,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) } if (ntype->GetRegCount() == 1) { - auto x = new FxConstant(ntype, defaults[i + k + skipdefs + implicit], ScriptPosition); + auto x = new FxConstant(ntype, (*defaults)[i + k + skipdefs + implicit], ScriptPosition); ArgList.Insert(i + k, x); } else @@ -9355,7 +9490,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) FxConstant *cs[4] = { nullptr }; for (int l = 0; l < ntype->GetRegCount(); l++) { - cs[l] = new FxConstant(TypeFloat64, defaults[l + i + k + skipdefs + implicit], ScriptPosition); + cs[l] = new FxConstant(TypeFloat64, (*defaults)[l + i + k + skipdefs + implicit], ScriptPosition); } FxExpression *x = new FxVectorValue(cs[0], cs[1], cs[2], cs[3], ScriptPosition); ArgList.Insert(i + k, x); @@ -9492,6 +9627,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) { ValueType = TypeVoid; } + return this; } @@ -9517,11 +9653,14 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) } } - VMFunction *vmfunc = Function->Variants[0].Implementation; - bool staticcall = ((vmfunc->VarFlags & VARF_Final) || vmfunc->VirtualIndex == ~0u || NoVirtual); + VMFunction *vmfunc = FnPtrCall ? nullptr : Function->Variants[0].Implementation; + bool staticcall = (FnPtrCall || (vmfunc->VarFlags & VARF_Final) || vmfunc->VirtualIndex == ~0u || NoVirtual); count = 0; - FunctionCallEmitter emitters(vmfunc); + + assert(!FnPtrCall || (FnPtrCall && Self && Self->ValueType && Self->ValueType->isFunctionPointer())); + + FunctionCallEmitter emitters(FnPtrCall ? FunctionCallEmitter(PType::toFunctionPointer(Self->ValueType)) : FunctionCallEmitter(vmfunc)); // Emit code to pass implied parameters ExpEmit selfemit; if (Function->Variants[0].Flags & VARF_Method) @@ -9565,43 +9704,57 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) } } } + else if (FnPtrCall) + { + 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; // Emit code to pass explicit parameters for (unsigned i = 0; i < ArgList.Size(); ++i) { emitters.AddParameter(build, ArgList[i]); } - // Complete the parameter list from the defaults. - auto &defaults = Function->Variants[0].Implementation->DefaultArgs; - for (unsigned i = emitters.Count(); i < defaults.Size(); i++) + if(!FnPtrCall) { - switch (defaults[i].Type) + // Complete the parameter list from the defaults. + auto &defaults = Function->Variants[0].Implementation->DefaultArgs; + for (unsigned i = emitters.Count(); i < defaults.Size(); i++) { - default: - case REGT_INT: - emitters.AddParameterIntConst(defaults[i].i); - break; - case REGT_FLOAT: - emitters.AddParameterFloatConst(defaults[i].f); - break; - case REGT_POINTER: - emitters.AddParameterPointerConst(defaults[i].a); - break; - case REGT_STRING: - emitters.AddParameterStringConst(defaults[i].s()); - break; + switch (defaults[i].Type) + { + default: + case REGT_INT: + emitters.AddParameterIntConst(defaults[i].i); + break; + case REGT_FLOAT: + emitters.AddParameterFloatConst(defaults[i].f); + break; + case REGT_POINTER: + emitters.AddParameterPointerConst(defaults[i].a); + break; + case REGT_STRING: + emitters.AddParameterStringConst(defaults[i].s()); + break; + } } } ArgList.DeleteAndClear(); ArgList.ShrinkToFit(); if (!staticcall) emitters.SetVirtualReg(selfemit.RegNum); - int resultcount = vmfunc->Proto->ReturnTypes.Size() == 0 ? 0 : max(AssignCount, 1); - assert((unsigned)resultcount <= vmfunc->Proto->ReturnTypes.Size()); + PPrototype * proto = FnPtrCall ? static_cast(static_cast(Self->ValueType)->PointedType) : vmfunc->Proto; + + int resultcount = proto->ReturnTypes.Size() == 0 ? 0 : max(AssignCount, 1); + + assert((unsigned)resultcount <= proto->ReturnTypes.Size()); for (int i = 0; i < resultcount; i++) { - emitters.AddReturn(vmfunc->Proto->ReturnTypes[i]->GetRegType(), vmfunc->Proto->ReturnTypes[i]->GetRegCount()); + emitters.AddReturn(proto->ReturnTypes[i]->GetRegType(), proto->ReturnTypes[i]->GetRegCount()); } return emitters.EmitCall(build, resultcount > 1? &ReturnRegs : nullptr); } @@ -11226,6 +11379,10 @@ FxExpression *FxReturnStatement::Resolve(FCompileContext &ctx) { mismatchSeverity = MSG_ERROR; } + else if (protoRetCount > retCount) + { // also warn when returning less values then the return count + mismatchSeverity = ctx.Version >= MakeVersion(4, 12) ? MSG_ERROR : MSG_WARNING; + } } if (mismatchSeverity != -1) @@ -11629,6 +11786,228 @@ ExpEmit FxClassPtrCast::Emit(VMFunctionBuilder *build) return emitters.EmitCall(build); } +//========================================================================== +// +//========================================================================== + +FxFunctionPtrCast::FxFunctionPtrCast(PFunctionPointer *ftype, FxExpression *x) + : FxExpression(EFX_FunctionPtrCast, x->ScriptPosition) +{ + ValueType = ftype; + basex = x; +} + +//========================================================================== +// +// +// +//========================================================================== + +FxFunctionPtrCast::~FxFunctionPtrCast() +{ + SAFE_DELETE(basex); +} + +//========================================================================== +// +// +// +//========================================================================== + +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(toType), static_cast(fromType))) return false; + } + else if(fromType->isClassPointer() && toType->isClassPointer()) + { + PClassPointer * fromClass = static_cast(fromType); + PClassPointer * toClass = static_cast(toType); + //allow narrowing parameters + if(!CanNarrowTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false; + } + else if(fromType->isObjectPointer() && toType->isObjectPointer()) + { + PObjectPointer * fromObj = static_cast(fromType); + PObjectPointer * toObj = static_cast(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(toType), static_cast(fromType))) return false; + } + else if(fromType->isClassPointer() && toType->isClassPointer()) + { + PClassPointer * fromClass = static_cast(fromType); + PClassPointer * toClass = static_cast(toType); + //allow widening returns + if(!CanWidenTo(fromClass->ClassRestriction, toClass->ClassRestriction)) return false; + } + else if(fromType->isObjectPointer() && toType->isObjectPointer()) + { + PObjectPointer * fromObj = static_cast(fromType); + PObjectPointer * toObj = static_cast(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; + return nullptr; + } + auto to = static_cast(ValueType); + auto from = static_cast(basex->ValueType); + + if(from->PointedType == TypeVoid) + { // nothing to check at compile-time for casts from Function + return this; + } + else if(AreCompatibleFnPtrs(to, from)) + { // no need to do anything for (Function)(...) or compatible casts + basex->ValueType = ValueType; + auto x = basex; + basex = nullptr; + delete this; + return x; + } + else + { + ScriptPosition.Message(MSG_ERROR, "Cannot cast %s to %s. The types are incompatible.", basex->ValueType->DescriptiveName(), to->DescriptiveName()); + delete this; + return nullptr; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +PFunction *NativeFunctionPointerCast(PFunction *from, const PFunctionPointer *to) +{ + 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(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) +{ + PARAM_PROLOGUE; + PARAM_POINTER(from, PFunction); + PARAM_POINTER(to, PFunctionPointer); + ACTION_RETURN_POINTER(NativeFunctionPointerCast(from, to)); +} + +ExpEmit FxFunctionPtrCast::Emit(VMFunctionBuilder *build) +{ + ExpEmit funcptr = basex->Emit(build); + + auto sym = FindBuiltinFunction(NAME_BuiltinFunctionPtrCast); + assert(sym); + + FunctionCallEmitter emitters(sym->Variants[0].Implementation); + emitters.AddParameter(funcptr, false); + emitters.AddParameterPointerConst(ValueType); + + emitters.AddReturn(REGT_POINTER); + return emitters.EmitCall(build); +} + //========================================================================== // // declares a single local variable (no arrays) @@ -11693,6 +12072,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) { diff --git a/source/common/scripting/backend/codegen.h b/source/common/scripting/backend/codegen.h index 5dd066b570..6aff3b75d3 100644 --- a/source/common/scripting/backend/codegen.h +++ b/source/common/scripting/backend/codegen.h @@ -277,6 +277,7 @@ enum EFxType EFX_ReturnStatement, EFX_ClassTypeCast, EFX_ClassPtrCast, + EFX_FunctionPtrCast, EFX_StateByIndex, EFX_RuntimeStateIndex, EFX_MultiNameState, @@ -512,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) { @@ -557,6 +565,8 @@ class FxConstant : public FxExpression return value; } ExpEmit Emit(VMFunctionBuilder *build); + + friend class FxTypeCast; }; //========================================================================== @@ -735,6 +745,8 @@ class FxTypeCast : public FxExpression FxExpression *Resolve(FCompileContext&); ExpEmit Emit(VMFunctionBuilder *build); + + static FxConstant * convertRawFunctionToFunctionPointer(FxExpression * in, FScriptPosition &ScriptPosition); }; //========================================================================== @@ -1818,6 +1830,7 @@ class FxVMFunctionCall : public FxExpression bool CheckAccessibility(const VersionInfo &ver); public: + const bool FnPtrCall; FArgumentList ArgList; PFunction* Function; @@ -2127,6 +2140,24 @@ class FxClassPtrCast : public FxExpression // //========================================================================== +class FxFunctionPtrCast : public FxExpression +{ + FxExpression *basex; + +public: + + FxFunctionPtrCast (PFunctionPointer *ftype, FxExpression *x); + ~FxFunctionPtrCast(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// +// +//========================================================================== + class FxNop : public FxExpression { public: diff --git a/source/common/scripting/backend/vmbuilder.cpp b/source/common/scripting/backend/vmbuilder.cpp index d063790d28..259055dc9f 100644 --- a/source/common/scripting/backend/vmbuilder.cpp +++ b/source/common/scripting/backend/vmbuilder.cpp @@ -290,6 +290,24 @@ unsigned VMFunctionBuilder::GetConstantAddress(void *ptr) } } + +//========================================================================== +// +// VMFunctionBuilder :: FindConstantInt +// +// Returns a constant register initialized with the given value. +// +//========================================================================== + +int VMFunctionBuilder::FindConstantInt(unsigned index) +{ + if(IntConstantList.Size() < index) + { + return IntConstantList[index]; + } + return 0; +} + //========================================================================== // // VMFunctionBuilder :: AllocConstants* @@ -298,7 +316,7 @@ unsigned VMFunctionBuilder::GetConstantAddress(void *ptr) // //========================================================================== -unsigned VMFunctionBuilder::AllocConstantsInt(unsigned count, int *values) +unsigned VMFunctionBuilder::AllocConstantsInt(unsigned int count, int *values) { unsigned addr = IntConstantList.Reserve(count); memcpy(&IntConstantList[addr], values, count * sizeof(int)); @@ -309,7 +327,7 @@ unsigned VMFunctionBuilder::AllocConstantsInt(unsigned count, int *values) return addr; } -unsigned VMFunctionBuilder::AllocConstantsFloat(unsigned count, double *values) +unsigned VMFunctionBuilder::AllocConstantsFloat(unsigned int count, double *values) { unsigned addr = FloatConstantList.Reserve(count); memcpy(&FloatConstantList[addr], values, count * sizeof(double)); @@ -320,7 +338,7 @@ unsigned VMFunctionBuilder::AllocConstantsFloat(unsigned count, double *values) return addr; } -unsigned VMFunctionBuilder::AllocConstantsAddress(unsigned count, void **ptrs) +unsigned VMFunctionBuilder::AllocConstantsAddress(unsigned int count, void **ptrs) { unsigned addr = AddressConstantList.Reserve(count); memcpy(&AddressConstantList[addr], ptrs, count * sizeof(void *)); @@ -331,7 +349,7 @@ unsigned VMFunctionBuilder::AllocConstantsAddress(unsigned count, void **ptrs) return addr; } -unsigned VMFunctionBuilder::AllocConstantsString(unsigned count, FString *ptrs) +unsigned VMFunctionBuilder::AllocConstantsString(unsigned int count, FString *ptrs) { unsigned addr = StringConstantList.Reserve(count); for (unsigned i = 0; i < count; i++) @@ -949,6 +967,18 @@ void FFunctionBuildList::DumpJit(bool include_gzdoom_pk3) #endif // HAVE_VM_JIT } +FunctionCallEmitter::FunctionCallEmitter(VMFunction *func) +{ + target = func; + is_vararg = target->VarFlags & VARF_VarArg; +} + +FunctionCallEmitter::FunctionCallEmitter(class PFunctionPointer *func) +{ + fnptr = func; + is_vararg = false; // function pointers cannot point to vararg functions +} + void FunctionCallEmitter::AddParameter(VMFunctionBuilder *build, FxExpression *operand) { @@ -959,7 +989,7 @@ void FunctionCallEmitter::AddParameter(VMFunctionBuilder *build, FxExpression *o operand->ScriptPosition.Message(MSG_ERROR, "Attempted to pass a non-value"); } numparams += where.RegCount; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) for (unsigned i = 0; i < where.RegCount; i++) reginfo.Push(where.RegType & REGT_TYPE); emitters.push_back([=](VMFunctionBuilder *build) -> int @@ -982,7 +1012,7 @@ void FunctionCallEmitter::AddParameter(VMFunctionBuilder *build, FxExpression *o void FunctionCallEmitter::AddParameter(ExpEmit &emit, bool reference) { numparams += emit.RegCount; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) { if (reference) reginfo.Push(REGT_POINTER); else for (unsigned i = 0; i < emit.RegCount; i++) reginfo.Push(emit.RegType & REGT_TYPE); @@ -999,7 +1029,7 @@ void FunctionCallEmitter::AddParameter(ExpEmit &emit, bool reference) void FunctionCallEmitter::AddParameterPointerConst(void *konst) { numparams++; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) reginfo.Push(REGT_POINTER); emitters.push_back([=](VMFunctionBuilder *build) ->int { @@ -1011,7 +1041,7 @@ void FunctionCallEmitter::AddParameterPointerConst(void *konst) void FunctionCallEmitter::AddParameterPointer(int index, bool konst) { numparams++; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) reginfo.Push(REGT_POINTER); emitters.push_back([=](VMFunctionBuilder *build) ->int { @@ -1023,7 +1053,7 @@ void FunctionCallEmitter::AddParameterPointer(int index, bool konst) void FunctionCallEmitter::AddParameterFloatConst(double konst) { numparams++; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) reginfo.Push(REGT_FLOAT); emitters.push_back([=](VMFunctionBuilder *build) ->int { @@ -1035,7 +1065,7 @@ void FunctionCallEmitter::AddParameterFloatConst(double konst) void FunctionCallEmitter::AddParameterIntConst(int konst) { numparams++; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) reginfo.Push(REGT_INT); emitters.push_back([=](VMFunctionBuilder *build) ->int { @@ -1055,7 +1085,7 @@ void FunctionCallEmitter::AddParameterIntConst(int konst) void FunctionCallEmitter::AddParameterStringConst(const FString &konst) { numparams++; - if (target->VarFlags & VARF_VarArg) + if (is_vararg) reginfo.Push(REGT_STRING); emitters.push_back([=](VMFunctionBuilder *build) ->int { @@ -1074,7 +1104,7 @@ ExpEmit FunctionCallEmitter::EmitCall(VMFunctionBuilder *build, TArray paramcount += func(build); } assert(paramcount == numparams); - if (target->VarFlags & VARF_VarArg) + if (is_vararg) { // Pass a hidden type information parameter to vararg functions. // It would really be nicer to actually pass real types but that'd require a far more complex interface on the compiler side than what we have. @@ -1084,9 +1114,24 @@ ExpEmit FunctionCallEmitter::EmitCall(VMFunctionBuilder *build, TArray paramcount++; } + if(fnptr) + { + ExpEmit reg(build, REGT_POINTER); + + assert(fnptr->Scope != -1); + assert(fnptr->PointedType != TypeVoid); + + // OP_LP , Load from memory. rA = *(rB + rkC) + // reg = &PFunction->Variants[0] -- PFunction::Variant* + build->Emit(OP_LP, reg.RegNum, virtualselfreg, build->GetConstantInt(offsetof(PFunction, Variants) + offsetof(FArray, Array))); + // reg = (&PFunction->Variants[0])->Implementation -- VMFunction* + build->Emit(OP_LP, reg.RegNum, reg.RegNum, build->GetConstantInt(offsetof(PFunction::Variant, Implementation))); + build->Emit(OP_CALL, reg.RegNum, paramcount, vm_jit? static_cast(fnptr->PointedType)->ReturnTypes.Size() : returns.Size()); - if (virtualselfreg == -1) + reg.Free(build); + } + else if (virtualselfreg == -1) { build->Emit(OP_CALL_K, build->GetConstantAddress(target), paramcount, vm_jit ? target->Proto->ReturnTypes.Size() : returns.Size()); } @@ -1110,9 +1155,13 @@ ExpEmit FunctionCallEmitter::EmitCall(VMFunctionBuilder *build, TArray } if (vm_jit) // The JIT compiler needs this, but the VM interpreter does not. { - for (unsigned i = returns.Size(); i < target->Proto->ReturnTypes.Size(); i++) + assert(!fnptr || fnptr->PointedType != TypeVoid); + + PPrototype * proto = fnptr ? static_cast(fnptr->PointedType) : target->Proto; + + for (unsigned i = returns.Size(); i < proto->ReturnTypes.Size(); i++) { - ExpEmit reg(build, target->Proto->ReturnTypes[i]->RegType, target->Proto->ReturnTypes[i]->RegCount); + ExpEmit reg(build, proto->ReturnTypes[i]->RegType, proto->ReturnTypes[i]->RegCount); build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum); reg.Free(build); } diff --git a/source/common/scripting/backend/vmbuilder.h b/source/common/scripting/backend/vmbuilder.h index 43ef1bc4cf..2ead8f9069 100644 --- a/source/common/scripting/backend/vmbuilder.h +++ b/source/common/scripting/backend/vmbuilder.h @@ -65,6 +65,11 @@ class VMFunctionBuilder unsigned GetConstantAddress(void *ptr); unsigned GetConstantString(FString str); + int FindConstantInt(unsigned index); + //double FindConstantFloat(unsigned index); + //void * FindConstantAddress(unsigned index); + //const FString& FindConstantString(unsigned index); + unsigned AllocConstantsInt(unsigned int count, int *values); unsigned AllocConstantsFloat(unsigned int count, double *values); unsigned AllocConstantsAddress(unsigned int count, void **ptrs); @@ -180,13 +185,12 @@ class FunctionCallEmitter TArray reginfo; unsigned numparams = 0; // This counts the number of pushed elements, which can differ from the number of emitters with vectors. VMFunction *target = nullptr; + class PFunctionPointer *fnptr = nullptr; int virtualselfreg = -1; - + bool is_vararg; public: - FunctionCallEmitter(VMFunction *func) - { - target = func; - } + FunctionCallEmitter(VMFunction *func); + FunctionCallEmitter(class PFunctionPointer *func); void SetVirtualReg(int virtreg) { diff --git a/source/common/scripting/core/scopebarrier.cpp b/source/common/scripting/core/scopebarrier.cpp index 8e03f757fe..b274435707 100644 --- a/source/common/scripting/core/scopebarrier.cpp +++ b/source/common/scripting/core/scopebarrier.cpp @@ -208,6 +208,14 @@ void FScopeBarrier::AddFlags(int flags1, int flags2, const char* name) } } +bool FScopeBarrier::CheckSidesForFunctionPointer(int from, int to) +{ + if(to == -1) return true; + + if(from == Side_Clear) from = Side_PlainData; + return ((from == to) || (from == Side_PlainData)); +} + // these are for vmexec.h void FScopeBarrier::ValidateNew(PClass* cls, int outerside) { diff --git a/source/common/scripting/core/scopebarrier.h b/source/common/scripting/core/scopebarrier.h index b5eeae78fc..08fc0a195a 100644 --- a/source/common/scripting/core/scopebarrier.h +++ b/source/common/scripting/core/scopebarrier.h @@ -62,6 +62,9 @@ struct FScopeBarrier // This struct is used so that the logic is in a single place. void AddFlags(int flags1, int flags2, const char* name); + + static bool CheckSidesForFunctionPointer(int from, int to); + // this is called from vmexec.h static void ValidateNew(PClass* cls, int scope); static void ValidateCall(PClass* selftype, VMFunction *calledfunc, int outerside); diff --git a/source/common/scripting/core/types.cpp b/source/common/scripting/core/types.cpp index f27faa7ab5..efe4dfec54 100644 --- a/source/common/scripting/core/types.cpp +++ b/source/common/scripting/core/types.cpp @@ -75,6 +75,7 @@ PStruct *TypeStringStruct; PStruct* TypeQuaternionStruct; PPointer *TypeNullPtr; PPointer *TypeVoidPtr; +PPointer *TypeRawFunction; PPointer* TypeVMFunction; @@ -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); @@ -2818,6 +2821,239 @@ PMapIterator *NewMapIterator(PType *keyType, PType *valueType) return (PMapIterator *)mapIteratorType; } +/* PFunctionPointer *******************************************************/ + +//========================================================================== +// +// PFunctionPointer - Parameterized Constructor +// +//========================================================================== + +static FString MakeFunctionPointerDescriptiveName(PPrototype * proto,const TArray &ArgFlags, int scope) +{ + FString mDescriptiveName; + + mDescriptiveName = "Function<"; + switch(scope) + { + case FScopeBarrier::Side_PlainData: + mDescriptiveName += "clearscope "; + break; + case FScopeBarrier::Side_Play: + mDescriptiveName += "play "; + break; + case FScopeBarrier::Side_UI: + mDescriptiveName += "ui "; + break; + } + if(proto->ReturnTypes.Size() > 0) + { + mDescriptiveName += proto->ReturnTypes[0]->DescriptiveName(); + + const unsigned n = proto->ReturnTypes.Size(); + for(unsigned i = 1; i < n; i++) + { + mDescriptiveName += ", "; + mDescriptiveName += proto->ReturnTypes[i]->DescriptiveName(); + } + mDescriptiveName += " ("; + } + else + { + mDescriptiveName += "void ("; + } + if(proto->ArgumentTypes.Size() > 0) + { + if(ArgFlags[0] == VARF_Out) mDescriptiveName += "out "; + mDescriptiveName += proto->ArgumentTypes[0]->DescriptiveName(); + const unsigned n = proto->ArgumentTypes.Size(); + for(unsigned i = 1; i < n; i++) + { + mDescriptiveName += ", "; + if(ArgFlags[i] == VARF_Out) mDescriptiveName += "out "; + mDescriptiveName += proto->ArgumentTypes[i]->DescriptiveName(); + } + mDescriptiveName += ")>"; + } + else + { + mDescriptiveName += "void)>"; + } + + return mDescriptiveName; +} + + +FString PFunctionPointer::GenerateNameForError(const PFunction * from) +{ + return MakeFunctionPointerDescriptiveName(from->Variants[0].Proto, from->Variants[0].ArgFlags, FScopeBarrier::SideFromFlags(from->Variants[0].Flags)); +} + +PFunctionPointer::PFunctionPointer(PPrototype * proto, TArray && argflags, int scope) + : PPointer(proto ? (PType*) proto : TypeVoid, false), ArgFlags(std::move(argflags)), Scope(scope) +{ + if(!proto) + { + mDescriptiveName = "Function"; + } + else + { + mDescriptiveName = MakeFunctionPointerDescriptiveName(proto, ArgFlags, scope); + } + + Flags |= TYPE_FunctionPointer; + + if(proto) + { + assert(Scope != -1); // for now, a scope is always required + + TArray ArgNames; + TArray ArgFlags2(ArgFlags); // AddVariant calls std::move on this, so it needs to be a copy, + // but it takes it as a regular reference, so it needs to be a full variable instead of a temporary + ArgNames.Resize(ArgFlags.Size()); + FakeFunction = Create(); + FakeFunction->AddVariant(proto, ArgFlags2, ArgNames, nullptr, FScopeBarrier::FlagsFromSide(Scope), 0); + } + else + { + FakeFunction = nullptr; + } +} + +void PFunctionPointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const +{ + auto p = *(const PFunction**)(addr); + if(p) + { + FunctionPointerValue val; + FunctionPointerValue *fpv = &val; + val.ClassName = FString((p->OwningClass ? p->OwningClass->TypeName : NAME_None).GetChars()); + val.FunctionName = FString(p->SymbolName.GetChars()); + SerializeFunctionPointer(ar, key, fpv); + } + else + { + FunctionPointerValue *fpv = nullptr; + SerializeFunctionPointer(ar, key, fpv); + } +} + +PFunction *NativeFunctionPointerCast(PFunction *from, const PFunctionPointer *to); + +bool PFunctionPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + FunctionPointerValue val; + FunctionPointerValue *fpv = &val; + SerializeFunctionPointer(ar, key, fpv); + + PFunction ** fn = (PFunction**)(addr); + + if(fpv) + { + auto cls = PClass::FindClass(val.ClassName); + if(!cls) + { + *fn = nullptr; + Printf(TEXTCOLOR_RED "Function Pointer ('%s::%s'): '%s' is not a valid class\n", + val.ClassName.GetChars(), + val.FunctionName.GetChars(), + val.ClassName.GetChars() + ); + ar.mErrors++; + return false; + } + auto sym = cls->FindSymbol(FName(val.FunctionName), true); + if(!sym) + { + *fn = nullptr; + Printf(TEXTCOLOR_RED "Function Pointer ('%s::%s'): symbol '%s' does not exist in class '%s'\n", + val.ClassName.GetChars(), + val.FunctionName.GetChars(), + val.FunctionName.GetChars(), + val.ClassName.GetChars() + ); + ar.mErrors++; + return false; + } + PFunction* p = dyn_cast(sym); + if(!p) + { + *fn = nullptr; + Printf(TEXTCOLOR_RED "Function Pointer (%s::%s): '%s' in class '%s' is a variable, not a function\n", + val.ClassName.GetChars(), + val.FunctionName.GetChars(), + val.FunctionName.GetChars(), + val.ClassName.GetChars() + ); + ar.mErrors++; + return false; + } + *fn = NativeFunctionPointerCast(p, this); + if(!*fn) + { + if((p->Variants[0].Flags & (VARF_Action | VARF_Virtual)) != 0) + { + *fn = nullptr; + Printf(TEXTCOLOR_RED "Function Pointer (%s::%s): function '%s' in class '%s' is %s, not a static function\n", + val.ClassName.GetChars(), + val.FunctionName.GetChars(), + val.FunctionName.GetChars(), + val.ClassName.GetChars(), + (p->GetImplicitArgs() == 1 ? "a virtual function" : "an action function") + ); + } + else + { + FString fn_name = MakeFunctionPointerDescriptiveName(p->Variants[0].Proto,p->Variants[0].ArgFlags, FScopeBarrier::SideFromFlags(p->Variants[0].Flags)); + Printf(TEXTCOLOR_RED "Function Pointer (%s::%s) has incompatible type (Pointer is '%s', Function is '%s')\n", + val.ClassName.GetChars(), + val.FunctionName.GetChars(), + fn_name.GetChars(), + mDescriptiveName.GetChars() + ); + } + ar.mErrors++; + return false; + } + return true; + } + else + { + *fn = nullptr; + } + return true; +} + +bool PFunctionPointer::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PPrototype * proto = (const PPrototype*) id1; + const PFunctionPointer::FlagsAndScope * flags_and_scope = (const PFunctionPointer::FlagsAndScope *) id2; + return (proto == (PointedType == TypeVoid ? nullptr : PointedType)) + && (Scope == flags_and_scope->Scope) + && (ArgFlags == *flags_and_scope->ArgFlags); +} + +void PFunctionPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ //NOT SUPPORTED + assert(0 && "GetTypeIDs not supported for PFunctionPointer"); +} + +PFunctionPointer * NewFunctionPointer(PPrototype * proto, TArray && argflags, int scope) +{ + size_t bucket; + + PFunctionPointer::FlagsAndScope flags_and_scope { &argflags, scope }; + + PType *fn = TypeTable.FindType(NAME_Function, (intptr_t)proto, (intptr_t)&flags_and_scope, &bucket); + if (fn == nullptr) + { + fn = new PFunctionPointer(proto, std::move(argflags), scope); + flags_and_scope.ArgFlags = &static_cast(fn)->ArgFlags; + TypeTable.AddType(fn, NAME_Function, (intptr_t)proto, (intptr_t)&flags_and_scope, bucket); + } + return (PFunctionPointer *)fn; +} + /* PStruct ****************************************************************/ //========================================================================== @@ -3227,13 +3463,13 @@ size_t FTypeTable::Hash(FName p1, intptr_t p2, intptr_t p3) // to transform this into a ROR or ROL. i1 = (i1 >> (sizeof(size_t)*4)) | (i1 << (sizeof(size_t)*4)); - if (p1 != NAME_Prototype) + if (p1 != NAME_Prototype && p1 != NAME_Function) { size_t i2 = (size_t)p2; size_t i3 = (size_t)p3; return (~i1 ^ i2) + i3 * 961748927; // i3 is multiplied by a prime } - else + else if(p1 == NAME_Prototype) { // Prototypes need to hash the TArrays at p2 and p3 const TArray *a2 = (const TArray *)p2; const TArray *a3 = (const TArray *)p3; @@ -3247,6 +3483,18 @@ size_t FTypeTable::Hash(FName p1, intptr_t p2, intptr_t p3) } return i1; } + else // if(p1 == NAME_Function) + { // functions need custom hashing as well + size_t i2 = (size_t)p2; + const PFunctionPointer::FlagsAndScope * flags_and_scope = (const PFunctionPointer::FlagsAndScope *) p3; + const TArray * a3 = flags_and_scope->ArgFlags; + i1 = (~i1 ^ i2); + for (unsigned i = 0; i < a3->Size(); ++i) + { + i1 = (i1 * 961748927) + (size_t)((*a3)[i]); + } + return (i1 * 961748927) + (size_t)flags_and_scope->Scope; + } } //========================================================================== diff --git a/source/common/scripting/core/types.h b/source/common/scripting/core/types.h index 850c354a81..e24bcd38bb 100644 --- a/source/common/scripting/core/types.h +++ b/source/common/scripting/core/types.h @@ -66,6 +66,7 @@ enum class PContainerType; class PPointer; class PClassPointer; +class PFunctionPointer; class PArray; class PStruct; class PClassType; @@ -86,6 +87,7 @@ class PType : public PTypeBase TYPE_ObjectPointer = 64, TYPE_ClassPointer = 128, TYPE_Array = 256, + TYPE_FunctionPointer = 512, TYPE_IntCompatible = TYPE_Int | TYPE_IntNotInt, // must be the combination of all flags that are subtypes of int and can be cast to an int. }; @@ -194,9 +196,10 @@ class PType : public PTypeBase bool isIntCompatible() const { return !!(Flags & TYPE_IntCompatible); } bool isFloat() const { return !!(Flags & TYPE_Float); } bool isPointer() const { return !!(Flags & TYPE_Pointer); } - bool isRealPointer() const { return (Flags & (TYPE_Pointer|TYPE_ClassPointer)) == TYPE_Pointer; } // This excludes class pointers which use their PointedType differently + bool isRealPointer() const { return (Flags & (TYPE_Pointer | TYPE_ClassPointer | TYPE_FunctionPointer)) == TYPE_Pointer; } // This excludes class and function pointers which use their PointedType differently bool isObjectPointer() const { return !!(Flags & TYPE_ObjectPointer); } bool isClassPointer() const { return !!(Flags & TYPE_ClassPointer); } + bool isFunctionPointer() const { return !!(Flags & TYPE_FunctionPointer); } bool isEnum() const { return TypeTableType == NAME_Enum; } bool isArray() const { return !!(Flags & TYPE_Array); } bool isStaticArray() const { return TypeTableType == NAME_StaticArray; } @@ -210,6 +213,7 @@ class PType : public PTypeBase PContainerType *toContainer() { return isContainer() ? (PContainerType*)this : nullptr; } PPointer *toPointer() { return isPointer() ? (PPointer*)this : nullptr; } static PClassPointer *toClassPointer(PType *t) { return t && t->isClassPointer() ? (PClassPointer*)t : nullptr; } + static PFunctionPointer *toFunctionPointer(PType *t) { return t && t->isFunctionPointer() ? (PFunctionPointer*)t : nullptr; } static PClassType *toClass(PType *t) { return t && t->isClass() ? (PClassType*)t : nullptr; } }; @@ -595,6 +599,33 @@ class PMapIterator : public PCompoundType void DestroyValue(void *addr) const override; }; +class PFunctionPointer : public PPointer +{ +public: + //PointedType = PPrototype or TypeVoid + PFunctionPointer(PPrototype * proto, TArray &&argflags, int scope); + + static FString GenerateNameForError(const PFunction * from); + + TArray ArgFlags; + int Scope; + + PFunction *FakeFunction; // used for type checking in FxFunctionCall + + void WriteValue(FSerializer &ar, const char *key, const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key, void *addr) const override; + + + struct FlagsAndScope + { // used for IsMatch's id2 + TArray * ArgFlags; + int Scope; + }; + + bool IsMatch(intptr_t id1, intptr_t id2) const override; + void GetTypeIDs(intptr_t &id1, intptr_t &id2) const override; //NOT SUPPORTED +}; + class PStruct : public PContainerType { public: @@ -657,6 +688,7 @@ PMapIterator *NewMapIterator(PType *keytype, PType *valuetype); PArray *NewArray(PType *type, unsigned int count); PStaticArray *NewStaticArray(PType *type); PDynArray *NewDynArray(PType *type); +PFunctionPointer *NewFunctionPointer(PPrototype * proto, TArray && argflags, int scope); PPointer *NewPointer(PType *type, bool isconst = false); PPointer *NewPointer(PClass *type, bool isconst = false); PClassPointer *NewClassPointer(PClass *restrict); @@ -697,6 +729,7 @@ extern PPointer *TypeFont; extern PStateLabel *TypeStateLabel; extern PPointer *TypeNullPtr; extern PPointer *TypeVoidPtr; +extern PPointer* TypeRawFunction; extern PPointer* TypeVMFunction; diff --git a/source/common/scripting/core/vmdisasm.cpp b/source/common/scripting/core/vmdisasm.cpp index 86d566985b..09524d3b58 100644 --- a/source/common/scripting/core/vmdisasm.cpp +++ b/source/common/scripting/core/vmdisasm.cpp @@ -46,6 +46,9 @@ #define LKP MODE_AP | MODE_BCJOINT | MODE_BCKP #define LFP MODE_AP | MODE_BUNUSED | MODE_CUNUSED + +#define RP MODE_AP | MODE_BUNUSED | MODE_CUNUSED + #define RIRPKI MODE_AI | MODE_BP | MODE_CKI #define RIRPRI MODE_AI | MODE_BP | MODE_CI #define RFRPKI MODE_AF | MODE_BP | MODE_CKI diff --git a/source/common/scripting/frontend/ast.cpp b/source/common/scripting/frontend/ast.cpp index 50e1c2e9f1..2841c16431 100644 --- a/source/common/scripting/frontend/ast.cpp +++ b/source/common/scripting/frontend/ast.cpp @@ -521,6 +521,26 @@ static void PrintDynArrayType(FLispString &out, const ZCC_TreeNode *node) out.Close(); } +static void PrintFuncPtrParamDecl(FLispString &out, const ZCC_TreeNode *node) +{ + ZCC_FuncPtrParamDecl *dnode = (ZCC_FuncPtrParamDecl *)node; + out.Break(); + out.Open("func-ptr-param-decl"); + PrintNodes(out, dnode->Type); + out.AddHex(dnode->Flags); + out.Close(); +} + +static void PrintFuncPtrType(FLispString &out, const ZCC_TreeNode *node){ + ZCC_FuncPtrType *dnode = (ZCC_FuncPtrType *)node; + out.Break(); + out.Open("func-ptr-type"); + PrintNodes(out, dnode->RetType); + PrintNodes(out, dnode->Params); + out.AddHex(dnode->Scope); + out.Close(); +} + static void PrintClassType(FLispString &out, const ZCC_TreeNode *node) { ZCC_ClassType *tnode = (ZCC_ClassType *)node; @@ -628,6 +648,16 @@ static void PrintExprClassCast(FLispString &out, const ZCC_TreeNode *node) out.Close(); } +static void PrintExprFunctionPtrCast(FLispString &out, const ZCC_TreeNode *node) +{ + ZCC_FunctionPtrCast *enode = (ZCC_FunctionPtrCast *)node; + assert(enode->Operation == PEX_FunctionPtrCast); + out.Open("expr-func-ptr-cast"); + PrintNodes(out, enode->PtrType, false); + PrintNodes(out, enode->Expr, false); + out.Close(); +} + static void PrintStaticArray(FLispString &out, const ZCC_TreeNode *node) { ZCC_StaticArrayStatement *enode = (ZCC_StaticArrayStatement *)node; @@ -976,6 +1006,8 @@ static const NodePrinterFunc TreeNodePrinter[] = PrintMapType, PrintMapIteratorType, PrintDynArrayType, + PrintFuncPtrParamDecl, + PrintFuncPtrType, PrintClassType, PrintExpression, PrintExprID, @@ -1011,6 +1043,7 @@ static const NodePrinterFunc TreeNodePrinter[] = PrintVectorInitializer, PrintDeclFlags, PrintExprClassCast, + PrintExprFunctionPtrCast, PrintStaticArrayState, PrintProperty, PrintFlagDef, diff --git a/source/common/scripting/frontend/zcc-parse.lemon b/source/common/scripting/frontend/zcc-parse.lemon index 295c58dd9a..347fddcc1e 100644 --- a/source/common/scripting/frontend/zcc-parse.lemon +++ b/source/common/scripting/frontend/zcc-parse.lemon @@ -988,6 +988,71 @@ aggregate_type(X) ::= ARRAY(T) LT type_or_array(A) GT. /* TArray */ X = arr; } +aggregate_type(X) ::= func_ptr_type(A). { X = A; /*X-overwrites-A*/ } + +%type func_ptr_type {ZCC_FuncPtrType *} +%type func_ptr_params {ZCC_FuncPtrParamDecl *} +%type func_ptr_param_list {ZCC_FuncPtrParamDecl *} +%type func_ptr_param {ZCC_FuncPtrParamDecl *} + +//fn_ptr_flag(X) ::= . { X.Int = 0; } //implicit scope not allowed +fn_ptr_flag(X) ::= UI. { X.Int = ZCC_UIFlag; } +fn_ptr_flag(X) ::= PLAY. { X.Int = ZCC_Play; } +fn_ptr_flag(X) ::= CLEARSCOPE. { X.Int = ZCC_ClearScope; } +//fn_ptr_flag(X) ::= VIRTUALSCOPE. { X.Int = ZCC_VirtualScope; } //virtual scope not allowed + + +func_ptr_type(X) ::= FNTYPE(T) LT fn_ptr_flag(F) type_list_or_void(A) LPAREN func_ptr_params(B) RPAREN GT. /* Function<...(...)> */ +{ + NEW_AST_NODE(FuncPtrType,fn_ptr,T); + fn_ptr->RetType = A; + fn_ptr->Params = B; + fn_ptr->Scope = F.Int; + X = fn_ptr; +} + +func_ptr_type(X) ::= FNTYPE(T) LT VOID GT. /* Function */ +{ + NEW_AST_NODE(FuncPtrType,fn_ptr,T); + fn_ptr->RetType = nullptr; + fn_ptr->Params = nullptr; + fn_ptr->Scope = -1; + X = fn_ptr; +} + +func_ptr_params(X) ::= . /* empty */ { X = NULL; } +func_ptr_params(X) ::= VOID. { X = NULL; } +func_ptr_params(X) ::= func_ptr_param_list(X). + +// varargs function pointers not currently supported +//func_ptr_params(X) ::= func_ptr_param_list(A) COMMA ELLIPSIS. +//{ +// NEW_AST_NODE(FuncPtrParamDecl,parm,stat->sc->GetMessageLine()); +// parm->Type = nullptr; +// parm->Flags = 0; +// X = A; /*X-overwrites-A*/ +// AppendTreeNodeSibling(X, parm); +//} + +func_ptr_param_list(X) ::= func_ptr_param(X). +func_ptr_param_list(X) ::= func_ptr_param_list(A) COMMA func_ptr_param(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +func_ptr_param(X) ::= func_param_flags(A) type(B). +{ + NEW_AST_NODE(FuncPtrParamDecl,parm,A.SourceLoc ? A.SourceLoc : B->SourceLoc); + parm->Type = B; + parm->Flags = A.Int; + X = parm; +} + +func_ptr_param(X) ::= func_param_flags(A) type(B) AND. +{ + NEW_AST_NODE(FuncPtrParamDecl,parm,A.SourceLoc ? A.SourceLoc : B->SourceLoc); + parm->Type = B; + parm->Flags = A.Int | ZCC_Out; + X = parm; +} + aggregate_type(X) ::= CLASS(T) class_restrictor(A). /* class */ { NEW_AST_NODE(ClassType,cls,T); @@ -1408,6 +1473,17 @@ primary(X) ::= LPAREN CLASS LT IDENTIFIER(A) GT RPAREN LPAREN func_expr_list(B) expr->Parameters = B; X = expr; } + +primary(X) ::= LPAREN func_ptr_type(A) RPAREN LPAREN expr(B) RPAREN. [DOT] // function pointer type cast +{ + NEW_AST_NODE(FunctionPtrCast, expr, A); + expr->Operation = PEX_FunctionPtrCast; + A->ArraySize = NULL; + expr->PtrType = A; + expr->Expr = B; + X = expr; +} + primary(X) ::= primary(A) LBRACKET expr(B) RBRACKET. [DOT] // Array access { NEW_AST_NODE(ExprBinary, expr, B); @@ -1417,6 +1493,7 @@ primary(X) ::= primary(A) LBRACKET expr(B) RBRACKET. [DOT] // Array access expr->Right = B; X = expr; } + primary(X) ::= primary(A) DOT IDENTIFIER(B). // Member access { NEW_AST_NODE(ExprMemberAccess, expr, B); diff --git a/source/common/scripting/frontend/zcc_compile.cpp b/source/common/scripting/frontend/zcc_compile.cpp index 815af694b7..c29a580a32 100644 --- a/source/common/scripting/frontend/zcc_compile.cpp +++ b/source/common/scripting/frontend/zcc_compile.cpp @@ -2024,6 +2024,52 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n } break; } + case AST_FuncPtrType: + { + auto fn = static_cast(ztype); + + if(fn->Scope == -1) + { // Function + retval = NewFunctionPointer(nullptr, {}, -1); + } + else + { + TArray returns; + TArray args; + TArray argflags; + + if(auto *t = fn->RetType; t != nullptr) do { + returns.Push(DetermineType(outertype, field, name, t, false, false)); + } while( (t = (ZCC_Type *)t->SiblingNext) != fn->RetType); + + if(auto *t = fn->Params; t != nullptr) do { + args.Push(DetermineType(outertype, field, name, t->Type, false, false)); + argflags.Push(t->Flags == ZCC_Out ? VARF_Out : 0); + } while( (t = (ZCC_FuncPtrParamDecl *) t->SiblingNext) != fn->Params); + + auto proto = NewPrototype(returns,args); + switch(fn->Scope) + { // only play/ui/clearscope functions are allowed, no data or virtual scope functions + case ZCC_Play: + fn->Scope = FScopeBarrier::Side_Play; + break; + case ZCC_UIFlag: + fn->Scope = FScopeBarrier::Side_UI; + break; + case ZCC_ClearScope: + fn->Scope = FScopeBarrier::Side_PlainData; + break; + case 0: + fn->Scope = -1; + break; + default: + Error(field, "Invalid Scope for Function Pointer"); + break; + } + retval = NewFunctionPointer(proto, std::move(argflags), fn->Scope); + } + break; + } case AST_ClassType: { auto ctype = static_cast(ztype); @@ -2992,6 +3038,17 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast, bool substitute) return new FxClassPtrCast(cls, ConvertNode(cc->Parameters)); } + case AST_FunctionPtrCast: + { + auto cast = static_cast(ast); + + auto type = DetermineType(ConvertClass, cast, NAME_None, cast->PtrType, false, false); + assert(type->isFunctionPointer()); + auto ptrType = static_cast(type); + + return new FxFunctionPtrCast(ptrType, ConvertNode(cast->Expr)); + } + case AST_StaticArrayStatement: { auto sas = static_cast(ast); diff --git a/source/common/scripting/frontend/zcc_exprlist.h b/source/common/scripting/frontend/zcc_exprlist.h index faf6af6a43..f05758379a 100644 --- a/source/common/scripting/frontend/zcc_exprlist.h +++ b/source/common/scripting/frontend/zcc_exprlist.h @@ -9,6 +9,7 @@ xx(FuncCall, '(') xx(ArrayAccess, TK_Array) xx(MemberAccess, '.') xx(ClassCast, TK_Class) +xx(FunctionPtrCast, TK_FunctionType) xx(TypeRef, TK_Class) xx(Vector, TK_Vector2) diff --git a/source/common/scripting/frontend/zcc_parser.cpp b/source/common/scripting/frontend/zcc_parser.cpp index e689d42494..24b2ecb1f3 100644 --- a/source/common/scripting/frontend/zcc_parser.cpp +++ b/source/common/scripting/frontend/zcc_parser.cpp @@ -222,6 +222,7 @@ static void InitTokenMap() TOKENDEF2(TK_Map, ZCC_MAP, NAME_Map); TOKENDEF2(TK_MapIterator, ZCC_MAPITERATOR,NAME_MapIterator); TOKENDEF2(TK_Array, ZCC_ARRAY, NAME_Array); + TOKENDEF2(TK_FunctionType, ZCC_FNTYPE, NAME_Function); TOKENDEF2(TK_Include, ZCC_INCLUDE, NAME_Include); TOKENDEF (TK_Void, ZCC_VOID); TOKENDEF (TK_True, ZCC_TRUE); @@ -925,6 +926,29 @@ ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool c break; } + case AST_FuncPtrParamDecl: + { + TreeNodeDeepCopy_Start(FuncPtrParamDecl); + + // ZCC_FuncPtrParamDecl + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Flags = origCasted->Flags; + + break; + } + + case AST_FuncPtrType: + { + TreeNodeDeepCopy_Start(FuncPtrType); + + // ZCC_FuncPtrType + copy->RetType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->RetType, true, copiedNodesList)); + copy->Params = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Params, true, copiedNodesList)); + copy->Scope = origCasted->Scope; + + break; + } + case AST_ClassType: { TreeNodeDeepCopy_Start(ClassType); @@ -1371,7 +1395,21 @@ ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool c break; } + + case AST_FunctionPtrCast: + { + TreeNodeDeepCopy_Start(FunctionPtrCast); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_FunctionPtrCast + copy->PtrType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->PtrType, true, copiedNodesList)); + copy->Expr = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Expr, true, copiedNodesList)); + break; + } + case AST_StaticArrayStatement: { TreeNodeDeepCopy_Start(StaticArrayStatement); diff --git a/source/common/scripting/frontend/zcc_parser.h b/source/common/scripting/frontend/zcc_parser.h index e4e4ea3635..c9c1a2933d 100644 --- a/source/common/scripting/frontend/zcc_parser.h +++ b/source/common/scripting/frontend/zcc_parser.h @@ -101,6 +101,8 @@ enum EZCCTreeNodeType AST_MapType, AST_MapIteratorType, AST_DynArrayType, + AST_FuncPtrParamDecl, + AST_FuncPtrType, AST_ClassType, AST_Expression, AST_ExprID, @@ -136,6 +138,7 @@ enum EZCCTreeNodeType AST_VectorValue, AST_DeclFlags, AST_ClassCast, + AST_FunctionPtrCast, AST_StaticArrayStatement, AST_Property, AST_FlagDef, @@ -382,6 +385,19 @@ struct ZCC_DynArrayType : ZCC_Type ZCC_Type *ElementType; }; +struct ZCC_FuncPtrParamDecl : ZCC_TreeNode +{ + ZCC_Type *Type; + int Flags; +}; + +struct ZCC_FuncPtrType : ZCC_Type +{ + ZCC_Type *RetType; + ZCC_FuncPtrParamDecl *Params; + int Scope; +}; + struct ZCC_ClassType : ZCC_Type { ZCC_Identifier *Restriction; @@ -428,6 +444,12 @@ struct ZCC_ClassCast : ZCC_Expression ZCC_FuncParm *Parameters; }; +struct ZCC_FunctionPtrCast : ZCC_Expression +{ + ZCC_FuncPtrType *PtrType; + ZCC_Expression *Expr; +}; + struct ZCC_ExprMemberAccess : ZCC_Expression { ZCC_Expression *Left; diff --git a/source/common/scripting/interface/vmnatives.cpp b/source/common/scripting/interface/vmnatives.cpp index 1289f31637..5ab3051d44 100644 --- a/source/common/scripting/interface/vmnatives.cpp +++ b/source/common/scripting/interface/vmnatives.cpp @@ -54,6 +54,7 @@ #include "i_time.h" #include "maps.h" +#include "types.h" static ZSMap AllServices; @@ -847,6 +848,13 @@ DEFINE_ACTION_FUNCTION(_Wads, ReadLump) ACTION_RETURN_STRING(isLumpValid ? GetStringFromLump(lump, false) : FString()); } +DEFINE_ACTION_FUNCTION(_Wads, GetLumpLength) +{ + PARAM_PROLOGUE; + PARAM_INT(lump); + ACTION_RETURN_INT(fileSystem.FileLength(lump)); +} + //========================================================================== // // CVARs @@ -1348,3 +1356,19 @@ DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, Inverse, QuatInverse) QuatInverse(self->X, self->Y, self->Z, self->W, &quat); ACTION_RETURN_QUAT(quat); } + +PFunction * FindFunctionPointer(PClass * cls, int fn_name) +{ + auto fn = dyn_cast(cls->FindSymbol(ENamedName(fn_name), true)); + return (fn && (fn->Variants[0].Flags & (VARF_Action | VARF_Virtual)) == 0 ) ? fn : nullptr; +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, FindFunction, FindFunctionPointer) +{ + PARAM_PROLOGUE; + PARAM_CLASS(cls, DObject); + PARAM_NAME(fn); + + ACTION_RETURN_POINTER(FindFunctionPointer(cls, fn.GetIndex())); +} + diff --git a/source/common/scripting/jit/jit_call.cpp b/source/common/scripting/jit/jit_call.cpp index d7074edc9d..7154d9e271 100644 --- a/source/common/scripting/jit/jit_call.cpp +++ b/source/common/scripting/jit/jit_call.cpp @@ -719,3 +719,8 @@ asmjit::FuncSignature JitCompiler::CreateFuncSignature() signature.init(CallConv::kIdHost, rettype, cachedArgs->Data(), cachedArgs->Size()); return signature; } + +void JitCompiler::EmitNULLCHECK() +{ + EmitNullPointerThrow(A, X_READ_NIL); +} \ No newline at end of file diff --git a/source/common/scripting/vm/vmexec.h b/source/common/scripting/vm/vmexec.h index f1e0cb2350..0d5737d256 100644 --- a/source/common/scripting/vm/vmexec.h +++ b/source/common/scripting/vm/vmexec.h @@ -1937,6 +1937,15 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret) CMPJMP(reg.a[B] == konsta[C].v); NEXTOP; + OP(NULLCHECK): + ASSERTA(a); + if (PA == nullptr) + { + ThrowAbortException(X_WRITE_NIL, nullptr); + return 0; + } + NEXTOP; + OP(NOP): NEXTOP; } diff --git a/source/common/scripting/vm/vmops.h b/source/common/scripting/vm/vmops.h index 1782e78b70..9280b746c5 100644 --- a/source/common/scripting/vm/vmops.h +++ b/source/common/scripting/vm/vmops.h @@ -289,4 +289,7 @@ xx(SUBA, sub, RIRPRP, NOP, 0, 0) // dA = pB - pC xx(EQA_R, beq, CPRR, NOP, 0, 0) // if ((pB == pkC) != A) then pc++ xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER) +// Null check +xx(NULLCHECK, nullcheck, RP, NOP, 0, 0) // EmitNullPointerThrow(pA) + #undef xx diff --git a/source/common/utility/tarray.h b/source/common/utility/tarray.h index 760dd60921..e607726882 100644 --- a/source/common/utility/tarray.h +++ b/source/common/utility/tarray.h @@ -455,6 +455,13 @@ class TArray return start; } + unsigned AddUnique(const T& obj) + { + auto f = Find(obj); + if (f == Size()) Push(obj); + return f; + } + bool Pop () { if (Count > 0) diff --git a/source/core/rendering/hw_models.cpp b/source/core/rendering/hw_models.cpp index 81f42448aa..c4746b7fe1 100644 --- a/source/core/rendering/hw_models.cpp +++ b/source/core/rendering/hw_models.cpp @@ -61,7 +61,7 @@ void FHWModelRenderer::EndDrawModel(FRenderStyle style, FSpriteModelFrame *smf) state.SetCulling(Cull_None); } -void FHWModelRenderer::BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored) +void FHWModelRenderer::BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored, FSpriteModelFrame*) { state.SetDepthFunc(DF_LEqual); state.SetCulling(mirrored ? Cull_CCW : Cull_CW); @@ -69,7 +69,7 @@ void FHWModelRenderer::BeginDrawHUDModel(FRenderStyle style, const VSMatrix &obj state.EnableModelMatrix(true); } -void FHWModelRenderer::EndDrawHUDModel(FRenderStyle style) +void FHWModelRenderer::EndDrawHUDModel(FRenderStyle style, FSpriteModelFrame*) { state.EnableModelMatrix(false); diff --git a/source/core/rendering/hw_models.h b/source/core/rendering/hw_models.h index 7e41535e2c..7773d75502 100644 --- a/source/core/rendering/hw_models.h +++ b/source/core/rendering/hw_models.h @@ -46,8 +46,8 @@ class FHWModelRenderer : public FModelRenderer void EndDrawModel(FRenderStyle style, FSpriteModelFrame *smf) override; IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) override; VSMatrix GetViewToWorldMatrix() override; - void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored) override; - void EndDrawHUDModel(FRenderStyle style) override; + void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored, FSpriteModelFrame* smf) override; + void EndDrawHUDModel(FRenderStyle style, FSpriteModelFrame* smf) override; void SetInterpolation(double interpolation) override; void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) override; void DrawArrays(int start, int count) override;