x86-64 ABI fixes.

abitest now passes; however test1-3 fail in init_test. All other tests
pass. I need to re-test Win32 and Linux-x86.

I've added a dummy implementation of gfunc_sret to c67-gen.c so it
should now compile, and I think it should behave as before I created
gfunc_sret.
This commit is contained in:
James Lyon
2013-04-19 00:40:48 +01:00
parent 3f1d900007
commit 55ea6d3fc1
9 changed files with 334 additions and 75 deletions

2
.gitignore vendored
View File

@ -57,3 +57,5 @@ tcc-doc.info
conftest* conftest*
tiny_libmaker tiny_libmaker
*.dSYM *.dSYM
*~

View File

@ -801,17 +801,22 @@ int assign_fpreg(struct avail_regs *avregs, int align, int size)
#endif #endif
/* Return 1 if this function returns via an sret pointer, 0 otherwise */ /* Return 1 if this function returns via an sret pointer, 0 otherwise */
ST_FUNC int gfunc_sret(CType *vt, CType *ret, int *align) { ST_FUNC int gfunc_sret(CType *vt, CType *ret, int *ret_align) {
#if TCC_ARM_EABI
int size, align;
size = type_size(vt, &align); size = type_size(vt, &align);
if (size > 4) { if (size > 4) {
return 1; return 1;
} else { } else {
*align = 4; *ret_align = 4;
ret->ref = NULL; ret->ref = NULL;
ret->t = VT_INT; ret->t = VT_INT;
}
return 0; return 0;
} }
#else
return 1;
#endif
}
/* Generate function call. The function address is pushed first, then /* Generate function call. The function address is pushed first, then
all the parameters in call order. This functions pops all the all the parameters in call order. This functions pops all the

View File

@ -1879,6 +1879,12 @@ static void gcall_or_jmp(int is_jmp)
} }
} }
/* Return 1 if this function returns via an sret pointer, 0 otherwise */
ST_FUNC int gfunc_sret(CType *vt, CType *ret, int *ret_align) {
*ret_align = 1; // Never have to re-align return values for x86-64
return 1;
}
/* generate function call with address in (vtop->t, vtop->c) and free function /* generate function call with address in (vtop->t, vtop->c) and free function
context. Stack entry is popped */ context. Stack entry is popped */
void gfunc_call(int nb_args) void gfunc_call(int nb_args)

2
tcc.c
View File

@ -69,7 +69,7 @@ static void help(void)
" -Bdir use 'dir' as tcc internal library and include path\n" " -Bdir use 'dir' as tcc internal library and include path\n"
" -MD generate target dependencies for make\n" " -MD generate target dependencies for make\n"
" -MF depfile put generated dependencies here\n" " -MF depfile put generated dependencies here\n"
" -norunsrc Do not compile the file which is the first argument after -run." " -norunsrc Do not compile the file which is the first argument after -run.\n"
); );
} }

2
tcc.h
View File

@ -718,6 +718,8 @@ struct TCCState {
#define VT_LLONG 12 /* 64 bit integer */ #define VT_LLONG 12 /* 64 bit integer */
#define VT_LONG 13 /* long integer (NEVER USED as type, only #define VT_LONG 13 /* long integer (NEVER USED as type, only
during parsing) */ during parsing) */
#define VT_QLONG 14 /* 128-bit integer. Only used for x86-64 ABI */
#define VT_QFLOAT 15 /* 128-bit float. Only used for x86-64 ABI */
#define VT_UNSIGNED 0x0010 /* unsigned type */ #define VT_UNSIGNED 0x0010 /* unsigned type */
#define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */ #define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */
#define VT_BITFIELD 0x0040 /* bitfield modifier */ #define VT_BITFIELD 0x0040 /* bitfield modifier */

View File

@ -2495,21 +2495,29 @@ ST_FUNC void vstore(void)
vtop[-1].r = t | VT_LVAL; vtop[-1].r = t | VT_LVAL;
} }
store(r, vtop - 1); store(r, vtop - 1);
#ifndef TCC_TARGET_X86_64 /* two word case handling : store second register at word + 4 (or +8 for x86-64) */
/* two word case handling : store second register at word + 4 */ #ifdef TCC_TARGET_X86_64
if ((ft & VT_BTYPE) == VT_QLONG) {
#else
if ((ft & VT_BTYPE) == VT_LLONG) { if ((ft & VT_BTYPE) == VT_LLONG) {
#endif
vswap(); vswap();
/* convert to int to increment easily */ /* convert to int to increment easily */
#ifdef TCC_TARGET_X86_64
vtop->type.t = VT_LLONG;
gaddrof();
vpushi(8);
#else
vtop->type.t = VT_INT; vtop->type.t = VT_INT;
gaddrof(); gaddrof();
vpushi(4); vpushi(4);
#endif
gen_op('+'); gen_op('+');
vtop->r |= VT_LVAL; vtop->r |= VT_LVAL;
vswap(); vswap();
/* XXX: it works because r2 is spilled last ! */ /* XXX: it works because r2 is spilled last ! */
store(vtop->r2, vtop - 1); store(vtop->r2, vtop - 1);
} }
#endif
} }
vswap(); vswap();
vtop--; /* NOT vpop() because on x86 it would flush the fp stack */ vtop--; /* NOT vpop() because on x86 it would flush the fp stack */

