/* AngelCode Scripting Library Copyright (c) 2003-2012 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 */ // Modified by Lasse Öörni for Urho3D // // as_callfunc_x86.cpp // // These functions handle the actual calling of system functions // #include "as_config.h" #ifndef AS_MAX_PORTABILITY #ifdef AS_X86 #include "as_callfunc.h" #include "as_scriptengine.h" #include "as_texts.h" #include "as_tokendef.h" BEGIN_AS_NAMESPACE // // With some compile level optimizations the functions don't clear the FPU // stack themselves. So we have to do it as part of calling the native functions, // as the compiler will not be able to predict when it is supposed to do it by // itself due to the dynamic nature of scripts // // - fninit clears the FPU stack and the FPU control word // - emms only clears the FPU stack, while preserving the FPU control word // // By default I use fninit as it seems to be what works for most people, // but some may find it necessary to define this as emms instead. // // TODO: Figure out when one or the other must be used, and a way to // configure this automatically in as_config.h // #ifndef CLEAR_FPU_STACK #define CLEAR_FPU_STACK fninit #endif // These macros are just to allow me to use the above macro in the GNUC style inline assembly #define _S(x) _TOSTRING(x) #define _TOSTRING(x) #x typedef asQWORD (*t_CallCDeclQW)(const asDWORD *, int, asFUNCTION_t); typedef asQWORD (*t_CallCDeclQWObj)(void *obj, const asDWORD *, int, asFUNCTION_t); typedef asDWORD (*t_CallCDeclRetByRef)(const asDWORD *, int, asFUNCTION_t, void *); typedef asDWORD (*t_CallCDeclObjRetByRef)(void *obj, const asDWORD *, int, asFUNCTION_t, void *); typedef asQWORD (*t_CallSTDCallQW)(const asDWORD *, int, asFUNCTION_t); typedef asQWORD (*t_CallThisCallQW)(const void *, const asDWORD *, int, asFUNCTION_t); typedef asDWORD (*t_CallThisCallRetByRef)(const void *, const asDWORD *, int, asFUNCTION_t, void *); // Prototypes void CallCDeclFunction(const asDWORD *args, int paramSize, asFUNCTION_t func); void CallCDeclFunctionObjLast(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func); void CallCDeclFunctionObjFirst(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func); void CallCDeclFunctionRetByRef_impl(const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr); void CallCDeclFunctionRetByRefObjLast_impl(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr); void CallCDeclFunctionRetByRefObjFirst_impl(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr); void CallSTDCallFunction(const asDWORD *args, int paramSize, asFUNCTION_t func); void CallThisCallFunction(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func); void CallThisCallFunctionRetByRef_impl(const void *, const asDWORD *, int, asFUNCTION_t, void *retPtr); // Initialize function pointers const t_CallCDeclQW CallCDeclFunctionQWord = (t_CallCDeclQW)CallCDeclFunction; const t_CallCDeclQWObj CallCDeclFunctionQWordObjLast = (t_CallCDeclQWObj)CallCDeclFunctionObjLast; const t_CallCDeclQWObj CallCDeclFunctionQWordObjFirst = (t_CallCDeclQWObj)CallCDeclFunctionObjFirst; const t_CallCDeclRetByRef CallCDeclFunctionRetByRef = (t_CallCDeclRetByRef)CallCDeclFunctionRetByRef_impl; const t_CallCDeclObjRetByRef CallCDeclFunctionRetByRefObjLast = (t_CallCDeclObjRetByRef)CallCDeclFunctionRetByRefObjLast_impl; const t_CallCDeclObjRetByRef CallCDeclFunctionRetByRefObjFirst = (t_CallCDeclObjRetByRef)CallCDeclFunctionRetByRefObjFirst_impl; const t_CallSTDCallQW CallSTDCallFunctionQWord = (t_CallSTDCallQW)CallSTDCallFunction; const t_CallThisCallQW CallThisCallFunctionQWord = (t_CallThisCallQW)CallThisCallFunction; const t_CallThisCallRetByRef CallThisCallFunctionRetByRef = (t_CallThisCallRetByRef)CallThisCallFunctionRetByRef_impl; asDWORD GetReturnedFloat(); asQWORD GetReturnedDouble(); asQWORD CallSystemFunctionNative(asCContext *context, asCScriptFunction *descr, void *obj, asDWORD *args, void *retPointer, asQWORD &/*retQW2*/) { asCScriptEngine *engine = context->engine; asSSystemFunctionInterface *sysFunc = descr->sysFuncIntf; // This needs to be volatile, as GCC 4.2 is optimizing // away the assignment of the return code on Mac OS X. // ref: http://www.gamedev.net/topic/621357-porting-dustforce-to-os-x/ // Urho3D: do not apply this fix on Linux, as it causes problems in release builds #ifndef __linux__ volatile asQWORD retQW = 0; #else asQWORD retQW = 0; #endif // Prepare the parameters int paramSize = sysFunc->paramSize; asDWORD paramBuffer[64]; if( sysFunc->takesObjByVal ) { paramSize = 0; int spos = 0; int dpos = 1; for( asUINT n = 0; n < descr->parameterTypes.GetLength(); n++ ) { if( descr->parameterTypes[n].IsObject() && !descr->parameterTypes[n].IsObjectHandle() && !descr->parameterTypes[n].IsReference() ) { #ifdef COMPLEX_OBJS_PASSED_BY_REF if( descr->parameterTypes[n].GetObjectType()->flags & COMPLEX_MASK ) { paramBuffer[dpos++] = args[spos++]; paramSize++; } else #endif { // Copy the object's memory to the buffer memcpy(¶mBuffer[dpos], *(void**)(args+spos), descr->parameterTypes[n].GetSizeInMemoryBytes()); // Delete the original memory engine->CallFree(*(char**)(args+spos)); spos++; dpos += descr->parameterTypes[n].GetSizeInMemoryDWords(); paramSize += descr->parameterTypes[n].GetSizeInMemoryDWords(); } } else { // Copy the value directly paramBuffer[dpos++] = args[spos++]; if( descr->parameterTypes[n].GetSizeOnStackDWords() > 1 ) paramBuffer[dpos++] = args[spos++]; paramSize += descr->parameterTypes[n].GetSizeOnStackDWords(); } } // Keep a free location at the beginning args = ¶mBuffer[1]; } // Make the actual call asFUNCTION_t func = sysFunc->func; int callConv = sysFunc->callConv; if( sysFunc->hostReturnInMemory ) callConv++; switch( callConv ) { case ICC_CDECL: retQW = CallCDeclFunctionQWord(args, paramSize<<2, func); break; case ICC_CDECL_RETURNINMEM: retQW = CallCDeclFunctionRetByRef(args, paramSize<<2, func, retPointer); break; case ICC_STDCALL: retQW = CallSTDCallFunctionQWord(args, paramSize<<2, func); break; case ICC_STDCALL_RETURNINMEM: // Push the return pointer on the stack paramSize++; args--; *(asPWORD*)args = (size_t)retPointer; retQW = CallSTDCallFunctionQWord(args, paramSize<<2, func); break; case ICC_THISCALL: retQW = CallThisCallFunctionQWord(obj, args, paramSize<<2, func); break; case ICC_THISCALL_RETURNINMEM: retQW = CallThisCallFunctionRetByRef(obj, args, paramSize<<2, func, retPointer); break; case ICC_VIRTUAL_THISCALL: { // Get virtual function table from the object pointer asFUNCTION_t *vftable = *(asFUNCTION_t**)obj; retQW = CallThisCallFunctionQWord(obj, args, paramSize<<2, vftable[FuncPtrToUInt(func)>>2]); } break; case ICC_VIRTUAL_THISCALL_RETURNINMEM: { // Get virtual function table from the object pointer asFUNCTION_t *vftable = *(asFUNCTION_t**)obj; retQW = CallThisCallFunctionRetByRef(obj, args, paramSize<<2, vftable[FuncPtrToUInt(func)>>2], retPointer); } break; case ICC_CDECL_OBJLAST: retQW = CallCDeclFunctionQWordObjLast(obj, args, paramSize<<2, func); break; case ICC_CDECL_OBJLAST_RETURNINMEM: // Call the system object method as a cdecl with the obj ref as the last parameter retQW = CallCDeclFunctionRetByRefObjLast(obj, args, paramSize<<2, func, retPointer); break; case ICC_CDECL_OBJFIRST: // Call the system object method as a cdecl with the obj ref as the first parameter retQW = CallCDeclFunctionQWordObjFirst(obj, args, paramSize<<2, func); break; case ICC_CDECL_OBJFIRST_RETURNINMEM: // Call the system object method as a cdecl with the obj ref as the first parameter retQW = CallCDeclFunctionRetByRefObjFirst(obj, args, paramSize<<2, func, retPointer); break; default: context->SetInternalException(TXT_INVALID_CALLING_CONVENTION); } // If the return is a float value we need to get the value from the FP register if( sysFunc->hostReturnFloat ) { if( sysFunc->hostReturnSize == 1 ) *(asDWORD*)&retQW = GetReturnedFloat(); else retQW = GetReturnedDouble(); } return retQW; } // On GCC we need to prevent the compiler from inlining these assembler routines when // optimizing for speed (-O3), as the loop labels get duplicated which cause compile errors. #ifdef __GNUC__ #define NOINLINE __attribute ((__noinline__)) #else #define NOINLINE #endif void NOINLINE CallCDeclFunction(const asDWORD *args, int paramSize, asFUNCTION_t func) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Call function call [func] // Pop arguments from stack add esp, paramSize // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); // GNUC 4.6.1 seems to have a bug when compiling with -O2. This wasn't a problem in earlier versions. // Even though the clobber list is specifically listing the esp register, it still doesn't understand // that it cannot rely on esp for getting the function arguments. So in order to work around this // I'm passing the address of the first arg in edx to the inline assembly, and then copy it to ebx // where it is guaranteed to be maintained over the function call. asm __volatile__( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 4(%%ebx), %%eax \n" // paramSize "addl $4, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 4(%%ebx), %%ecx \n" // paramSize "movl 0(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy \n" "copyloop: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop \n" "endcopy: \n" "call *8(%%ebx) \n" "addl 4(%%ebx), %%esp \n" // pop arguments // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&args) // input - pass pointer of args in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallCDeclFunctionObjLast(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Push the object pointer as the last argument to the function push obj // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Call function call [func] // Pop arguments from stack add esp, paramSize add esp, 4 // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $8, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "pushl 0(%%ebx) \n" // obj "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy8 \n" "copyloop8: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop8 \n" "endcopy8: \n" "call *12(%%ebx) \n" "addl 8(%%ebx), %%esp \n" // pop arguments "addl $4, %%esp \n" // pop obj // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallCDeclFunctionObjFirst(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // push object as first parameter push obj // Call function call [func] // Pop arguments from stack add esp, paramSize add esp, 4 // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $8, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy6 \n" "copyloop6: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop6 \n" "endcopy6: \n" "pushl 0(%%ebx) \n" // push obj "call *12(%%ebx) \n" "addl 8(%%ebx), %%esp \n" // pop arguments "addl $4, %%esp \n" // pop obj // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallCDeclFunctionRetByRefObjFirst_impl(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Push the object pointer push obj // Push the return pointer push retPtr; // Call function call [func] // Pop arguments from stack add esp, paramSize #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER // Pop the return pointer add esp, 8 #else add esp, 4 #endif // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); UNUSED_VAR(retPtr); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $12, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy5 \n" "copyloop5: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop5 \n" "endcopy5: \n" "pushl 0(%%ebx) \n" // push object first "pushl 16(%%ebx) \n" // retPtr "call *12(%%ebx) \n" // func "addl 8(%%ebx), %%esp \n" // pop arguments #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER "addl $8, %%esp \n" // Pop the return pointer and object pointer #else "addl $4, %%esp \n" // Pop the object pointer #endif // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallCDeclFunctionRetByRef_impl(const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Push the return pointer push retPtr; // Call function call [func] // Pop arguments from stack add esp, paramSize #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER // Pop the return pointer add esp, 4 #endif // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); UNUSED_VAR(retPtr); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 4(%%ebx), %%eax \n" // paramSize "addl $8, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 4(%%ebx), %%ecx \n" // paramSize "movl 0(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy7 \n" "copyloop7: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop7 \n" "endcopy7: \n" "pushl 12(%%ebx) \n" // retPtr "call *8(%%ebx) \n" // func "addl 4(%%ebx), %%esp \n" // pop arguments #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER "addl $4, %%esp \n" // Pop the return pointer #endif // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&args) // input - pass pointer of args in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallCDeclFunctionRetByRefObjLast_impl(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK push obj // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Push the return pointer push retPtr; // Call function call [func] // Pop arguments from stack add esp, paramSize add esp, 4 #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER // Pop the return pointer add esp, 4 #endif // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); UNUSED_VAR(retPtr); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $12, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "pushl 0(%%ebx) \n" // obj "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy4 \n" "copyloop4: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop4 \n" "endcopy4: \n" "pushl 16(%%ebx) \n" // retPtr "call *12(%%ebx) \n" // func "addl 8(%%ebx), %%esp \n" // pop arguments #ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER "addl $8, %%esp \n" // Pop the return pointer and object pointer #else "addl $4, %%esp \n" // Pop the object pointer #endif // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallSTDCallFunction(const asDWORD *args, int paramSize, asFUNCTION_t func) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: // Call function call [func] // The callee already removed parameters from the stack // Restore registers pop ecx // return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 4(%%ebx), %%eax \n" // paramSize "addl $4, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 4(%%ebx), %%ecx \n" // paramSize "movl 0(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push arguments on the stack "cmp $0, %%ecx \n" "je endcopy2 \n" "copyloop2: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop2 \n" "endcopy2: \n" "call *8(%%ebx) \n" // callee pops the arguments // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&args) // input - pass pointer of args in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallThisCallFunction(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: #ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK // Push the object pointer on the stack push obj #else // Move object pointer to ECX mov ecx, obj #endif // Call function call [func] #ifndef THISCALL_CALLEE_POPS_ARGUMENTS // Pop arguments add esp, paramSize #ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK // Pop object pointer add esp, 4 #endif #endif // Restore registers pop ecx // Return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $8, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push all arguments on the stack "cmp $0, %%ecx \n" "je endcopy1 \n" "copyloop1: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop1 \n" "endcopy1: \n" "movl 0(%%ebx), %%ecx \n" // move obj into ECX "pushl %%ecx \n" // push obj on the stack "call *12(%%ebx) \n" "addl 8(%%ebx), %%esp \n" // pop arguments "addl $4, %%esp \n" // pop obj // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } void NOINLINE CallThisCallFunctionRetByRef_impl(const void *obj, const asDWORD *args, int paramSize, asFUNCTION_t func, void *retPtr) { #if defined ASM_INTEL // Copy the data to the real stack. If we fail to do // this we may run into trouble in case of exceptions. __asm { // We must save registers that are used push ecx // Clear the FPU stack, in case the called function doesn't do it by itself CLEAR_FPU_STACK // Copy arguments from script // stack to application stack mov ecx, paramSize mov eax, args add eax, ecx cmp ecx, 0 je endcopy copyloop: sub eax, 4 push dword ptr [eax] sub ecx, 4 jne copyloop endcopy: #ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK // Push the object pointer on the stack push obj #else // Move object pointer to ECX mov ecx, obj #endif // Push the return pointer push retPtr // Call function call [func] #ifndef THISCALL_CALLEE_POPS_HIDDEN_RETURN_POINTER // Pop the return pointer add esp, 4 #endif #ifndef THISCALL_CALLEE_POPS_ARGUMENTS // Pop arguments add esp, paramSize #ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK // Pop object pointer add esp, 4 #endif #endif // Restore registers pop ecx // Return value in EAX or EAX:EDX } #elif defined ASM_AT_N_T UNUSED_VAR(obj); UNUSED_VAR(args); UNUSED_VAR(paramSize); UNUSED_VAR(func); UNUSED_VAR(retPtr); asm __volatile__ ( _S(CLEAR_FPU_STACK) "\n" "pushl %%ebx \n" "movl %%edx, %%ebx \n" // Need to align the stack pointer so that it is aligned to 16 bytes when making the function call. // It is assumed that when entering this function, the stack pointer is already aligned, so we need // to calculate how much we will put on the stack during this call. "movl 8(%%ebx), %%eax \n" // paramSize "addl $12, %%eax \n" // counting esp that we will push on the stack "movl %%esp, %%ecx \n" "subl %%eax, %%ecx \n" "andl $15, %%ecx \n" "movl %%esp, %%eax \n" "subl %%ecx, %%esp \n" "pushl %%eax \n" // Store the original stack pointer "movl 8(%%ebx), %%ecx \n" // paramSize "movl 4(%%ebx), %%eax \n" // args "addl %%ecx, %%eax \n" // push all arguments to the stack "cmp $0, %%ecx \n" "je endcopy3 \n" "copyloop3: \n" "subl $4, %%eax \n" "pushl (%%eax) \n" "subl $4, %%ecx \n" "jne copyloop3 \n" "endcopy3: \n" "movl 0(%%ebx), %%ecx \n" // move obj into ECX "pushl %%ecx \n" // push obj on the stack "pushl 16(%%ebx) \n" // push retPtr on the stack "call *12(%%ebx) \n" #ifndef THISCALL_CALLEE_POPS_HIDDEN_RETURN_POINTER "addl $4, %%esp \n" // pop return pointer #endif "addl 8(%%ebx), %%esp \n" // pop arguments "addl $4, %%esp \n" // pop the object pointer // the return pointer was popped by the callee // Pop the alignment bytes "popl %%esp \n" "popl %%ebx \n" : // output : "d"(&obj) // input - pass pointer of obj in edx : "%eax", "%ecx", "%esp" // clobber ); #endif } asDWORD GetReturnedFloat() { asDWORD f; #if defined ASM_INTEL // Get the float value from ST0 __asm fstp dword ptr [f] #elif defined ASM_AT_N_T asm("fstps %0 \n" : "=m" (f)); #endif return f; } asQWORD GetReturnedDouble() { asQWORD d; #if defined ASM_INTEL // Get the double value from ST0 __asm fstp qword ptr [d] #elif defined ASM_AT_N_T asm("fstpl %0 \n" : "=m" (d)); #endif return d; } END_AS_NAMESPACE #endif // AS_X86 #endif // AS_MAX_PORTABILITY