1 /*
2 AngelCode Scripting Library
3 Copyright (c) 2003-2015 Andreas Jonsson
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any
7 damages arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any
10 purpose, including commercial applications, and to alter it and
11 redistribute it freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you
14 must not claim that you wrote the original software. If you use
15 this software in a product, an acknowledgment in the product
16 documentation would be appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and
19 must not be misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source
22 distribution.
23
24 The original version of this library can be located at:
25 http://www.angelcode.com/angelscript/
26
27 Andreas Jonsson
28 andreas@angelcode.com
29 */
30
31 /*
32 * Implements the AMD64 calling convention for gcc-based 64bit Unices
33 *
34 * Author: Ionut "gargltk" Leonte <ileonte@bitdefender.com>
35 *
36 * Initial author: niteice
37 *
38 * Added support for functor methods by Jordi Oliveras Rovira in April, 2014.
39 */
40
41 // Useful references for the System V AMD64 ABI:
42 // http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/
43 // http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_amd64.pdf
44
45 #include "as_config.h"
46
47 #ifndef AS_MAX_PORTABILITY
48 #ifdef AS_X64_GCC
49
50 #include "as_scriptengine.h"
51 #include "as_texts.h"
52 #include "as_context.h"
53
54 BEGIN_AS_NAMESPACE
55
56 enum argTypes { x64INTARG = 0, x64FLOATARG = 1 };
57 typedef asQWORD ( *funcptr_t )( void );
58
59 #define X64_MAX_ARGS 32
60 #define MAX_CALL_INT_REGISTERS 6
61 #define MAX_CALL_SSE_REGISTERS 8
62 #define X64_CALLSTACK_SIZE ( X64_MAX_ARGS + MAX_CALL_SSE_REGISTERS + 3 )
63
64 // Note to self: Always remember to inform the used registers on the clobber line,
65 // so that the gcc optimizer doesn't try to use them for other things
66
X64_CallFunction(const asQWORD * args,int cnt,funcptr_t func,asQWORD & retQW2,bool returnFloat)67 static asQWORD __attribute__((noinline)) X64_CallFunction(const asQWORD *args, int cnt, funcptr_t func, asQWORD &retQW2, bool returnFloat)
68 {
69 // Need to flag the variable as volatile so the compiler doesn't optimize out the variable
70 volatile asQWORD retQW1 = 0;
71
72 // Reference: http://www.x86-64.org/documentation/abi.pdf
73
74 __asm__ __volatile__ (
75
76 " movq %0, %%rcx \n" // rcx = cnt
77 " movq %1, %%r10 \n" // r10 = args
78 " movq %2, %%r11 \n" // r11 = func
79
80 // Backup stack pointer in R15 that is guaranteed to maintain its value over function calls
81 " movq %%rsp, %%r15 \n"
82 #ifdef __OPTIMIZE__
83 // Make sure the stack unwind logic knows we've backed up the stack pointer in register r15
84 // This should only be done if any optimization is done. If no optimization (-O0) is used,
85 // then the compiler already backups the rsp before entering the inline assembler code
86 " .cfi_def_cfa_register r15 \n"
87 #endif
88
89 // Skip the first 128 bytes on the stack frame, called "red zone",
90 // that might be used by the compiler to store temporary values
91 " sub $128, %%rsp \n"
92
93 // Make sure the stack pointer will be aligned to 16 bytes when the function is called
94 " movq %%rcx, %%rdx \n"
95 " salq $3, %%rdx \n"
96 " movq %%rsp, %%rax \n"
97 " sub %%rdx, %%rax \n"
98 " and $15, %%rax \n"
99 " sub %%rax, %%rsp \n"
100
101 // Push the stack parameters, i.e. the arguments that won't be loaded into registers
102 " movq %%rcx, %%rsi \n"
103 " testl %%esi, %%esi \n"
104 " jle endstack \n"
105 " subl $1, %%esi \n"
106 " xorl %%edx, %%edx \n"
107 " leaq 8(, %%rsi, 8), %%rcx \n"
108 "loopstack: \n"
109 " movq 112(%%r10, %%rdx), %%rax \n"
110 " pushq %%rax \n"
111 " addq $8, %%rdx \n"
112 " cmpq %%rcx, %%rdx \n"
113 " jne loopstack \n"
114 "endstack: \n"
115
116 // Populate integer and floating point parameters
117 " movq %%r10, %%rax \n"
118 " mov (%%rax), %%rdi \n"
119 " mov 8(%%rax), %%rsi \n"
120 " mov 16(%%rax), %%rdx \n"
121 " mov 24(%%rax), %%rcx \n"
122 " mov 32(%%rax), %%r8 \n"
123 " mov 40(%%rax), %%r9 \n"
124 " add $48, %%rax \n"
125 " movsd (%%rax), %%xmm0 \n"
126 " movsd 8(%%rax), %%xmm1 \n"
127 " movsd 16(%%rax), %%xmm2 \n"
128 " movsd 24(%%rax), %%xmm3 \n"
129 " movsd 32(%%rax), %%xmm4 \n"
130 " movsd 40(%%rax), %%xmm5 \n"
131 " movsd 48(%%rax), %%xmm6 \n"
132 " movsd 56(%%rax), %%xmm7 \n"
133
134 // Call the function
135 " call *%%r11 \n"
136
137 // Restore stack pointer
138 " mov %%r15, %%rsp \n"
139 #ifdef __OPTIMIZE__
140 // Inform the stack unwind logic that the stack pointer has been restored
141 // This should only be done if any optimization is done. If no optimization (-O0) is used,
142 // then the compiler already backups the rsp before entering the inline assembler code
143 " .cfi_def_cfa_register rsp \n"
144 #endif
145
146 // Put return value in retQW1 and retQW2, using either RAX:RDX or XMM0:XMM1 depending on type of return value
147 " movl %5, %%ecx \n"
148 " testb %%cl, %%cl \n"
149 " je intret \n"
150 " lea %3, %%rax \n"
151 " movq %%xmm0, (%%rax) \n"
152 " lea %4, %%rdx \n"
153 " movq %%xmm1, (%%rdx) \n"
154 " jmp endcall \n"
155 "intret: \n"
156 " movq %%rax, %3 \n"
157 " movq %%rdx, %4 \n"
158 "endcall: \n"
159
160 : : "r" ((asQWORD)cnt), "r" (args), "r" (func), "m" (retQW1), "m" (retQW2), "m" (returnFloat)
161 : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7",
162 "%rdi", "%rsi", "%rax", "%rdx", "%rcx", "%r8", "%r9", "%r10", "%r11", "%r15");
163
164 return retQW1;
165 }
166
167 // returns true if the given parameter is a 'variable argument'
IsVariableArgument(asCDataType type)168 static inline bool IsVariableArgument( asCDataType type )
169 {
170 return ( type.GetTokenType() == ttQuestion ) ? true : false;
171 }
172
CallSystemFunctionNative(asCContext * context,asCScriptFunction * descr,void * obj,asDWORD * args,void * retPointer,asQWORD & retQW2,void * secondObject)173 asQWORD CallSystemFunctionNative(asCContext *context, asCScriptFunction *descr, void *obj, asDWORD *args, void *retPointer, asQWORD &retQW2, void *secondObject)
174 {
175 asCScriptEngine *engine = context->m_engine;
176 asSSystemFunctionInterface *sysFunc = descr->sysFuncIntf;
177 int callConv = sysFunc->callConv;
178 asQWORD retQW = 0;
179 asDWORD *stack_pointer = args;
180 funcptr_t *vftable = NULL;
181 int totalArgumentCount = 0;
182 int n = 0;
183 int param_post = 0;
184 int argIndex = 0;
185 funcptr_t func = (funcptr_t)sysFunc->func;
186
187 if( sysFunc->hostReturnInMemory )
188 {
189 // The return is made in memory
190 callConv++;
191 }
192
193 #ifdef AS_NO_THISCALL_FUNCTOR_METHOD
194 // Determine the real function pointer in case of virtual method
195 if ( obj && ( callConv == ICC_VIRTUAL_THISCALL || callConv == ICC_VIRTUAL_THISCALL_RETURNINMEM ) )
196 #else
197 if ( obj && ( callConv == ICC_VIRTUAL_THISCALL ||
198 callConv == ICC_VIRTUAL_THISCALL_RETURNINMEM ||
199 callConv == ICC_VIRTUAL_THISCALL_OBJFIRST ||
200 callConv == ICC_VIRTUAL_THISCALL_OBJFIRST_RETURNINMEM ||
201 callConv == ICC_VIRTUAL_THISCALL_OBJLAST ||
202 callConv == ICC_VIRTUAL_THISCALL_OBJLAST_RETURNINMEM) )
203 #endif
204 {
205 vftable = *((funcptr_t**)obj);
206 func = vftable[FuncPtrToUInt(asFUNCTION_t(func)) >> 3];
207 }
208
209 // Determine the type of the arguments, and prepare the input array for the X64_CallFunction
210 asQWORD paramBuffer[X64_CALLSTACK_SIZE] = { 0 };
211 asBYTE argsType[X64_CALLSTACK_SIZE] = { 0 };
212
213 switch ( callConv )
214 {
215 case ICC_CDECL_RETURNINMEM:
216 case ICC_STDCALL_RETURNINMEM:
217 {
218 paramBuffer[0] = (asPWORD)retPointer;
219 argsType[0] = x64INTARG;
220
221 argIndex = 1;
222
223 break;
224 }
225 #ifndef AS_NO_THISCALL_FUNCTOR_METHOD
226 case ICC_THISCALL_OBJLAST:
227 case ICC_VIRTUAL_THISCALL_OBJLAST:
228 param_post = 2;
229 #endif
230 case ICC_THISCALL:
231 case ICC_VIRTUAL_THISCALL:
232 case ICC_CDECL_OBJFIRST:
233 {
234 paramBuffer[0] = (asPWORD)obj;
235 argsType[0] = x64INTARG;
236
237 argIndex = 1;
238
239 break;
240 }
241 #ifndef AS_NO_THISCALL_FUNCTOR_METHOD
242 case ICC_THISCALL_OBJLAST_RETURNINMEM:
243 case ICC_VIRTUAL_THISCALL_OBJLAST_RETURNINMEM:
244 param_post = 2;
245 #endif
246 case ICC_THISCALL_RETURNINMEM:
247 case ICC_VIRTUAL_THISCALL_RETURNINMEM:
248 case ICC_CDECL_OBJFIRST_RETURNINMEM:
249 {
250 paramBuffer[0] = (asPWORD)retPointer;
251 paramBuffer[1] = (asPWORD)obj;
252 argsType[0] = x64INTARG;
253 argsType[1] = x64INTARG;
254
255 argIndex = 2;
256
257 break;
258 }
259 #ifndef AS_NO_THISCALL_FUNCTOR_METHOD
260 case ICC_THISCALL_OBJFIRST:
261 case ICC_VIRTUAL_THISCALL_OBJFIRST:
262 {
263 paramBuffer[0] = (asPWORD)obj;
264 paramBuffer[1] = (asPWORD)secondObject;
265 argsType[0] = x64INTARG;
266 argsType[1] = x64INTARG;
267
268 argIndex = 2;
269 break;
270 }
271 case ICC_THISCALL_OBJFIRST_RETURNINMEM:
272 case ICC_VIRTUAL_THISCALL_OBJFIRST_RETURNINMEM:
273 {
274 paramBuffer[0] = (asPWORD)retPointer;
275 paramBuffer[1] = (asPWORD)obj;
276 paramBuffer[2] = (asPWORD)secondObject;
277 argsType[0] = x64INTARG;
278 argsType[1] = x64INTARG;
279 argsType[2] = x64INTARG;
280
281 argIndex = 3;
282 break;
283 }
284 #endif
285 case ICC_CDECL_OBJLAST:
286 param_post = 1;
287 break;
288 case ICC_CDECL_OBJLAST_RETURNINMEM:
289 {
290 paramBuffer[0] = (asPWORD)retPointer;
291 argsType[0] = x64INTARG;
292
293 argIndex = 1;
294 param_post = 1;
295
296 break;
297 }
298 }
299
300 int argumentCount = ( int )descr->parameterTypes.GetLength();
301 for( int a = 0; a < argumentCount; ++a )
302 {
303 const asCDataType &parmType = descr->parameterTypes[a];
304 if( parmType.IsFloatType() && !parmType.IsReference() )
305 {
306 argsType[argIndex] = x64FLOATARG;
307 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(float));
308 argIndex++;
309 stack_pointer++;
310 }
311 else if( parmType.IsDoubleType() && !parmType.IsReference() )
312 {
313 argsType[argIndex] = x64FLOATARG;
314 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(double));
315 argIndex++;
316 stack_pointer += 2;
317 }
318 else if( IsVariableArgument( parmType ) )
319 {
320 // The variable args are really two, one pointer and one type id
321 argsType[argIndex] = x64INTARG;
322 argsType[argIndex+1] = x64INTARG;
323 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(void*));
324 memcpy(paramBuffer + argIndex + 1, stack_pointer + 2, sizeof(asDWORD));
325 argIndex += 2;
326 stack_pointer += 3;
327 }
328 else if( parmType.IsPrimitive() ||
329 parmType.IsReference() ||
330 parmType.IsObjectHandle() )
331 {
332 argsType[argIndex] = x64INTARG;
333 if( parmType.GetSizeOnStackDWords() == 1 )
334 {
335 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(asDWORD));
336 stack_pointer++;
337 }
338 else
339 {
340 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(asQWORD));
341 stack_pointer += 2;
342 }
343 argIndex++;
344 }
345 else
346 {
347 // An object is being passed by value
348 if( (parmType.GetTypeInfo()->flags & COMPLEX_MASK) ||
349 parmType.GetSizeInMemoryDWords() > 4 )
350 {
351 // Copy the address of the object
352 argsType[argIndex] = x64INTARG;
353 memcpy(paramBuffer + argIndex, stack_pointer, sizeof(asQWORD));
354 argIndex++;
355 }
356 else if( (parmType.GetTypeInfo()->flags & asOBJ_APP_CLASS_ALLINTS) ||
357 (parmType.GetTypeInfo()->flags & asOBJ_APP_PRIMITIVE) )
358 {
359 // Copy the value of the object
360 if( parmType.GetSizeInMemoryDWords() > 2 )
361 {
362 argsType[argIndex] = x64INTARG;
363 argsType[argIndex+1] = x64INTARG;
364 memcpy(paramBuffer + argIndex, *(asDWORD**)stack_pointer, parmType.GetSizeInMemoryBytes());
365 argIndex += 2;
366 }
367 else
368 {
369 argsType[argIndex] = x64INTARG;
370 memcpy(paramBuffer + argIndex, *(asDWORD**)stack_pointer, parmType.GetSizeInMemoryBytes());
371 argIndex++;
372 }
373 // Delete the original memory
374 engine->CallFree(*(void**)stack_pointer);
375 }
376 else if( (parmType.GetTypeInfo()->flags & asOBJ_APP_CLASS_ALLFLOATS) ||
377 (parmType.GetTypeInfo()->flags & asOBJ_APP_FLOAT) )
378 {
379 // Copy the value of the object
380 if( parmType.GetSizeInMemoryDWords() > 2 )
381 {
382 argsType[argIndex] = x64FLOATARG;
383 argsType[argIndex+1] = x64FLOATARG;
384 memcpy(paramBuffer + argIndex, *(asDWORD**)stack_pointer, parmType.GetSizeInMemoryBytes());
385 argIndex += 2;
386 }
387 else
388 {
389 argsType[argIndex] = x64FLOATARG;
390 memcpy(paramBuffer + argIndex, *(asDWORD**)stack_pointer, parmType.GetSizeInMemoryBytes());
391 argIndex++;
392 }
393 // Delete the original memory
394 engine->CallFree(*(void**)stack_pointer);
395 }
396 stack_pointer += 2;
397 }
398 }
399
400 // For the CDECL_OBJ_LAST calling convention we need to add the object pointer as the last argument
401 if( param_post )
402 {
403 #ifdef AS_NO_THISCALL_FUNCTOR_METHOD
404 paramBuffer[argIndex] = (asPWORD)obj;
405 #else
406 paramBuffer[argIndex] = (asPWORD)(param_post > 1 ? secondObject : obj);
407 #endif
408 argsType[argIndex] = x64INTARG;
409 argIndex++;
410 }
411
412 totalArgumentCount = argIndex;
413
414 /*
415 * Q: WTF is going on here !?
416 *
417 * A: The idea is to pre-arange the parameters so that X64_CallFunction() can do
418 * it's little magic which must work regardless of how the compiler decides to
419 * allocate registers. Basically:
420 * - the first MAX_CALL_INT_REGISTERS entries in tempBuff will
421 * contain the values/types of the x64INTARG parameters - that is the ones who
422 * go into the registers. If the function has less then MAX_CALL_INT_REGISTERS
423 * integer parameters then the last entries will be set to 0
424 * - the next MAX_CALL_SSE_REGISTERS entries will contain the float/double arguments
425 * that go into the floating point registers. If the function has less than
426 * MAX_CALL_SSE_REGISTERS floating point parameters then the last entries will
427 * be set to 0
428 * - index MAX_CALL_INT_REGISTERS + MAX_CALL_SSE_REGISTERS marks the start of the
429 * parameters which will get passed on the stack. These are added to the array
430 * in reverse order so that X64_CallFunction() can simply push them to the stack
431 * without the need to perform further tests
432 */
433 asQWORD tempBuff[X64_CALLSTACK_SIZE] = { 0 };
434 asBYTE argsSet[X64_CALLSTACK_SIZE] = { 0 };
435 int used_int_regs = 0;
436 int used_sse_regs = 0;
437 int used_stack_args = 0;
438 int idx = 0;
439 for ( n = 0; ( n < totalArgumentCount ) && ( used_int_regs < MAX_CALL_INT_REGISTERS ); n++ )
440 {
441 if ( argsType[n] == x64INTARG )
442 {
443 argsSet[n] = 1;
444 tempBuff[idx++] = paramBuffer[n];
445 used_int_regs++;
446 }
447 }
448 idx = MAX_CALL_INT_REGISTERS;
449 for ( n = 0; ( n < totalArgumentCount ) && ( used_sse_regs < MAX_CALL_SSE_REGISTERS ); n++ )
450 {
451 if ( argsType[n] == x64FLOATARG )
452 {
453 argsSet[n] = 1;
454 tempBuff[idx++] = paramBuffer[n];
455 used_sse_regs++;
456 }
457 }
458 idx = MAX_CALL_INT_REGISTERS + MAX_CALL_SSE_REGISTERS;
459 for ( n = totalArgumentCount - 1; n >= 0; n-- )
460 {
461 if ( !argsSet[n] )
462 {
463 tempBuff[idx++] = paramBuffer[n];
464 used_stack_args++;
465 }
466 }
467
468 retQW = X64_CallFunction( tempBuff, used_stack_args, func, retQW2, sysFunc->hostReturnFloat );
469
470 return retQW;
471 }
472
473 END_AS_NAMESPACE
474
475 #endif // AS_X64_GCC
476 #endif // AS_MAX_PORTABILITY
477
478