810 lines
30 KiB
C++
810 lines
30 KiB
C++
/*
|
|
AngelCode Scripting Library
|
|
Copyright (c) 2003-2021 Andreas Jonsson
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any
|
|
damages arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
purpose, including commercial applications, and to alter it and
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you
|
|
must not claim that you wrote the original software. If you use
|
|
this software in a product, an acknowledgment in the product
|
|
documentation would be appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
must not be misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
distribution.
|
|
|
|
The original version of this library can be located at:
|
|
http://www.angelcode.com/angelscript/
|
|
|
|
Andreas Jonsson
|
|
andreas@angelcode.com
|
|
*/
|
|
|
|
|
|
//
|
|
// as_callfunc.cpp
|
|
//
|
|
// These functions handle the actual calling of system functions
|
|
//
|
|
|
|
|
|
|
|
#include "as_config.h"
|
|
#include "as_callfunc.h"
|
|
#include "as_scriptengine.h"
|
|
#include "as_texts.h"
|
|
#include "as_context.h"
|
|
|
|
BEGIN_AS_NAMESPACE
|
|
|
|
// ref: Member Function Pointers and the Fastest Possible C++ Delegates
|
|
// describes the structure of class method pointers for most compilers
|
|
// http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
|
|
|
|
// ref: The code comments for ItaniumCXXABI::EmitLoadOfMemberFunctionPointer in the LLVM compiler
|
|
// describes the structure for class method pointers on Itanium and arm64 ABI
|
|
// http://clang.llvm.org/doxygen/CodeGen_2ItaniumCXXABI_8cpp_source.html#l00937
|
|
|
|
int DetectCallingConvention(bool isMethod, const asSFuncPtr &ptr, int callConv, void *auxiliary, asSSystemFunctionInterface *internal) {
|
|
internal->Clear();
|
|
internal->func = ptr.ptr.f.func;
|
|
internal->auxiliary = 0;
|
|
|
|
// Was a compatible calling convention specified?
|
|
if (internal->func) {
|
|
if (ptr.flag == 1 && callConv != asCALL_GENERIC)
|
|
return asWRONG_CALLING_CONV;
|
|
else if (ptr.flag == 2 && (callConv == asCALL_GENERIC || callConv == asCALL_THISCALL || callConv == asCALL_THISCALL_ASGLOBAL || callConv == asCALL_THISCALL_OBJFIRST || callConv == asCALL_THISCALL_OBJLAST))
|
|
return asWRONG_CALLING_CONV;
|
|
else if (ptr.flag == 3 && !(callConv == asCALL_THISCALL || callConv == asCALL_THISCALL_ASGLOBAL || callConv == asCALL_THISCALL_OBJFIRST || callConv == asCALL_THISCALL_OBJLAST))
|
|
return asWRONG_CALLING_CONV;
|
|
}
|
|
|
|
asDWORD base = callConv;
|
|
if (!isMethod) {
|
|
if (base == asCALL_CDECL)
|
|
internal->callConv = ICC_CDECL;
|
|
else if (base == asCALL_STDCALL)
|
|
internal->callConv = ICC_STDCALL;
|
|
else if (base == asCALL_THISCALL_ASGLOBAL) {
|
|
if (auxiliary == 0)
|
|
return asINVALID_ARG;
|
|
internal->auxiliary = auxiliary;
|
|
internal->callConv = ICC_THISCALL;
|
|
|
|
// This is really a thiscall, so it is necessary to check for virtual method pointers
|
|
base = asCALL_THISCALL;
|
|
isMethod = true;
|
|
} else if (base == asCALL_GENERIC) {
|
|
internal->callConv = ICC_GENERIC_FUNC;
|
|
|
|
// The auxiliary object is optional for generic calling convention
|
|
internal->auxiliary = auxiliary;
|
|
} else
|
|
return asNOT_SUPPORTED;
|
|
}
|
|
|
|
if (isMethod) {
|
|
#ifndef AS_NO_CLASS_METHODS
|
|
if (base == asCALL_THISCALL || base == asCALL_THISCALL_OBJFIRST || base == asCALL_THISCALL_OBJLAST) {
|
|
internalCallConv thisCallConv;
|
|
if (base == asCALL_THISCALL) {
|
|
if (callConv != asCALL_THISCALL_ASGLOBAL && auxiliary)
|
|
return asINVALID_ARG;
|
|
|
|
thisCallConv = ICC_THISCALL;
|
|
} else {
|
|
#ifdef AS_NO_THISCALL_FUNCTOR_METHOD
|
|
return asNOT_SUPPORTED;
|
|
#else
|
|
if (auxiliary == 0)
|
|
return asINVALID_ARG;
|
|
|
|
internal->auxiliary = auxiliary;
|
|
if (base == asCALL_THISCALL_OBJFIRST)
|
|
thisCallConv = ICC_THISCALL_OBJFIRST;
|
|
else //if( base == asCALL_THISCALL_OBJLAST )
|
|
thisCallConv = ICC_THISCALL_OBJLAST;
|
|
#endif
|
|
}
|
|
|
|
internal->callConv = thisCallConv;
|
|
#ifdef GNU_STYLE_VIRTUAL_METHOD
|
|
if ((size_t(ptr.ptr.f.func) & 1))
|
|
internal->callConv = (internalCallConv)(thisCallConv + 2);
|
|
#endif
|
|
internal->baseOffset = (int)MULTI_BASE_OFFSET(ptr);
|
|
#if (defined(AS_ARM64) || defined(AS_ARM) || defined(AS_MIPS)) && (defined(__GNUC__) || defined(AS_PSVITA))
|
|
// As the least significant bit in func is used to switch to THUMB mode
|
|
// on ARM processors, the LSB in the __delta variable is used instead of
|
|
// the one in __pfn on ARM processors.
|
|
// MIPS also appear to use the base offset to indicate virtual method.
|
|
if ((size_t(internal->baseOffset) & 1))
|
|
internal->callConv = (internalCallConv)(thisCallConv + 2);
|
|
#endif
|
|
|
|
#ifdef HAVE_VIRTUAL_BASE_OFFSET
|
|
// We don't support virtual inheritance
|
|
if (VIRTUAL_BASE_OFFSET(ptr) != 0)
|
|
return asNOT_SUPPORTED;
|
|
#endif
|
|
} else
|
|
#endif
|
|
if (base == asCALL_CDECL_OBJLAST)
|
|
internal->callConv = ICC_CDECL_OBJLAST;
|
|
else if (base == asCALL_CDECL_OBJFIRST)
|
|
internal->callConv = ICC_CDECL_OBJFIRST;
|
|
else if (base == asCALL_GENERIC) {
|
|
internal->callConv = ICC_GENERIC_METHOD;
|
|
internal->auxiliary = auxiliary;
|
|
} else
|
|
return asNOT_SUPPORTED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// This function should prepare system functions so that it will be faster to call them
|
|
int PrepareSystemFunctionGeneric(asCScriptFunction *func, asSSystemFunctionInterface *internal, asCScriptEngine *engine) {
|
|
asASSERT(internal->callConv == ICC_GENERIC_METHOD || internal->callConv == ICC_GENERIC_FUNC);
|
|
|
|
// Calculate the size needed for the parameters
|
|
internal->paramSize = func->GetSpaceNeededForArguments();
|
|
|
|
// Prepare the clean up instructions for the function arguments
|
|
internal->cleanArgs.SetLength(0);
|
|
int offset = 0;
|
|
for (asUINT n = 0; n < func->parameterTypes.GetLength(); n++) {
|
|
asCDataType &dt = func->parameterTypes[n];
|
|
|
|
if ((dt.IsObject() || dt.IsFuncdef()) && !dt.IsReference()) {
|
|
if (dt.IsFuncdef()) {
|
|
// If the generic call mode is set to old behaviour then always release handles
|
|
// else only release the handle if the function is declared with auto handles
|
|
if (engine->ep.genericCallMode == 0 || (internal->paramAutoHandles.GetLength() > n && internal->paramAutoHandles[n])) {
|
|
asSSystemFunctionInterface::SClean clean;
|
|
clean.op = 0; // call release
|
|
clean.ot = &engine->functionBehaviours;
|
|
clean.off = short(offset);
|
|
internal->cleanArgs.PushLast(clean);
|
|
}
|
|
} else if (dt.GetTypeInfo()->flags & asOBJ_REF) {
|
|
// If the generic call mode is set to old behaviour then always release handles
|
|
// else only release the handle if the function is declared with auto handles
|
|
if (!dt.IsObjectHandle() ||
|
|
engine->ep.genericCallMode == 0 ||
|
|
(internal->paramAutoHandles.GetLength() > n && internal->paramAutoHandles[n])) {
|
|
asSTypeBehaviour *beh = &CastToObjectType(dt.GetTypeInfo())->beh;
|
|
asASSERT((dt.GetTypeInfo()->flags & asOBJ_NOCOUNT) || beh->release);
|
|
if (beh->release) {
|
|
asSSystemFunctionInterface::SClean clean;
|
|
clean.op = 0; // call release
|
|
clean.ot = CastToObjectType(dt.GetTypeInfo());
|
|
clean.off = short(offset);
|
|
internal->cleanArgs.PushLast(clean);
|
|
}
|
|
}
|
|
} else {
|
|
asSSystemFunctionInterface::SClean clean;
|
|
clean.op = 1; // call free
|
|
clean.ot = CastToObjectType(dt.GetTypeInfo());
|
|
clean.off = short(offset);
|
|
|
|
// Call the destructor then free the memory
|
|
asSTypeBehaviour *beh = &CastToObjectType(dt.GetTypeInfo())->beh;
|
|
if (beh->destruct)
|
|
clean.op = 2; // call destruct, then free
|
|
|
|
internal->cleanArgs.PushLast(clean);
|
|
}
|
|
}
|
|
|
|
if (dt.IsObject() && !dt.IsObjectHandle() && !dt.IsReference())
|
|
offset += AS_PTR_SIZE;
|
|
else
|
|
offset += dt.GetSizeOnStackDWords();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// This function should prepare system functions so that it will be faster to call them
|
|
int PrepareSystemFunction(asCScriptFunction *func, asSSystemFunctionInterface *internal, asCScriptEngine *engine) {
|
|
#ifdef AS_MAX_PORTABILITY
|
|
UNUSED_VAR(func);
|
|
UNUSED_VAR(internal);
|
|
UNUSED_VAR(engine);
|
|
|
|
// This should never happen, as when AS_MAX_PORTABILITY is on, all functions
|
|
// are asCALL_GENERIC, which are prepared by PrepareSystemFunctionGeneric
|
|
asASSERT(false);
|
|
#else
|
|
// References are always returned as primitive data
|
|
if (func->returnType.IsReference() || func->returnType.IsObjectHandle()) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
internal->hostReturnFloat = false;
|
|
}
|
|
// Registered types have special flags that determine how they are returned
|
|
else if (func->returnType.IsObject()) {
|
|
asDWORD objType = func->returnType.GetTypeInfo()->flags;
|
|
|
|
// Only value types can be returned by value
|
|
asASSERT(objType & asOBJ_VALUE);
|
|
|
|
if (!(objType & (asOBJ_APP_CLASS | asOBJ_APP_PRIMITIVE | asOBJ_APP_FLOAT | asOBJ_APP_ARRAY))) {
|
|
// If the return is by value then we need to know the true type
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, func->GetDeclarationStr().AddressOf());
|
|
|
|
asCString str;
|
|
str.Format(TXT_CANNOT_RET_TYPE_s_BY_VAL, func->returnType.GetTypeInfo()->name.AddressOf());
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
|
|
engine->ConfigError(asINVALID_CONFIGURATION, 0, 0, 0);
|
|
} else if (objType & asOBJ_APP_ARRAY) {
|
|
// Array types are always returned in memory
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
internal->hostReturnFloat = false;
|
|
} else if (objType & asOBJ_APP_CLASS) {
|
|
internal->hostReturnFloat = false;
|
|
if (objType & COMPLEX_RETURN_MASK) {
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
} else {
|
|
#ifdef HAS_128_BIT_PRIMITIVES
|
|
if (func->returnType.GetSizeInMemoryDWords() > 4)
|
|
#else
|
|
if (func->returnType.GetSizeInMemoryDWords() > 2)
|
|
#endif
|
|
{
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
} else {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = func->returnType.GetSizeInMemoryDWords();
|
|
#ifdef SPLIT_OBJS_BY_MEMBER_TYPES
|
|
if (func->returnType.GetTypeInfo()->flags & asOBJ_APP_CLASS_ALLFLOATS)
|
|
internal->hostReturnFloat = true;
|
|
#endif
|
|
}
|
|
|
|
#ifdef THISCALL_RETURN_SIMPLE_IN_MEMORY
|
|
if ((internal->callConv == ICC_THISCALL ||
|
|
#ifdef AS_NO_THISCALL_FUNCTOR_METHOD
|
|
internal->callConv == ICC_VIRTUAL_THISCALL) &&
|
|
#else
|
|
internal->callConv == ICC_VIRTUAL_THISCALL ||
|
|
internal->callConv == ICC_THISCALL_OBJFIRST ||
|
|
internal->callConv == ICC_THISCALL_OBJLAST) &&
|
|
#endif
|
|
func->returnType.GetSizeInMemoryDWords() >= THISCALL_RETURN_SIMPLE_IN_MEMORY_MIN_SIZE) {
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
}
|
|
#endif
|
|
#ifdef CDECL_RETURN_SIMPLE_IN_MEMORY
|
|
if ((internal->callConv == ICC_CDECL ||
|
|
internal->callConv == ICC_CDECL_OBJLAST ||
|
|
internal->callConv == ICC_CDECL_OBJFIRST) &&
|
|
func->returnType.GetSizeInMemoryDWords() >= CDECL_RETURN_SIMPLE_IN_MEMORY_MIN_SIZE) {
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
}
|
|
#endif
|
|
#ifdef STDCALL_RETURN_SIMPLE_IN_MEMORY
|
|
if (internal->callConv == ICC_STDCALL &&
|
|
func->returnType.GetSizeInMemoryDWords() >= STDCALL_RETURN_SIMPLE_IN_MEMORY_MIN_SIZE) {
|
|
internal->hostReturnInMemory = true;
|
|
internal->hostReturnSize = sizeof(void *) / 4;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef SPLIT_OBJS_BY_MEMBER_TYPES
|
|
// It's not safe to return objects by value because different registers
|
|
// will be used depending on the memory layout of the object.
|
|
// Ref: http://www.x86-64.org/documentation/abi.pdf
|
|
// Ref: http://www.agner.org/optimize/calling_conventions.pdf
|
|
// If the application informs that the class should be treated as all integers, then we allow it
|
|
if (!internal->hostReturnInMemory &&
|
|
!(func->returnType.GetTypeInfo()->flags & (asOBJ_APP_CLASS_ALLINTS | asOBJ_APP_CLASS_ALLFLOATS))) {
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, func->GetDeclarationStr().AddressOf());
|
|
|
|
asCString str;
|
|
str.Format(TXT_DONT_SUPPORT_RET_TYPE_s_BY_VAL, func->returnType.Format(func->nameSpace).AddressOf());
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
|
|
engine->ConfigError(asINVALID_CONFIGURATION, 0, 0, 0);
|
|
}
|
|
#endif
|
|
} else if (objType & asOBJ_APP_PRIMITIVE) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = func->returnType.GetSizeInMemoryDWords();
|
|
internal->hostReturnFloat = false;
|
|
} else if (objType & asOBJ_APP_FLOAT) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = func->returnType.GetSizeInMemoryDWords();
|
|
internal->hostReturnFloat = true;
|
|
}
|
|
}
|
|
// Primitive types can easily be determined
|
|
#ifdef HAS_128_BIT_PRIMITIVES
|
|
else if (func->returnType.GetSizeInMemoryDWords() > 4) {
|
|
// Shouldn't be possible to get here
|
|
asASSERT(false);
|
|
} else if (func->returnType.GetSizeInMemoryDWords() == 4) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = 4;
|
|
internal->hostReturnFloat = false;
|
|
}
|
|
#else
|
|
else if (func->returnType.GetSizeInMemoryDWords() > 2) {
|
|
// Shouldn't be possible to get here
|
|
asASSERT(false);
|
|
}
|
|
#endif
|
|
else if (func->returnType.GetSizeInMemoryDWords() == 2) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = 2;
|
|
internal->hostReturnFloat = func->returnType.IsEqualExceptConst(asCDataType::CreatePrimitive(ttDouble, true));
|
|
} else if (func->returnType.GetSizeInMemoryDWords() == 1) {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = 1;
|
|
internal->hostReturnFloat = func->returnType.IsEqualExceptConst(asCDataType::CreatePrimitive(ttFloat, true));
|
|
} else {
|
|
internal->hostReturnInMemory = false;
|
|
internal->hostReturnSize = 0;
|
|
internal->hostReturnFloat = false;
|
|
}
|
|
|
|
// Calculate the size needed for the parameters
|
|
internal->paramSize = func->GetSpaceNeededForArguments();
|
|
|
|
// Verify if the function takes any objects by value
|
|
asUINT n;
|
|
internal->takesObjByVal = false;
|
|
for (n = 0; n < func->parameterTypes.GetLength(); n++) {
|
|
if (func->parameterTypes[n].IsObject() && !func->parameterTypes[n].IsObjectHandle() && !func->parameterTypes[n].IsReference()) {
|
|
internal->takesObjByVal = true;
|
|
|
|
// Can't pass objects by value unless the application type is informed
|
|
if (!(func->parameterTypes[n].GetTypeInfo()->flags & (asOBJ_APP_CLASS | asOBJ_APP_PRIMITIVE | asOBJ_APP_FLOAT | asOBJ_APP_ARRAY))) {
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, func->GetDeclarationStr().AddressOf());
|
|
|
|
asCString str;
|
|
str.Format(TXT_CANNOT_PASS_TYPE_s_BY_VAL, func->parameterTypes[n].GetTypeInfo()->name.AddressOf());
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
|
|
engine->ConfigError(asINVALID_CONFIGURATION, 0, 0, 0);
|
|
}
|
|
|
|
|
|
#ifdef SPLIT_OBJS_BY_MEMBER_TYPES
|
|
// It's not safe to pass objects by value because different registers
|
|
// will be used depending on the memory layout of the object
|
|
// Ref: http://www.x86-64.org/documentation/abi.pdf
|
|
// Ref: http://www.agner.org/optimize/calling_conventions.pdf
|
|
if (
|
|
#ifdef COMPLEX_OBJS_PASSED_BY_REF
|
|
!(func->parameterTypes[n].GetTypeInfo()->flags & COMPLEX_MASK) &&
|
|
#endif
|
|
#ifdef LARGE_OBJS_PASS_BY_REF
|
|
func->parameterTypes[n].GetSizeInMemoryDWords() < AS_LARGE_OBJ_MIN_SIZE &&
|
|
#endif
|
|
!(func->parameterTypes[n].GetTypeInfo()->flags & (asOBJ_APP_PRIMITIVE | asOBJ_APP_FLOAT | asOBJ_APP_CLASS_ALLINTS | asOBJ_APP_CLASS_ALLFLOATS))) {
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, func->GetDeclarationStr().AddressOf());
|
|
|
|
asCString str;
|
|
str.Format(TXT_DONT_SUPPORT_TYPE_s_BY_VAL, func->parameterTypes[n].GetTypeInfo()->name.AddressOf());
|
|
engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
|
|
engine->ConfigError(asINVALID_CONFIGURATION, 0, 0, 0);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Prepare the clean up instructions for the function arguments
|
|
internal->cleanArgs.SetLength(0);
|
|
int offset = 0;
|
|
for (n = 0; n < func->parameterTypes.GetLength(); n++) {
|
|
asCDataType &dt = func->parameterTypes[n];
|
|
|
|
#if defined(COMPLEX_OBJS_PASSED_BY_REF) || defined(AS_LARGE_OBJS_PASSED_BY_REF)
|
|
bool needFree = false;
|
|
#ifdef COMPLEX_OBJS_PASSED_BY_REF
|
|
if (dt.GetTypeInfo() && dt.GetTypeInfo()->flags & COMPLEX_MASK) needFree = true;
|
|
#endif
|
|
#ifdef AS_LARGE_OBJS_PASSED_BY_REF
|
|
if (dt.GetSizeInMemoryDWords() >= AS_LARGE_OBJ_MIN_SIZE) needFree = true;
|
|
#endif
|
|
if (needFree &&
|
|
dt.IsObject() &&
|
|
!dt.IsObjectHandle() &&
|
|
!dt.IsReference()) {
|
|
asSSystemFunctionInterface::SClean clean;
|
|
clean.op = 1; // call free
|
|
clean.ot = CastToObjectType(dt.GetTypeInfo());
|
|
clean.off = short(offset);
|
|
|
|
#ifndef AS_CALLEE_DESTROY_OBJ_BY_VAL
|
|
// If the called function doesn't destroy objects passed by value we must do so here
|
|
asSTypeBehaviour *beh = &CastToObjectType(dt.GetTypeInfo())->beh;
|
|
if (beh->destruct)
|
|
clean.op = 2; // call destruct, then free
|
|
#endif
|
|
|
|
internal->cleanArgs.PushLast(clean);
|
|
}
|
|
#endif
|
|
|
|
if (n < internal->paramAutoHandles.GetLength() && internal->paramAutoHandles[n]) {
|
|
asSSystemFunctionInterface::SClean clean;
|
|
clean.op = 0; // call release
|
|
if (dt.IsFuncdef())
|
|
clean.ot = &engine->functionBehaviours;
|
|
else
|
|
clean.ot = CastToObjectType(dt.GetTypeInfo());
|
|
clean.off = short(offset);
|
|
internal->cleanArgs.PushLast(clean);
|
|
}
|
|
|
|
if (dt.IsObject() && !dt.IsObjectHandle() && !dt.IsReference())
|
|
offset += AS_PTR_SIZE;
|
|
else
|
|
offset += dt.GetSizeOnStackDWords();
|
|
}
|
|
#endif // !defined(AS_MAX_PORTABILITY)
|
|
return 0;
|
|
}
|
|
|
|
#ifdef AS_MAX_PORTABILITY
|
|
|
|
int CallSystemFunction(int id, asCContext *context) {
|
|
asCScriptEngine *engine = context->m_engine;
|
|
asCScriptFunction *func = engine->scriptFunctions[id];
|
|
asSSystemFunctionInterface *sysFunc = func->sysFuncIntf;
|
|
int callConv = sysFunc->callConv;
|
|
if (callConv == ICC_GENERIC_FUNC || callConv == ICC_GENERIC_METHOD)
|
|
return context->CallGeneric(func);
|
|
|
|
context->SetInternalException(TXT_INVALID_CALLING_CONVENTION);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
//
|
|
// CallSystemFunctionNative
|
|
//
|
|
// This function is implemented for each platform where the native calling conventions is supported.
|
|
// See the various as_callfunc_xxx.cpp files for their implementation. It is responsible for preparing
|
|
// the arguments for the function call, calling the function, and then retrieving the return value.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// context - This is the context that can be used to retrieve specific information from the engine
|
|
// descr - This is the script function object that holds the information on how to call the function
|
|
// obj - This is the object pointer, if the call is for a class method, otherwise it is null
|
|
// args - This is the function arguments, which are packed as in AngelScript
|
|
// retPointer - This points to a the memory buffer where the return object is to be placed, if the function returns the value in memory rather than in registers
|
|
// retQW2 - This output parameter should be used if the function returns a value larger than 64bits in registers
|
|
// secondObj - This is the object pointer that the proxy method should invoke its method on when the call convention is THISCALL_OBJFIRST/LAST
|
|
//
|
|
// Return value:
|
|
//
|
|
// The function should return the value that is returned in registers.
|
|
asQWORD CallSystemFunctionNative(asCContext *context, asCScriptFunction *descr, void *obj, asDWORD *args, void *retPointer, asQWORD &retQW2, void *secondObj);
|
|
|
|
|
|
int CallSystemFunction(int id, asCContext *context) {
|
|
asCScriptEngine *engine = context->m_engine;
|
|
asCScriptFunction *descr = engine->scriptFunctions[id];
|
|
asSSystemFunctionInterface *sysFunc = descr->sysFuncIntf;
|
|
|
|
int callConv = sysFunc->callConv;
|
|
if (callConv == ICC_GENERIC_FUNC || callConv == ICC_GENERIC_METHOD)
|
|
return context->CallGeneric(descr);
|
|
|
|
asQWORD retQW = 0;
|
|
asQWORD retQW2 = 0;
|
|
asDWORD *args = context->m_regs.stackPointer;
|
|
void *retPointer = 0;
|
|
int popSize = sysFunc->paramSize;
|
|
|
|
// TODO: clean-up: CallSystemFunctionNative should have two arguments for object pointers
|
|
// objForThiscall is the object pointer that should be used for the thiscall
|
|
// objForArg is the object pointer that should be passed as argument when using OBJFIRST or OBJLAST
|
|
|
|
// Used to save two object pointers with THISCALL_OBJLAST or THISCALL_OBJFIRST
|
|
void *obj = 0;
|
|
void *secondObj = 0;
|
|
|
|
#ifdef AS_NO_THISCALL_FUNCTOR_METHOD
|
|
if (callConv >= ICC_THISCALL) {
|
|
if (sysFunc->auxiliary) {
|
|
// This class method is being called as if it is a global function
|
|
obj = sysFunc->auxiliary;
|
|
} else {
|
|
// The object pointer should be popped from the context stack
|
|
popSize += AS_PTR_SIZE;
|
|
|
|
// Check for null pointer
|
|
obj = (void *) * (asPWORD *)(args);
|
|
if (obj == 0) {
|
|
context->SetInternalException(TXT_NULL_POINTER_ACCESS);
|
|
return 0;
|
|
}
|
|
|
|
// Skip the object pointer
|
|
args += AS_PTR_SIZE;
|
|
}
|
|
|
|
// Add the base offset for multiple inheritance
|
|
#if (defined(__GNUC__) && (defined(AS_ARM64) || defined(AS_ARM) || defined(AS_MIPS))) || defined(AS_PSVITA)
|
|
// On GNUC + ARM the lsb of the offset is used to indicate a virtual function
|
|
// and the whole offset is thus shifted one bit left to keep the original
|
|
// offset resolution
|
|
// MIPS also work like ARM in this regard
|
|
obj = (void *)(asPWORD(obj) + (sysFunc->baseOffset >> 1));
|
|
#else
|
|
obj = (void *)(asPWORD(obj) + sysFunc->baseOffset);
|
|
#endif
|
|
}
|
|
#else // !defined(AS_NO_THISCALL_FUNCTOR_METHOD)
|
|
|
|
if (callConv >= ICC_THISCALL) {
|
|
bool continueCheck = true; // True if need check objectPointer or context stack for object
|
|
int continueCheckIndex = 0; // Index into objectsPtrs to save the object if continueCheck
|
|
|
|
if (callConv >= ICC_THISCALL_OBJLAST) {
|
|
asASSERT(sysFunc->auxiliary != 0);
|
|
// This class method is being called as object method (sysFunc->auxiliary must be set).
|
|
obj = sysFunc->auxiliary;
|
|
continueCheckIndex = 1;
|
|
} else if (sysFunc->auxiliary) {
|
|
// This class method is being called as if it is a global function
|
|
obj = sysFunc->auxiliary;
|
|
continueCheck = false;
|
|
}
|
|
|
|
if (obj) {
|
|
// Add the base offset for multiple inheritance
|
|
#if (defined(__GNUC__) && (defined(AS_ARM64) || defined(AS_ARM) || defined(AS_MIPS))) || defined(AS_PSVITA)
|
|
// On GNUC + ARM the lsb of the offset is used to indicate a virtual function
|
|
// and the whole offset is thus shifted one bit left to keep the original
|
|
// offset resolution
|
|
// MIPS also work like ARM in this regard
|
|
obj = (void *)(asPWORD(obj) + (sysFunc->baseOffset >> 1));
|
|
#else
|
|
obj = (void *)(asPWORD(obj) + sysFunc->baseOffset);
|
|
#endif
|
|
}
|
|
|
|
if (continueCheck) {
|
|
void *tempPtr = 0;
|
|
|
|
// The object pointer should be popped from the context stack
|
|
popSize += AS_PTR_SIZE;
|
|
|
|
// Check for null pointer
|
|
tempPtr = (void *) * (asPWORD *)(args);
|
|
if (tempPtr == 0) {
|
|
context->SetInternalException(TXT_NULL_POINTER_ACCESS);
|
|
return 0;
|
|
}
|
|
|
|
// Add the base offset for multiple inheritance
|
|
#if (defined(__GNUC__) && (defined(AS_ARM64) || defined(AS_ARM) || defined(AS_MIPS))) || defined(AS_PSVITA)
|
|
// On GNUC + ARM the lsb of the offset is used to indicate a virtual function
|
|
// and the whole offset is thus shifted one bit left to keep the original
|
|
// offset resolution
|
|
// MIPS also work like ARM in this regard
|
|
tempPtr = (void *)(asPWORD(tempPtr) + (sysFunc->baseOffset >> 1));
|
|
#else
|
|
tempPtr = (void *)(asPWORD(tempPtr) + sysFunc->baseOffset);
|
|
#endif
|
|
|
|
// Skip the object pointer
|
|
args += AS_PTR_SIZE;
|
|
|
|
if (continueCheckIndex)
|
|
secondObj = tempPtr;
|
|
else {
|
|
asASSERT(obj == 0);
|
|
obj = tempPtr;
|
|
}
|
|
}
|
|
}
|
|
#endif // AS_NO_THISCALL_FUNCTOR_METHOD
|
|
|
|
if (descr->DoesReturnOnStack()) {
|
|
// Get the address of the location for the return value from the stack
|
|
retPointer = (void *) * (asPWORD *)(args);
|
|
popSize += AS_PTR_SIZE;
|
|
args += AS_PTR_SIZE;
|
|
|
|
// When returning the value on the location allocated by the called
|
|
// we shouldn't set the object type in the register
|
|
context->m_regs.objectType = 0;
|
|
} else {
|
|
// Set the object type of the reference held in the register
|
|
context->m_regs.objectType = descr->returnType.GetTypeInfo();
|
|
}
|
|
|
|
// For composition we need to add the offset and/or dereference the pointer
|
|
if (obj) {
|
|
obj = (void *)((char *) obj + sysFunc->compositeOffset);
|
|
if (sysFunc->isCompositeIndirect) obj = *((void **)obj);
|
|
}
|
|
|
|
context->m_callingSystemFunction = descr;
|
|
bool cppException = false;
|
|
#ifdef AS_NO_EXCEPTIONS
|
|
retQW = CallSystemFunctionNative(context, descr, obj, args, sysFunc->hostReturnInMemory ? retPointer : 0, retQW2, secondObj);
|
|
#else
|
|
// This try/catch block is to catch potential exception that may
|
|
// be thrown by the registered function. The implementation of the
|
|
// CallSystemFunctionNative() must make sure not to have any manual
|
|
// clean-up after the call to the real function, or that won't be
|
|
// executed in case of an exception.
|
|
try {
|
|
retQW = CallSystemFunctionNative(context, descr, obj, args, sysFunc->hostReturnInMemory ? retPointer : 0, retQW2, secondObj);
|
|
} catch (...) {
|
|
cppException = true;
|
|
|
|
// Convert the exception to a script exception so the VM can
|
|
// properly report the error to the application and then clean up
|
|
context->HandleAppException();
|
|
}
|
|
#endif
|
|
context->m_callingSystemFunction = 0;
|
|
|
|
// Store the returned value in our stack
|
|
if ((descr->returnType.IsObject() || descr->returnType.IsFuncdef()) && !descr->returnType.IsReference()) {
|
|
if (descr->returnType.IsObjectHandle()) {
|
|
#if defined(AS_BIG_ENDIAN) && AS_PTR_SIZE == 1
|
|
// Since we're treating the system function as if it is returning a QWORD we are
|
|
// actually receiving the value in the high DWORD of retQW.
|
|
retQW >>= 32;
|
|
#endif
|
|
|
|
context->m_regs.objectRegister = (void *)(asPWORD)retQW;
|
|
|
|
if (sysFunc->returnAutoHandle && context->m_regs.objectRegister) {
|
|
asASSERT(!(descr->returnType.GetTypeInfo()->flags & asOBJ_NOCOUNT));
|
|
engine->CallObjectMethod(context->m_regs.objectRegister, CastToObjectType(descr->returnType.GetTypeInfo())->beh.addref);
|
|
}
|
|
} else {
|
|
asASSERT(retPointer);
|
|
|
|
if (!sysFunc->hostReturnInMemory) {
|
|
// Copy the returned value to the pointer sent by the script engine
|
|
if (sysFunc->hostReturnSize == 1) {
|
|
#if defined(AS_BIG_ENDIAN) && AS_PTR_SIZE == 1
|
|
// Since we're treating the system function as if it is returning a QWORD we are
|
|
// actually receiving the value in the high DWORD of retQW.
|
|
retQW >>= 32;
|
|
#endif
|
|
|
|
*(asDWORD *)retPointer = (asDWORD)retQW;
|
|
} else if (sysFunc->hostReturnSize == 2)
|
|
*(asQWORD *)retPointer = retQW;
|
|
else if (sysFunc->hostReturnSize == 3) {
|
|
*(asQWORD *)retPointer = retQW;
|
|
*(((asDWORD *)retPointer) + 2) = (asDWORD)retQW2;
|
|
} else { // if( sysFunc->hostReturnSize == 4 )
|
|
*(asQWORD *)retPointer = retQW;
|
|
*(((asQWORD *)retPointer) + 1) = retQW2;
|
|
}
|
|
}
|
|
|
|
if (context->m_status == asEXECUTION_EXCEPTION && !cppException) {
|
|
// If the function raised a script exception it really shouldn't have
|
|
// initialized the object. However, as it is a soft exception there is
|
|
// no way for the application to not return a value, so instead we simply
|
|
// destroy it here, to pretend it was never created.
|
|
if (CastToObjectType(descr->returnType.GetTypeInfo())->beh.destruct)
|
|
engine->CallObjectMethod(retPointer, CastToObjectType(descr->returnType.GetTypeInfo())->beh.destruct);
|
|
}
|
|
}
|
|
} else {
|
|
// Store value in value register
|
|
if (sysFunc->hostReturnSize == 1) {
|
|
#if defined(AS_BIG_ENDIAN)
|
|
// Since we're treating the system function as if it is returning a QWORD we are
|
|
// actually receiving the value in the high DWORD of retQW.
|
|
retQW >>= 32;
|
|
|
|
// Due to endian issues we need to handle return values that are
|
|
// less than a DWORD (32 bits) in size specially
|
|
int numBytes = descr->returnType.GetSizeInMemoryBytes();
|
|
if (descr->returnType.IsReference()) numBytes = 4;
|
|
switch (numBytes) {
|
|
case 1: {
|
|
// 8 bits
|
|
asBYTE *val = (asBYTE *)&context->m_regs.valueRegister;
|
|
val[0] = (asBYTE)retQW;
|
|
val[1] = 0;
|
|
val[2] = 0;
|
|
val[3] = 0;
|
|
val[4] = 0;
|
|
val[5] = 0;
|
|
val[6] = 0;
|
|
val[7] = 0;
|
|
}
|
|
break;
|
|
case 2: {
|
|
// 16 bits
|
|
asWORD *val = (asWORD *)&context->m_regs.valueRegister;
|
|
val[0] = (asWORD)retQW;
|
|
val[1] = 0;
|
|
val[2] = 0;
|
|
val[3] = 0;
|
|
}
|
|
break;
|
|
default: {
|
|
// 32 bits
|
|
asDWORD *val = (asDWORD *)&context->m_regs.valueRegister;
|
|
val[0] = (asDWORD)retQW;
|
|
val[1] = 0;
|
|
}
|
|
break;
|
|
}
|
|
#else
|
|
*(asDWORD *)&context->m_regs.valueRegister = (asDWORD)retQW;
|
|
#endif
|
|
} else
|
|
context->m_regs.valueRegister = retQW;
|
|
}
|
|
|
|
// Clean up arguments
|
|
const asUINT cleanCount = sysFunc->cleanArgs.GetLength();
|
|
if (cleanCount) {
|
|
args = context->m_regs.stackPointer;
|
|
|
|
// Skip the hidden argument for the return pointer
|
|
// TODO: runtime optimize: This check and increment should have been done in PrepareSystemFunction
|
|
if (descr->DoesReturnOnStack())
|
|
args += AS_PTR_SIZE;
|
|
|
|
// Skip the object pointer on the stack
|
|
// TODO: runtime optimize: This check and increment should have been done in PrepareSystemFunction
|
|
if (callConv >= ICC_THISCALL && sysFunc->auxiliary == 0)
|
|
args += AS_PTR_SIZE;
|
|
|
|
asSSystemFunctionInterface::SClean *clean = sysFunc->cleanArgs.AddressOf();
|
|
for (asUINT n = 0; n < cleanCount; n++, clean++) {
|
|
void **addr = (void **)&args[clean->off];
|
|
if (clean->op == 0) {
|
|
if (*addr != 0) {
|
|
engine->CallObjectMethod(*addr, clean->ot->beh.release);
|
|
*addr = 0;
|
|
}
|
|
} else {
|
|
asASSERT(clean->op == 1 || clean->op == 2);
|
|
asASSERT(*addr);
|
|
|
|
if (clean->op == 2)
|
|
engine->CallObjectMethod(*addr, clean->ot->beh.destruct);
|
|
|
|
engine->CallFree(*addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return popSize;
|
|
}
|
|
|
|
#endif // AS_MAX_PORTABILITY
|
|
|
|
END_AS_NAMESPACE
|
|
|