mirror of
https://github.com/mirror/tinycc.git
synced 2024-12-28 04:00:06 +08:00
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).
This commit is contained in:
parent
55ea6d3fc1
commit
b961ba5396
3
tcc.h
3
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 ------------ */
|
||||
|
||||
|
18
tccgen.c
18
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);
|
||||
}
|
||||
}
|
||||
|
110
tests/abitest.c
110
tests/abitest.c
@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
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);
|
||||
|
||||
@ -78,6 +85,34 @@ static int ret_2float_test(void) {
|
||||
return run_callback(src, ret_2float_test_callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
@ -104,6 +139,32 @@ static int reg_pack_test(void) {
|
||||
return run_callback(src, reg_pack_test_callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 <stdarg.h>\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;
|
||||
}
|
||||
|
153
x86_64-gen.c
153
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size = type_size(ty, &align);
|
||||
size = (size + 7) & ~7;
|
||||
*psize = size;
|
||||
if (size > 16)
|
||||
return x86_64_mode_memory;
|
||||
|
||||
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;
|
||||
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++;
|
||||
} else {
|
||||
seen_stack_size += 8;
|
||||
}
|
||||
} else if ((type->t & VT_BTYPE) == VT_STRUCT) {
|
||||
size = type_size(type, &align);
|
||||
size = (size + 7) & ~7;
|
||||
mode = classify_x86_64_arg(type, NULL, &size, ®_count);
|
||||
switch (mode) {
|
||||
default:
|
||||
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_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;
|
||||
}
|
||||
break;
|
||||
|
||||
case x86_64_mode_sse:
|
||||
if (seen_sse_num + reg_count <= 8) {
|
||||
seen_sse_num += reg_count;
|
||||
} else {
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user