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