View File

@ -188,8 +188,8 @@ abitest-tcc$(EXESUF): abitest.c $(top_builddir)/$(LIBTCC)
abitest: abitest-cc$(EXESUF) abitest-tcc$(EXESUF) abitest: abitest-cc$(EXESUF) abitest-tcc$(EXESUF)
@echo ------------ $@ ------------ @echo ------------ $@ ------------
abitest-cc$(EXESUF) lib_path=.. ./abitest-cc$(EXESUF) lib_path=..
abitest-tcc$(EXESUF) lib_path=.. ./abitest-tcc$(EXESUF) lib_path=..
# targets for development # targets for development
%.bin: %.c tcc %.bin: %.c tcc

View File

@ -54,6 +54,7 @@ RET_PRIMITIVE_TEST(int, int)
RET_PRIMITIVE_TEST(longlong, long long) RET_PRIMITIVE_TEST(longlong, long long)
RET_PRIMITIVE_TEST(float, float) RET_PRIMITIVE_TEST(float, float)
RET_PRIMITIVE_TEST(double, double) RET_PRIMITIVE_TEST(double, double)
RET_PRIMITIVE_TEST(longdouble, long double)
typedef struct ret_2float_test_type_s {float x, y;} ret_2float_test_type; 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); typedef ret_2float_test_type (*ret_2float_test_function_type) (ret_2float_test_type);
@ -128,8 +129,52 @@ static int sret_test(void) {
return run_callback(src, sret_test_callback); return run_callback(src, sret_test_callback);
} }
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);
static int one_member_union_test_callback(void *ptr) {
one_member_union_test_function_type f = (one_member_union_test_function_type)ptr;
one_member_union_test_type a, b;
a.x = 34;
b = f(a);
return (b.x == a.x*2) ? 0 : -1;
}
static int one_member_union_test(void) {
const char *src =
"typedef union one_member_union_test_type_u {int x;} one_member_union_test_type;\n"
"one_member_union_test_type f(one_member_union_test_type a) {\n"
" one_member_union_test_type b;\n"
" b.x = a.x * 2;\n"
" return b;\n"
"}\n";
return run_callback(src, one_member_union_test_callback);
}
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);
static int two_member_union_test_callback(void *ptr) {
two_member_union_test_function_type f = (two_member_union_test_function_type)ptr;
two_member_union_test_type a, b;
a.x = 34;
b = f(a);
return (b.x == a.x*2) ? 0 : -1;
}
static int two_member_union_test(void) {
const char *src =
"typedef union two_member_union_test_type_u {int x; long y;} two_member_union_test_type;\n"
"two_member_union_test_type f(two_member_union_test_type a) {\n"
" two_member_union_test_type b;\n"
" b.x = a.x * 2;\n"
" return b;\n"
"}\n";
return run_callback(src, two_member_union_test_callback);
}
#define RUN_TEST(t) \ #define RUN_TEST(t) \
do { \ if (!testname || (strcmp(#t, testname) == 0)) { \
fputs(#t "... ", stdout); \ fputs(#t "... ", stdout); \
fflush(stdout); \ fflush(stdout); \
if (t() == 0) { \ if (t() == 0) { \
@ -138,20 +183,30 @@ static int sret_test(void) {
fputs("failure\n", stdout); \ fputs("failure\n", stdout); \
retval = EXIT_FAILURE; \ retval = EXIT_FAILURE; \
} \ } \
} while (0); }
int main(int argc, char **argv) { int main(int argc, char **argv) {
int i;
const char *testname = NULL;
int retval = EXIT_SUCCESS; int retval = EXIT_SUCCESS;
/* if tcclib.h and libtcc1.a are not installed, where can we find them */ /* if tcclib.h and libtcc1.a are not installed, where can we find them */
if (argc == 2 && !memcmp(argv[1], "lib_path=",9)) for (i = 1; i < argc; ++i) {
tccdir = argv[1] + 9; if (!memcmp(argv[i], "lib_path=",9))
tccdir = argv[i] + 9;
else if (!memcmp(argv[i], "run_test=", 9))
testname = argv[i] + 9;
}
RUN_TEST(ret_int_test); RUN_TEST(ret_int_test);
RUN_TEST(ret_longlong_test); RUN_TEST(ret_longlong_test);
RUN_TEST(ret_float_test); RUN_TEST(ret_float_test);
RUN_TEST(ret_double_test); RUN_TEST(ret_double_test);
RUN_TEST(ret_longdouble_test);
RUN_TEST(ret_2float_test); RUN_TEST(ret_2float_test);
RUN_TEST(reg_pack_test); RUN_TEST(reg_pack_test);
RUN_TEST(sret_test); RUN_TEST(sret_test);
RUN_TEST(one_member_union_test);
RUN_TEST(two_member_union_test);
return retval; return retval;
} }

View File

@ -602,6 +602,12 @@ void gen_offs_sp(int b, int r, int d)
} }
} }
/* Return 1 if this function returns via an sret pointer, 0 otherwise */
ST_FUNC int gfunc_sret(CType *vt, CType *ret, int *ret_align) {
*ret_align = 1; // Never have to re-align return values for x86-64
return 1;
}
void gfunc_call(int nb_args) void gfunc_call(int nb_args)
{ {
int size, align, r, args_size, i, d, j, bt, struct_size; int size, align, r, args_size, i, d, j, bt, struct_size;
@ -817,6 +823,139 @@ static void gadd_sp(int val)
} }
} }
typedef enum X86_64_Mode {
x86_64_mode_none,
x86_64_mode_memory,
x86_64_mode_integer,
x86_64_mode_sse,
x86_64_mode_x87
} X86_64_Mode;
static X86_64_Mode classify_x86_64_merge(X86_64_Mode a, X86_64_Mode b) {
if (a == b)
return a;
else if (a == x86_64_mode_none)
return b;
else if (b == x86_64_mode_none)
return a;
else if ((a == x86_64_mode_memory) || (b == x86_64_mode_memory))
return x86_64_mode_memory;
else if ((a == x86_64_mode_integer) || (b == x86_64_mode_integer))
return x86_64_mode_integer;
else if ((a == x86_64_mode_x87) || (b == x86_64_mode_x87))
return x86_64_mode_memory;
else
return x86_64_mode_sse;
}
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;
case VT_INT:
case VT_BYTE:
case VT_SHORT:
case VT_LLONG:
case VT_BOOL:
case VT_PTR:
case VT_ENUM: return x86_64_mode_integer;
case VT_FLOAT:
case VT_DOUBLE: return x86_64_mode_sse;
case VT_LDOUBLE: return x86_64_mode_x87;
case VT_STRUCT:
f = ty->ref;
// Detect union
if (f->next && (f->c == f->next->c))
return x86_64_mode_memory;
mode = x86_64_mode_none;
for (; f; f = f->next)
mode = classify_x86_64_merge(mode, classify_x86_64_inner(&f->type));
return mode;
}
}
static X86_64_Mode classify_x86_64_arg(CType *ty, int *psize, int *reg_count) {
X86_64_Mode mode;
int size, align;
if (ty->t & VT_ARRAY) {
*psize = 8;
*reg_count = 1;
return x86_64_mode_integer;
}
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;
}
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;
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;
}
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, &reg_count) == x86_64_mode_memory);
}
#define REGN 6 #define REGN 6
static const uint8_t arg_regs[REGN] = { static const uint8_t arg_regs[REGN] = {
TREG_RDI, TREG_RSI, TREG_RDX, TREG_RCX, TREG_R8, TREG_R9 TREG_RDI, TREG_RSI, TREG_RDX, TREG_RCX, TREG_R8, TREG_R9
@ -827,7 +966,9 @@ static const uint8_t arg_regs[REGN] = {
parameters and the function address. */ parameters and the function address. */
void gfunc_call(int nb_args) void gfunc_call(int nb_args)
{ {
int size, align, r, args_size, i; X86_64_Mode mode;
CType type;
int size, align, r, args_size, i, j, reg_count;
int nb_reg_args = 0; int nb_reg_args = 0;
int nb_sse_args = 0; int nb_sse_args = 0;
int sse_reg, gen_reg; int sse_reg, gen_reg;
@ -835,17 +976,22 @@ void gfunc_call(int nb_args)
/* calculate the number of integer/float arguments */ /* calculate the number of integer/float arguments */
args_size = 0; args_size = 0;
for(i = 0; i < nb_args; i++) { for(i = 0; i < nb_args; i++) {
if ((vtop[-i].type.t & VT_BTYPE) == VT_STRUCT) { mode = classify_x86_64_arg(&vtop[-i].type, &size, &reg_count);
args_size += type_size(&vtop[-i].type, &align); switch (mode) {
args_size = (args_size + 7) & ~7; case x86_64_mode_memory:
} else if ((vtop[-i].type.t & VT_BTYPE) == VT_LDOUBLE) { case x86_64_mode_x87:
args_size += 16; args_size += size;
} else if (is_sse_float(vtop[-i].type.t)) { break;
nb_sse_args++;
if (nb_sse_args > 8) args_size += 8; case x86_64_mode_sse:
} else { nb_sse_args += reg_count;
nb_reg_args++; if (nb_sse_args > 8) args_size += size;
if (nb_reg_args > REGN) args_size += 8; break;
case x86_64_mode_integer:
nb_reg_args += reg_count;
if (nb_reg_args > REGN) args_size += size;
break;
} }
} }
@ -875,10 +1021,9 @@ void gfunc_call(int nb_args)
SValue tmp = vtop[0]; SValue tmp = vtop[0];
vtop[0] = vtop[-i]; vtop[0] = vtop[-i];
vtop[-i] = tmp; vtop[-i] = tmp;
if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) { mode = classify_x86_64_arg(&vtop->type, &size, &reg_count);
size = type_size(&vtop->type, &align); switch (mode) {
/* align to stack align size */ case x86_64_mode_memory:
size = (size + 7) & ~7;
/* allocate the necessary size on stack */ /* allocate the necessary size on stack */
o(0x48); o(0x48);
oad(0xec81, size); /* sub $xxx, %rsp */ oad(0xec81, size); /* sub $xxx, %rsp */
@ -890,7 +1035,9 @@ void gfunc_call(int nb_args)
vswap(); vswap();
vstore(); vstore();
args_size += size; args_size += size;
} else if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { break;
case x86_64_mode_x87:
gv(RC_ST0); gv(RC_ST0);
size = LDOUBLE_SIZE; size = LDOUBLE_SIZE;
oad(0xec8148, size); /* sub $xxx, %rsp */ oad(0xec8148, size); /* sub $xxx, %rsp */
@ -898,25 +1045,30 @@ void gfunc_call(int nb_args)
g(0x24); g(0x24);
g(0x00); g(0x00);
args_size += size; args_size += size;
} else if (is_sse_float(vtop->type.t)) { break;
int j = --sse_reg;
if (j >= 8) { case x86_64_mode_sse:
if (sse_reg > 8) {
gv(RC_FLOAT); gv(RC_FLOAT);
o(0x50); /* push $rax */ o(0x50); /* push $rax */
/* movq %xmm0, (%rsp) */ /* movq %xmm0, (%rsp) */
o(0x04d60f66); o(0x04d60f66);
o(0x24); o(0x24);
args_size += 8; args_size += size;
} }
} else { sse_reg -= reg_count;
int j = --gen_reg; break;
case x86_64_mode_integer:
/* simple type */ /* simple type */
/* XXX: implicit cast ? */ /* XXX: implicit cast ? */
if (j >= REGN) { if (gen_reg > REGN) {
r = gv(RC_INT); r = gv(RC_INT);
orex(0,r,0,0x50 + REG_VALUE(r)); /* push r */ orex(0,r,0,0x50 + REG_VALUE(r)); /* push r */
args_size += 8; args_size += size;
} }
gen_reg -= reg_count;
break;
} }
/* And swap the argument back to it's original position. */ /* And swap the argument back to it's original position. */
@ -935,30 +1087,46 @@ void gfunc_call(int nb_args)
gen_reg = nb_reg_args; gen_reg = nb_reg_args;
sse_reg = nb_sse_args; sse_reg = nb_sse_args;
for(i = 0; i < nb_args; i++) { for(i = 0; i < nb_args; i++) {
if ((vtop->type.t & VT_BTYPE) == VT_STRUCT || mode = classify_x86_64_arg_type(&vtop->type, &type, &size, &reg_count);
(vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { /* Alter stack entry type so that gv() knows how to treat it */
} else if (is_sse_float(vtop->type.t)) { vtop->type = type;
int j = --sse_reg; switch (mode) {
if (j < 8) { default:
break;
case x86_64_mode_sse:
if (sse_reg > 8) {
sse_reg -= reg_count;
} else {
for (j = 0; j < reg_count; ++j) {
--sse_reg;
gv(RC_FLOAT); /* only one float register */ gv(RC_FLOAT); /* only one float register */
/* movaps %xmm0, %xmmN */ /* movaps %xmm0, %xmmN */
o(0x280f); o(0x280f);
o(0xc0 + (sse_reg << 3)); o(0xc0 + (sse_reg << 3));
} }
} else { }
int j = --gen_reg; break;
case x86_64_mode_integer:
/* simple type */ /* simple type */
/* XXX: implicit cast ? */ /* XXX: implicit cast ? */
if (j < REGN) { if (gen_reg > 8) {
int d = arg_regs[j]; gen_reg -= reg_count;
} else {
for (j = 0; j < reg_count; ++j) {
--gen_reg;
int d = arg_regs[gen_reg];
r = gv(RC_INT); r = gv(RC_INT);
if (j == 2 || j == 3) if (gen_reg == 2 || gen_reg == 3)
/* j=2: r10, j=3: r11 */ /* gen_reg=2: r10, gen_reg=3: r11 */
d = j + 8; d = gen_reg + 8;
orex(1,d,r,0x89); /* mov */ orex(1,d,r,0x89); /* mov */
o(0xc0 + REG_VALUE(r) * 8 + REG_VALUE(d)); o(0xc0 + REG_VALUE(r) * 8 + REG_VALUE(d));
} }
} }
break;
}
vtop--; vtop--;
} }
@ -994,7 +1162,8 @@ static void push_arg_reg(int i) {
/* generate function prolog of type 't' */ /* generate function prolog of type 't' */
void gfunc_prolog(CType *func_type) void gfunc_prolog(CType *func_type)
{ {
int i, addr, align, size; X86_64_Mode mode;
int i, addr, align, size, reg_count;
int param_index, param_addr, reg_param_index, sse_param_index; int param_index, param_addr, reg_param_index, sse_param_index;
Sym *sym; Sym *sym;
CType *type; CType *type;
@ -1070,7 +1239,8 @@ void gfunc_prolog(CType *func_type)
/* if the function returns a structure, then add an /* if the function returns a structure, then add an
implicit pointer parameter */ implicit pointer parameter */
func_vt = sym->type; func_vt = sym->type;
if ((func_vt.t & VT_BTYPE) == VT_STRUCT) { mode = classify_x86_64_arg(&func_vt, &size, &reg_count);
if (mode == x86_64_mode_memory) {
push_arg_reg(reg_param_index); push_arg_reg(reg_param_index);
param_addr = loc; param_addr = loc;
@ -1081,35 +1251,46 @@ void gfunc_prolog(CType *func_type)
/* define parameters */ /* define parameters */
while ((sym = sym->next) != NULL) { while ((sym = sym->next) != NULL) {
type = &sym->type; type = &sym->type;
size = type_size(type, &align); mode = classify_x86_64_arg(type, &size, &reg_count);
size = (size + 7) & ~7; switch (mode) {
if (is_sse_float(type->t)) { case x86_64_mode_sse:
if (sse_param_index < 8) { if (sse_param_index + reg_count <= 8) {
/* save arguments passed by register */ /* save arguments passed by register */
for (i = 0; i < reg_count; ++i) {
loc -= 8; loc -= 8;
o(0xd60f66); /* movq */ o(0xd60f66); /* movq */
gen_modrm(sse_param_index, VT_LOCAL, NULL, loc); gen_modrm(sse_param_index, VT_LOCAL, NULL, loc);
++sse_param_index;
}
param_addr = loc; param_addr = loc;
} else { } else {
param_addr = addr; param_addr = addr;
addr += size; addr += size;
sse_param_index += reg_count;
} }
sse_param_index++; break;
} else if ((type->t & VT_BTYPE) == VT_STRUCT || case x86_64_mode_memory:
(type->t & VT_BTYPE) == VT_LDOUBLE) { case x86_64_mode_x87:
param_addr = addr; param_addr = addr;
addr += size; addr += size;
} else { break;
if (reg_param_index < REGN) {
case x86_64_mode_integer: {
if (reg_param_index + reg_count <= REGN) {
/* save arguments passed by register */ /* save arguments passed by register */
for (i = 0; i < reg_count; ++i) {
push_arg_reg(reg_param_index); push_arg_reg(reg_param_index);
++reg_param_index;
}
param_addr = loc; param_addr = loc;
} else { } else {
param_addr = addr; param_addr = addr;
addr += 8; addr += size;
reg_param_index += reg_count;
}
break;
} }
reg_param_index++;
} }
sym_push(sym->v & ~SYM_FIELD, type, sym_push(sym->v & ~SYM_FIELD, type,
VT_LOCAL | VT_LVAL, param_addr); VT_LOCAL | VT_LVAL, param_addr);