From b961ba5396ae235b914f8c211af0ffc07e7c6f04 Mon Sep 17 00:00:00 2001 From: James Lyon Date: Fri, 19 Apr 2013 11:08:12 +0100 Subject: [PATCH] Got test1-3 working on x86-64. There are probably still issues on x86-64 I've missed. I've added a few new tests to abitest, which fail (2x long long and 2x double in a struct should be passed in registers). --- tcc.h | 3 ++ tccgen.c | 18 ++----- tests/abitest.c | 110 +++++++++++++++++++++++++++++++++++++++++ x86_64-gen.c | 151 +++++++++++++++++++++++++++++--------------------------- 4 files changed, 197 insertions(+), 85 deletions(-) diff --git a/tcc.h b/tcc.h index b336e1f7..6289d348 100644 --- a/tcc.h +++ b/tcc.h @@ -1201,6 +1201,9 @@ ST_FUNC void decl(int l); #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_C67 ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size); #endif +#ifdef TCC_TARGET_X86_64 +ST_FUNC int classify_x86_64_va_arg(CType *ty); +#endif /* ------------ tccelf.c ------------ */ diff --git a/tccgen.c b/tccgen.c index 83ee171a..c084186d 100644 --- a/tccgen.c +++ b/tccgen.c @@ -2067,6 +2067,9 @@ ST_FUNC int type_size(CType *type, int *a) } else if (bt == VT_SHORT) { *a = 2; return 2; + } else if (bt == VT_QLONG || bt == VT_QFLOAT) { + *a = 8; + return 16; } else { /* char, void, function, _Bool */ *a = 1; @@ -3695,24 +3698,13 @@ ST_FUNC void unary(void) #ifdef TCC_TARGET_X86_64 case TOK_builtin_va_arg_types: { - /* This definition must be synced with stdarg.h */ - enum __va_arg_type { - __va_gen_reg, __va_float_reg, __va_stack - }; CType type; int bt; next(); skip('('); parse_type(&type); skip(')'); - bt = type.t & VT_BTYPE; - if (bt == VT_STRUCT || bt == VT_LDOUBLE) { - vpushi(__va_stack); - } else if (bt == VT_FLOAT || bt == VT_DOUBLE) { - vpushi(__va_float_reg); - } else { - vpushi(__va_gen_reg); - } + vpushi(classify_x86_64_va_arg(&type)); } break; #endif @@ -4966,7 +4958,7 @@ static void init_putz(CType *t, Section *sec, unsigned long c, int size) vpush_global_sym(&func_old_type, TOK_memset); vseti(VT_LOCAL, c); vpushi(0); - vpushi(size); + vpushs(size); gfunc_call(3); } } diff --git a/tests/abitest.c b/tests/abitest.c index ff948d06..8c1358f6 100644 --- a/tests/abitest.c +++ b/tests/abitest.c @@ -2,6 +2,7 @@ #include #include #include +#include static const char *tccdir = NULL; @@ -56,6 +57,12 @@ RET_PRIMITIVE_TEST(float, float) RET_PRIMITIVE_TEST(double, double) RET_PRIMITIVE_TEST(longdouble, long double) +/* + * ret_2float_test: + * + * On x86-64, a struct with 2 floats should be packed into a single + * SSE register (VT_DOUBLE is used for this purpose). + */ typedef struct ret_2float_test_type_s {float x, y;} ret_2float_test_type; typedef ret_2float_test_type (*ret_2float_test_function_type) (ret_2float_test_type); @@ -79,6 +86,34 @@ static int ret_2float_test(void) { } /* + * ret_2double_test: + * + * On x86-64, a struct with 2 doubles should be packed into a single + * SSE register (this tests VT_QFLOAT). + */ +typedef struct ret_2double_test_type_s {double x, y;} ret_2double_test_type; +typedef ret_2double_test_type (*ret_2double_test_function_type) (ret_2double_test_type); + +static int ret_2double_test_callback(void *ptr) { + ret_2double_test_function_type f = (ret_2double_test_function_type)ptr; + ret_2double_test_type a = {10, 35}; + ret_2double_test_type r; + r = f(a); + return ((r.x == a.x*5) && (r.y == a.y*3)) ? 0 : -1; +} + +static int ret_2double_test(void) { + const char *src = + "typedef struct ret_2double_test_type_s {double x, y;} ret_2double_test_type;" + "ret_2double_test_type f(ret_2double_test_type a) {\n" + " ret_2double_test_type r = {a.x*5, a.y*3};\n" + " return r;\n" + "}\n"; + + return run_callback(src, ret_2double_test_callback); +} + +/* * reg_pack_test: return a small struct which should be packed into * registers (Win32) during return. */ @@ -105,6 +140,32 @@ static int reg_pack_test(void) { } /* + * reg_pack_longlong_test: return a small struct which should be packed into + * registers (x86-64) during return. + */ +typedef struct reg_pack_longlong_test_type_s {long long x, y;} reg_pack_longlong_test_type; +typedef reg_pack_longlong_test_type (*reg_pack_longlong_test_function_type) (reg_pack_longlong_test_type); + +static int reg_pack_longlong_test_callback(void *ptr) { + reg_pack_longlong_test_function_type f = (reg_pack_longlong_test_function_type)ptr; + reg_pack_longlong_test_type a = {10, 35}; + reg_pack_longlong_test_type r; + r = f(a); + return ((r.x == a.x*5) && (r.y == a.y*3)) ? 0 : -1; +} + +static int reg_pack_longlong_test(void) { + const char *src = + "typedef struct reg_pack_longlong_test_type_s {long long x, y;} reg_pack_longlong_test_type;" + "reg_pack_longlong_test_type f(reg_pack_longlong_test_type a) {\n" + " reg_pack_longlong_test_type r = {a.x*5, a.y*3};\n" + " return r;\n" + "}\n"; + + return run_callback(src, reg_pack_longlong_test_callback); +} + +/* * sret_test: Create a struct large enough to be returned via sret * (hidden pointer as first function argument) */ @@ -129,6 +190,12 @@ static int sret_test(void) { return run_callback(src, sret_test_callback); } +/* + * one_member_union_test: + * + * In the x86-64 ABI a union should always be passed on the stack. However + * it appears that a single member union is treated by GCC as its member. + */ typedef union one_member_union_test_type_u {int x;} one_member_union_test_type; typedef one_member_union_test_type (*one_member_union_test_function_type) (one_member_union_test_type); @@ -151,6 +218,11 @@ static int one_member_union_test(void) { return run_callback(src, one_member_union_test_callback); } +/* + * two_member_union_test: + * + * In the x86-64 ABI a union should always be passed on the stack. + */ typedef union two_member_union_test_type_u {int x; long y;} two_member_union_test_type; typedef two_member_union_test_type (*two_member_union_test_function_type) (two_member_union_test_type); @@ -173,6 +245,41 @@ static int two_member_union_test(void) { return run_callback(src, two_member_union_test_callback); } +/* + * stdarg_test: Test variable argument list ABI + */ + +typedef void (*stdarg_test_function_type) (int,int,...); + +static int stdarg_test_callback(void *ptr) { + stdarg_test_function_type f = (stdarg_test_function_type)ptr; + int x; + double y; + f(10, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &x, + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, &y); + return ((x == 55) && (y == 55)) ? 0 : -1; +} + +static int stdarg_test(void) { + const char *src = + "#include \n" + "void f(int n_int, int n_float, ...) {\n" + " int i, ti;\n" + " double td;\n" + " va_list ap;\n" + " va_start(ap, n_float);\n" + " for (i = 0, ti = 0; i < n_int; ++i)\n" + " ti += va_arg(ap, int);\n" + " *va_arg(ap, int*) = ti;\n" + " for (i = 0, td = 0; i < n_float; ++i)\n" + " td += va_arg(ap, double);\n" + " *va_arg(ap, double*) = td;\n" + " va_end(ap);" + "}\n"; + return run_callback(src, stdarg_test_callback); +} + #define RUN_TEST(t) \ if (!testname || (strcmp(#t, testname) == 0)) { \ fputs(#t "... ", stdout); \ @@ -204,9 +311,12 @@ int main(int argc, char **argv) { RUN_TEST(ret_double_test); RUN_TEST(ret_longdouble_test); RUN_TEST(ret_2float_test); + RUN_TEST(ret_2double_test); RUN_TEST(reg_pack_test); + RUN_TEST(reg_pack_longlong_test); RUN_TEST(sret_test); RUN_TEST(one_member_union_test); RUN_TEST(two_member_union_test); + RUN_TEST(stdarg_test); return retval; } diff --git a/x86_64-gen.c b/x86_64-gen.c index d9873424..318384be 100644 --- a/x86_64-gen.c +++ b/x86_64-gen.c @@ -852,9 +852,6 @@ static X86_64_Mode classify_x86_64_inner(CType *ty) { X86_64_Mode mode; Sym *f; - if (ty->t & VT_BITFIELD) - return x86_64_mode_memory; - switch (ty->t & VT_BTYPE) { case VT_VOID: return x86_64_mode_none; @@ -886,74 +883,79 @@ static X86_64_Mode classify_x86_64_inner(CType *ty) { } } -static X86_64_Mode classify_x86_64_arg(CType *ty, int *psize, int *reg_count) { +static X86_64_Mode classify_x86_64_arg(CType *ty, CType *ret, int *psize, int *reg_count) { X86_64_Mode mode; - int size, align; + int size, align, ret_t; - if (ty->t & VT_ARRAY) { + if (ty->t & (VT_BITFIELD|VT_ARRAY)) { *psize = 8; *reg_count = 1; - return x86_64_mode_integer; - } + ret_t = ty->t; + mode = x86_64_mode_integer; + } else { + size = type_size(ty, &align); + *psize = (size + 7) & ~7; - size = type_size(ty, &align); - size = (size + 7) & ~7; - *psize = size; - if (size > 16) - return x86_64_mode_memory; + if (size > 16) { + mode = x86_64_mode_memory; + } else { + mode = classify_x86_64_inner(ty); + switch (mode) { + case x86_64_mode_integer: + if (size > 8) { + *reg_count = 2; + ret_t = VT_QLONG; + } else { + *reg_count = 1; + ret_t = (size > 4) ? VT_LLONG : VT_INT; + } + break; + + case x86_64_mode_x87: + *reg_count = 1; + ret_t = VT_LDOUBLE; + break; - mode = classify_x86_64_inner(ty); - if (reg_count) { - if (mode == x86_64_mode_integer) - *reg_count = size / 8; - else if (mode == x86_64_mode_none) - *reg_count = 0; - else - *reg_count = 1; + case x86_64_mode_sse: + if (size > 8) { + *reg_count = 2; + ret_t = VT_QFLOAT; + } else { + *reg_count = 1; + ret_t = (size > 4) ? VT_DOUBLE : VT_FLOAT; + } + break; + } + } } + + if (ret) { + ret->ref = NULL; + ret->t = ret_t; + } + return mode; } -static X86_64_Mode classify_x86_64_arg_type(CType *vt, CType *ret, int *psize, int *reg_count) { - X86_64_Mode mode; - int size; - - ret->ref = NULL; - - mode = classify_x86_64_arg(vt, &size, reg_count); - *psize = size; +ST_FUNC int classify_x86_64_va_arg(CType *ty) { + /* This definition must be synced with stdarg.h */ + enum __va_arg_type { + __va_gen_reg, __va_float_reg, __va_stack + }; + int size, reg_count; + X86_64_Mode mode = classify_x86_64_arg(ty, NULL, &size, ®_count); switch (mode) { - case x86_64_mode_integer: - if (size > 8) - ret->t = VT_QLONG; - else if (size > 4) - ret->t = VT_LLONG; - else - ret->t = VT_INT; - break; - - case x86_64_mode_x87: - ret->t = VT_LDOUBLE; - break; - - case x86_64_mode_sse: - if (size > 8) - ret->t = VT_QFLOAT; - else if (size > 4) - ret->t = VT_DOUBLE; - else - ret->t = VT_FLOAT; - break; + default: return __va_stack; + case x86_64_mode_integer: return __va_gen_reg; + case x86_64_mode_sse: return __va_float_reg; } - - return mode; } /* Return 1 if this function returns via an sret pointer, 0 otherwise */ int gfunc_sret(CType *vt, CType *ret, int *ret_align) { int size, reg_count; *ret_align = 1; // Never have to re-align return values for x86-64 - return (classify_x86_64_arg_type(vt, ret, &size, ®_count) == x86_64_mode_memory); + return (classify_x86_64_arg(vt, ret, &size, ®_count) == x86_64_mode_memory); } #define REGN 6 @@ -976,7 +978,7 @@ void gfunc_call(int nb_args) /* calculate the number of integer/float arguments */ args_size = 0; for(i = 0; i < nb_args; i++) { - mode = classify_x86_64_arg(&vtop[-i].type, &size, ®_count); + mode = classify_x86_64_arg(&vtop[-i].type, NULL, &size, ®_count); switch (mode) { case x86_64_mode_memory: case x86_64_mode_x87: @@ -1021,7 +1023,7 @@ void gfunc_call(int nb_args) SValue tmp = vtop[0]; vtop[0] = vtop[-i]; vtop[-i] = tmp; - mode = classify_x86_64_arg(&vtop->type, &size, ®_count); + mode = classify_x86_64_arg(&vtop->type, NULL, &size, ®_count); switch (mode) { case x86_64_mode_memory: /* allocate the necessary size on stack */ @@ -1087,7 +1089,7 @@ void gfunc_call(int nb_args) gen_reg = nb_reg_args; sse_reg = nb_sse_args; for(i = 0; i < nb_args; i++) { - mode = classify_x86_64_arg_type(&vtop->type, &type, &size, ®_count); + mode = classify_x86_64_arg(&vtop->type, &type, &size, ®_count); /* Alter stack entry type so that gv() knows how to treat it */ vtop->type = type; switch (mode) { @@ -1184,24 +1186,29 @@ void gfunc_prolog(CType *func_type) sym = func_type->ref; while ((sym = sym->next) != NULL) { type = &sym->type; - if (is_sse_float(type->t)) { - if (seen_sse_num < 8) { - seen_sse_num++; + mode = classify_x86_64_arg(type, NULL, &size, ®_count); + switch (mode) { + default: + seen_stack_size += size; + break; + + case x86_64_mode_integer: + if (seen_reg_num + reg_count <= 8) { + seen_reg_num += reg_count; } else { - seen_stack_size += 8; + seen_reg_num = 8; + seen_stack_size += size; } - } else if ((type->t & VT_BTYPE) == VT_STRUCT) { - size = type_size(type, &align); - size = (size + 7) & ~7; - seen_stack_size += size; - } else if ((type->t & VT_BTYPE) == VT_LDOUBLE) { - seen_stack_size += LDOUBLE_SIZE; - } else { - if (seen_reg_num < REGN) { - seen_reg_num++; + break; + + case x86_64_mode_sse: + if (seen_sse_num + reg_count <= 8) { + seen_sse_num += reg_count; } else { - seen_stack_size += 8; + seen_sse_num = 8; + seen_stack_size += size; } + break; } } @@ -1239,7 +1246,7 @@ void gfunc_prolog(CType *func_type) /* if the function returns a structure, then add an implicit pointer parameter */ func_vt = sym->type; - mode = classify_x86_64_arg(&func_vt, &size, ®_count); + mode = classify_x86_64_arg(&func_vt, NULL, &size, ®_count); if (mode == x86_64_mode_memory) { push_arg_reg(reg_param_index); param_addr = loc; @@ -1251,7 +1258,7 @@ void gfunc_prolog(CType *func_type) /* define parameters */ while ((sym = sym->next) != NULL) { type = &sym->type; - mode = classify_x86_64_arg(type, &size, ®_count); + mode = classify_x86_64_arg(type, NULL, &size, ®_count); switch (mode) { case x86_64_mode_sse: if (sse_param_index + reg_count <= 8) { -- 2.11.4.GIT