diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index 948228cb916..70080a96519 100644 --- a/src/common/engine/namedef.h +++ b/src/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/src/common/engine/sc_man_scanner.re b/src/common/engine/sc_man_scanner.re index dfc3f050f0e..e5d507a28d5 100644 --- a/src/common/engine/sc_man_scanner.re +++ b/src/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, 11, 0)? TK_FunctionType : TK_Identifier); } 'in' { RET(TK_In); } 'sizeof' { RET(TK_SizeOf); } 'alignof' { RET(TK_AlignOf); } diff --git a/src/common/engine/sc_man_tokens.h b/src/common/engine/sc_man_tokens.h index 8fb2f88e3ef..48700312517 100644 --- a/src/common/engine/sc_man_tokens.h +++ b/src/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/src/common/engine/serializer.cpp b/src/common/engine/serializer.cpp index bb54c0d2be7..34fd6956b59 100644 --- a/src/common/engine/serializer.cpp +++ b/src/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/src/common/engine/serializer.h b/src/common/engine/serializer.h index a6f74c0371c..9328f2f223b 100644 --- a/src/common/engine/serializer.h +++ b/src/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/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index 9711abf7289..24ab59d93ed 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -297,6 +297,13 @@ 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); + 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)> to Function)> + } } return false; } @@ -8360,6 +8367,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) PContainerType *cls = nullptr; bool staticonly = false; bool novirtual = false; + bool fnptr = false; bool isreadonly = false; PContainerType *ccls = nullptr; @@ -8977,6 +8985,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()); @@ -9096,8 +9120,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); @@ -9111,7 +9135,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; @@ -9185,7 +9209,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; @@ -9210,7 +9234,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(); @@ -9278,6 +9302,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."); @@ -9327,7 +9357,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 @@ -9336,7 +9366,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); @@ -9473,6 +9503,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) { ValueType = TypeVoid; } + return this; } @@ -9498,11 +9529,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) @@ -9546,43 +9580,56 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) } } } + else if (FnPtrCall) + { + assert(Self != nullptr); + selfemit = Self->Emit(build); + assert(selfemit.RegType == REGT_POINTER); + 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); } @@ -11610,6 +11657,107 @@ 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); +} + +//========================================================================== +// +// +// +//========================================================================== + +FxExpression *FxFunctionPtrCast::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(basex, ctx); + + if (!(basex->ValueType && basex->ValueType->isFunctionPointer())) + { + delete this; + return nullptr; + } + auto to = static_cast(ValueType); + auto from = static_cast(basex->ValueType); + + if(to->PointedType == TypeVoid) + { // no need to do anything for (FunctionValueType = 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 + return this; + } + else + { + // TODO allow narrowing argument types and widening return types via cast, ex.: Function)> to Function)> + 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) +{ + // TODO allow narrowing argument types and widening return types via cast, ex.: Function)> to Function)> + return (to->PointedType == TypeVoid || (from && + ( from->Variants[0].Proto == static_cast(to->PointedType) + && from->Variants[0].ArgFlags == to->ArgFlags + && FScopeBarrier::CheckSidesForFunctionPointer(FScopeBarrier::SideFromFlags(from->Variants[0].Flags), to->Scope) + ))) ? from : 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) diff --git a/src/common/scripting/backend/codegen.h b/src/common/scripting/backend/codegen.h index 2781adbe5a8..e0ec9f2c0f7 100644 --- a/src/common/scripting/backend/codegen.h +++ b/src/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, @@ -1811,6 +1812,7 @@ class FxVMFunctionCall : public FxExpression bool CheckAccessibility(const VersionInfo &ver); public: + const bool FnPtrCall; FArgumentList ArgList; PFunction* Function; @@ -2120,6 +2122,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/src/common/scripting/backend/vmbuilder.cpp b/src/common/scripting/backend/vmbuilder.cpp index d063790d286..b64950e3441 100644 --- a/src/common/scripting/backend/vmbuilder.cpp +++ b/src/common/scripting/backend/vmbuilder.cpp @@ -949,6 +949,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 +971,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 +994,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 +1011,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 +1023,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 +1035,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 +1047,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 +1067,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 +1086,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 +1096,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 +1137,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/src/common/scripting/backend/vmbuilder.h b/src/common/scripting/backend/vmbuilder.h index 43ef1bc4cfb..f1c8d889f35 100644 --- a/src/common/scripting/backend/vmbuilder.h +++ b/src/common/scripting/backend/vmbuilder.h @@ -180,13 +180,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/src/common/scripting/core/scopebarrier.cpp b/src/common/scripting/core/scopebarrier.cpp index 8e03f757fe5..b2744357073 100644 --- a/src/common/scripting/core/scopebarrier.cpp +++ b/src/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/src/common/scripting/core/scopebarrier.h b/src/common/scripting/core/scopebarrier.h index b5eeae78fc5..08fc0a195a3 100644 --- a/src/common/scripting/core/scopebarrier.h +++ b/src/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/src/common/scripting/core/types.cpp b/src/common/scripting/core/types.cpp index 6543dde9e92..f7d6e3b7ffa 100644 --- a/src/common/scripting/core/types.cpp +++ b/src/common/scripting/core/types.cpp @@ -2816,6 +2816,232 @@ 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; +} + +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): symbol '%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; + } + else if(p->GetImplicitArgs() > 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 method" : "an action function") + ); + ar.mErrors++; + return false; + } + *fn = NativeFunctionPointerCast(p, this); + if(!*fn) + { + 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 ****************************************************************/ //========================================================================== @@ -3225,13 +3451,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; @@ -3245,6 +3471,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/src/common/scripting/core/types.h b/src/common/scripting/core/types.h index 789cc7d3e43..ebfaf7172d9 100644 --- a/src/common/scripting/core/types.h +++ b/src/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,31 @@ 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); + + 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 +686,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); diff --git a/src/common/scripting/frontend/ast.cpp b/src/common/scripting/frontend/ast.cpp index 50e1c2e9f1e..2841c164312 100644 --- a/src/common/scripting/frontend/ast.cpp +++ b/src/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/src/common/scripting/frontend/zcc-parse.lemon b/src/common/scripting/frontend/zcc-parse.lemon index 295c58dd9af..a222afa608c 100644 --- a/src/common/scripting/frontend/zcc-parse.lemon +++ b/src/common/scripting/frontend/zcc-parse.lemon @@ -988,6 +988,63 @@ 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; +} + aggregate_type(X) ::= CLASS(T) class_restrictor(A). /* class */ { NEW_AST_NODE(ClassType,cls,T); @@ -1408,6 +1465,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 +1485,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/src/common/scripting/frontend/zcc_compile.cpp b/src/common/scripting/frontend/zcc_compile.cpp index fbe3d80ddce..bcfcde57ae4 100644 --- a/src/common/scripting/frontend/zcc_compile.cpp +++ b/src/common/scripting/frontend/zcc_compile.cpp @@ -2017,6 +2017,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); @@ -2985,6 +3031,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/src/common/scripting/frontend/zcc_exprlist.h b/src/common/scripting/frontend/zcc_exprlist.h index faf6af6a43d..f05758379a2 100644 --- a/src/common/scripting/frontend/zcc_exprlist.h +++ b/src/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/src/common/scripting/frontend/zcc_parser.cpp b/src/common/scripting/frontend/zcc_parser.cpp index e689d424949..24b2ecb1f3d 100644 --- a/src/common/scripting/frontend/zcc_parser.cpp +++ b/src/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/src/common/scripting/frontend/zcc_parser.h b/src/common/scripting/frontend/zcc_parser.h index e4e4ea36352..c9c1a2933d1 100644 --- a/src/common/scripting/frontend/zcc_parser.h +++ b/src/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/src/common/scripting/interface/vmnatives.cpp b/src/common/scripting/interface/vmnatives.cpp index daed2f091d4..00e110b718c 100644 --- a/src/common/scripting/interface/vmnatives.cpp +++ b/src/common/scripting/interface/vmnatives.cpp @@ -1348,3 +1348,35 @@ 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->GetImplicitArgs() == 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())); +} + +/* +PFunction * FindMethodPointer(PClass * cls, int fn_name) +{ + auto fn = dyn_cast(cls->FindSymbol(ENamedName(fn_name), true)); + return (fn && fn->GetImplicitArgs() == 1) ? fn : nullptr; +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, FindMethod, FindMethodPointer) +{ + PARAM_PROLOGUE; + PARAM_CLASS(cls, DObject); + PARAM_NAME(fn); + + ACTION_RETURN_POINTER(FindMethodPointer(cls, fn.GetIndex())); +} +*/ diff --git a/src/scripting/backend/codegen_doom.cpp b/src/scripting/backend/codegen_doom.cpp index f93422e30b8..7aa0474c811 100644 --- a/src/scripting/backend/codegen_doom.cpp +++ b/src/scripting/backend/codegen_doom.cpp @@ -309,7 +309,8 @@ static bool UnravelVarArgAJump(FxVMFunctionCall *func, FCompileContext &ctx) static bool AJumpProcessing(FxVMFunctionCall *func, FCompileContext &ctx) { // Unfortunately the PrintableName is the only safe thing to catch this special case here. - if (stricmp(func->Function->Variants[0].Implementation->QualifiedName, "Actor.A_Jump") == 0) + // [RL0] It's not valid to access Variant::Implementation on function pointer calls, so skip this + if (!func->FnPtrCall && stricmp(func->Function->Variants[0].Implementation->QualifiedName, "Actor.A_Jump") == 0) { // Unravel the varargs part of this function here so that the VM->native interface does not have to deal with it anymore. if (func->ArgList.Size() > 2) diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 409acc2e3fb..27888cca959 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -1,4 +1,4 @@ -version "4.10" +version "4.11" // Generic engine code #include "zscript/engine/base.zs" diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index dd9813ae6bd..dae12d37bb3 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -748,11 +748,15 @@ class Object native private native static void BuiltinRandomSeed(voidptr rng, int seed); private native static Class BuiltinNameToClass(Name nm, Class filter); private native static Object BuiltinClassCast(Object inptr, Class test); + private native static Function BuiltinFunctionPtrCast(Function inptr, voidptr newtype); native static uint MSTime(); native static double MSTimeF(); native vararg static void ThrowAbortException(String fmt, ...); + native static Function FindFunction(Class cls, Name fn); + //native static Method FindMethod(Class cls, Name fn); + native virtualscope void Destroy(); // This does not call into the native method of the same name to avoid problems with objects that get garbage collected late on shutdown.