diff options
Diffstat (limited to 'libffi/src/powerpc/ffi.c')
-rw-r--r-- | libffi/src/powerpc/ffi.c | 203 |
1 files changed, 167 insertions, 36 deletions
diff --git a/libffi/src/powerpc/ffi.c b/libffi/src/powerpc/ffi.c index eaa4c869be8..e6b869f8737 100644 --- a/libffi/src/powerpc/ffi.c +++ b/libffi/src/powerpc/ffi.c @@ -50,10 +50,13 @@ enum { }; /* About the SYSV ABI. */ -enum { - NUM_GPR_ARG_REGISTERS = 8, - NUM_FPR_ARG_REGISTERS = 8 -}; +unsigned int NUM_GPR_ARG_REGISTERS = 8; +#ifndef __NO_FPRS__ +unsigned int NUM_FPR_ARG_REGISTERS = 8; +#else +unsigned int NUM_FPR_ARG_REGISTERS = 0; +#endif + enum { ASM_NEEDS_REGISTERS = 4 }; /* ffi_prep_args_SYSV is called by the assembly routine once stack space @@ -116,7 +119,7 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) /* 'next_arg' grows up as we put parameters in it. */ valp next_arg; - int i; + int i, ii MAYBE_UNUSED; ffi_type **ptr; double double_tmp; union { @@ -134,6 +137,9 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) size_t struct_copy_size; unsigned gprvalue; + if (ecif->cif->abi == FFI_LINUX_SOFT_FLOAT) + NUM_FPR_ARG_REGISTERS = 0; + stacktop.c = (char *) stack + bytes; gpr_base.u = stacktop.u - ASM_NEEDS_REGISTERS - NUM_GPR_ARG_REGISTERS; intarg_count = 0; @@ -165,6 +171,9 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) switch ((*ptr)->type) { case FFI_TYPE_FLOAT: + /* With FFI_LINUX_SOFT_FLOAT floats are handled like UINT32. */ + if (ecif->cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_float_prep; double_tmp = **p_argv.f; if (fparg_count >= NUM_FPR_ARG_REGISTERS) { @@ -178,6 +187,9 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) break; case FFI_TYPE_DOUBLE: + /* With FFI_LINUX_SOFT_FLOAT doubles are handled like UINT64. */ + if (ecif->cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_double_prep; double_tmp = **p_argv.d; if (fparg_count >= NUM_FPR_ARG_REGISTERS) @@ -199,38 +211,75 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - if (ecif->cif->abi != FFI_LINUX) + if ((ecif->cif->abi != FFI_LINUX) + && (ecif->cif->abi != FFI_LINUX_SOFT_FLOAT)) goto do_struct; - double_tmp = (*p_argv.d)[0]; - - if (fparg_count >= NUM_FPR_ARG_REGISTERS - 1) + /* The soft float ABI for long doubles works like this, + a long double is passed in four consecutive gprs if available. + A maximum of 2 long doubles can be passed in gprs. + If we do not have 4 gprs left, the long double is passed on the + stack, 4-byte aligned. */ + if (ecif->cif->abi == FFI_LINUX_SOFT_FLOAT) { - if (intarg_count >= NUM_GPR_ARG_REGISTERS - && intarg_count % 2 != 0) + unsigned int int_tmp = (*p_argv.ui)[0]; + if (intarg_count >= NUM_GPR_ARG_REGISTERS - 3) { - intarg_count++; + if (intarg_count < NUM_GPR_ARG_REGISTERS) + intarg_count += NUM_GPR_ARG_REGISTERS - intarg_count; + *next_arg.u = int_tmp; next_arg.u++; + for (ii = 1; ii < 4; ii++) + { + int_tmp = (*p_argv.ui)[ii]; + *next_arg.u = int_tmp; + next_arg.u++; + } } - *next_arg.d = double_tmp; - next_arg.u += 2; - double_tmp = (*p_argv.d)[1]; - *next_arg.d = double_tmp; - next_arg.u += 2; + else + { + *gpr_base.u++ = int_tmp; + for (ii = 1; ii < 4; ii++) + { + int_tmp = (*p_argv.ui)[ii]; + *gpr_base.u++ = int_tmp; + } + } + intarg_count +=4; } else { - *fpr_base.d++ = double_tmp; - double_tmp = (*p_argv.d)[1]; - *fpr_base.d++ = double_tmp; - } + double_tmp = (*p_argv.d)[0]; - fparg_count += 2; - FFI_ASSERT (flags & FLAG_FP_ARGUMENTS); + if (fparg_count >= NUM_FPR_ARG_REGISTERS - 1) + { + if (intarg_count >= NUM_GPR_ARG_REGISTERS + && intarg_count % 2 != 0) + { + intarg_count++; + next_arg.u++; + } + *next_arg.d = double_tmp; + next_arg.u += 2; + double_tmp = (*p_argv.d)[1]; + *next_arg.d = double_tmp; + next_arg.u += 2; + } + else + { + *fpr_base.d++ = double_tmp; + double_tmp = (*p_argv.d)[1]; + *fpr_base.d++ = double_tmp; + } + + fparg_count += 2; + FFI_ASSERT (flags & FLAG_FP_ARGUMENTS); + } break; #endif case FFI_TYPE_UINT64: case FFI_TYPE_SINT64: + soft_double_prep: if (intarg_count == NUM_GPR_ARG_REGISTERS-1) intarg_count++; if (intarg_count >= NUM_GPR_ARG_REGISTERS) @@ -293,6 +342,8 @@ ffi_prep_args_SYSV (extended_cif *ecif, unsigned *const stack) case FFI_TYPE_UINT32: case FFI_TYPE_SINT32: case FFI_TYPE_POINTER: + soft_float_prep: + gprvalue = **p_argv.ui; putgpr: @@ -546,6 +597,9 @@ ffi_prep_cif_machdep (ffi_cif *cif) unsigned type = cif->rtype->type; unsigned size = cif->rtype->size; + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + NUM_FPR_ARG_REGISTERS = 0; + if (cif->abi != FFI_LINUX64) { /* All the machine-independent calculation of cif->bytes will be wrong. @@ -582,14 +636,16 @@ ffi_prep_cif_machdep (ffi_cif *cif) For LINUX64: - integer values in gpr3; - Structures/Unions by reference; - - Single/double FP values in fpr1, long double in fpr1,fpr2. */ + - Single/double FP values in fpr1, long double in fpr1,fpr2. + - soft-float float/doubles are treated as UINT32/UINT64 respectivley. + - soft-float long doubles are returned in gpr3-gpr6. */ switch (type) { #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - if (cif->abi != FFI_LINUX && cif->abi != FFI_LINUX64) + if (cif->abi != FFI_LINUX && cif->abi != FFI_LINUX64 + && cif->abi != FFI_LINUX_SOFT_FLOAT) goto byref; - flags |= FLAG_RETURNS_128BITS; /* Fall through. */ #endif @@ -597,7 +653,9 @@ ffi_prep_cif_machdep (ffi_cif *cif) flags |= FLAG_RETURNS_64BITS; /* Fall through. */ case FFI_TYPE_FLOAT: - flags |= FLAG_RETURNS_FP; + /* With FFI_LINUX_SOFT_FLOAT no fp registers are used. */ + if (cif->abi != FFI_LINUX_SOFT_FLOAT) + flags |= FLAG_RETURNS_FP; break; case FFI_TYPE_UINT64: @@ -660,18 +718,36 @@ ffi_prep_cif_machdep (ffi_cif *cif) switch ((*ptr)->type) { case FFI_TYPE_FLOAT: + /* With FFI_LINUX_SOFT_FLOAT floats are handled like UINT32. */ + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_float_cif; fparg_count++; /* floating singles are not 8-aligned on stack */ break; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - if (cif->abi != FFI_LINUX) + if (cif->abi != FFI_LINUX && cif->abi != FFI_LINUX_SOFT_FLOAT) goto do_struct; - fparg_count++; + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + { + if (intarg_count >= NUM_GPR_ARG_REGISTERS - 3 + || intarg_count < NUM_GPR_ARG_REGISTERS) + /* A long double in FFI_LINUX_SOFT_FLOAT can use only + a set of four consecutive gprs. If we have not enough, + we have to adjust the intarg_count value. */ + intarg_count += NUM_GPR_ARG_REGISTERS - intarg_count; + intarg_count += 4; + break; + } + else + fparg_count++; /* Fall thru */ #endif case FFI_TYPE_DOUBLE: + /* With FFI_LINUX_SOFT_FLOAT doubles are handled like UINT64. */ + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_double_cif; fparg_count++; /* If this FP arg is going on the stack, it must be 8-byte-aligned. */ @@ -683,6 +759,7 @@ ffi_prep_cif_machdep (ffi_cif *cif) case FFI_TYPE_UINT64: case FFI_TYPE_SINT64: + soft_double_cif: /* 'long long' arguments are passed as two words, but either both words must fit in registers or both go on the stack. If they go on the stack, they must @@ -710,6 +787,7 @@ ffi_prep_cif_machdep (ffi_cif *cif) /* Fall through (allocate space for the pointer). */ default: + soft_float_cif: /* Everything else is passed as a 4-byte word in a GPR, either the object itself or a pointer to it. */ intarg_count++; @@ -723,8 +801,13 @@ ffi_prep_cif_machdep (ffi_cif *cif) { #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - fparg_count += 2; - intarg_count += 2; + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + intarg_count += 4; + else + { + fparg_count += 2; + intarg_count += 2; + } break; #endif case FFI_TYPE_FLOAT: @@ -818,6 +901,7 @@ ffi_call(ffi_cif *cif, void (*fn)(), void *rvalue, void **avalue) case FFI_SYSV: case FFI_GCC_SYSV: case FFI_LINUX: + case FFI_LINUX_SOFT_FLOAT: ffi_call_SYSV (&ecif, -cif->bytes, cif->flags, ecif.rvalue, fn); break; #else @@ -942,7 +1026,7 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, && !((cif->abi == FFI_SYSV) && (size <= 8))) #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE || (cif->rtype->type == FFI_TYPE_LONGDOUBLE - && cif->abi != FFI_LINUX) + && cif->abi != FFI_LINUX && cif->abi != FFI_LINUX_SOFT_FLOAT) #endif ) { @@ -995,6 +1079,7 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, case FFI_TYPE_SINT32: case FFI_TYPE_UINT32: case FFI_TYPE_POINTER: + soft_float_closure: /* there are 8 gpr registers used to pass values */ if (ng < 8) { @@ -1030,6 +1115,7 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, case FFI_TYPE_SINT64: case FFI_TYPE_UINT64: + soft_double_closure: /* passing long long ints are complex, they must * be passed in suitable register pairs such as * (r3,r4) or (r5,r6) or (r6,r7), or (r7,r8) or (r9,r10) @@ -1061,6 +1147,9 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, break; case FFI_TYPE_FLOAT: + /* With FFI_LINUX_SOFT_FLOAT floats are handled like UINT32. */ + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_float_closure; /* unfortunately float values are stored as doubles * in the ffi_closure_SYSV code (since we don't check * the type in that routine). @@ -1089,6 +1178,9 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, break; case FFI_TYPE_DOUBLE: + /* With FFI_LINUX_SOFT_FLOAT doubles are handled like UINT64. */ + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + goto soft_double_closure; /* On the outgoing stack all values are aligned to 8 */ /* there are 8 64bit floating point registers */ @@ -1109,9 +1201,24 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE case FFI_TYPE_LONGDOUBLE: - if (cif->abi != FFI_LINUX) + if (cif->abi != FFI_LINUX && cif->abi != FFI_LINUX_SOFT_FLOAT) goto do_struct; - + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + { /* Test if for the whole long double, 4 gprs are available. + otherwise the stuff ends up on the stack. */ + if (ng < 5) + { + avalue[i] = pgr; + pgr += 4; + ng += 4; + } + else + { + avalue[i] = pst; + pst += 4; + } + break; + } if (nf < 7) { avalue[i] = pfr; @@ -1147,10 +1254,34 @@ ffi_closure_helper_SYSV (ffi_closure *closure, void *rvalue, return FFI_SYSV_TYPE_SMALL_STRUCT + size; #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE else if (cif->rtype->type == FFI_TYPE_LONGDOUBLE - && cif->abi != FFI_LINUX) + && cif->abi != FFI_LINUX && cif->abi != FFI_LINUX_SOFT_FLOAT) return FFI_TYPE_STRUCT; #endif - return cif->rtype->type; + /* With FFI_LINUX_SOFT_FLOAT floats and doubles are handled like UINT32 + respectivley UINT64. */ + if (cif->abi == FFI_LINUX_SOFT_FLOAT) + { + switch (cif->rtype->type) + { + case FFI_TYPE_FLOAT: + return FFI_TYPE_UINT32; + break; + case FFI_TYPE_DOUBLE: + return FFI_TYPE_UINT64; + break; +#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE + case FFI_TYPE_LONGDOUBLE: + return FFI_TYPE_UINT128; + break; +#endif + default: + return cif->rtype->type; + } + } + else + { + return cif->rtype->type; + } } int FFI_HIDDEN ffi_closure_helper_LINUX64 (ffi_closure *, void *, |