Add arm64 (AArch64) as a target architecture.

This commit is contained in:
Edmund Grimley Evans
2015-02-13 18:58:31 +00:00
parent 738606dbd5
commit b14ef0e24b
16 changed files with 3265 additions and 40 deletions

127
tccgen.c
View File

@ -545,7 +545,7 @@ ST_FUNC void save_reg(int r)
type = &p->type;
if ((p->r & VT_LVAL) ||
(!is_float(type->t) && (type->t & VT_BTYPE) != VT_LLONG))
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
type = &char_pointer_type;
#else
type = &int_type;
@ -562,7 +562,7 @@ ST_FUNC void save_reg(int r)
o(0xd8dd); /* fstp %st(0) */
}
#endif
#ifndef TCC_TARGET_X86_64
#if !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_X86_64)
/* special long long case */
if ((type->t & VT_BTYPE) == VT_LLONG) {
sv.c.ul += 4;
@ -681,7 +681,7 @@ static void move_reg(int r, int s, int t)
}
/* get address of vtop (vtop MUST BE an lvalue) */
static void gaddrof(void)
ST_FUNC void gaddrof(void)
{
if (vtop->r & VT_REF)
gv(RC_INT);
@ -803,11 +803,13 @@ ST_FUNC int gv(int rc)
r = vtop->r & VT_VALMASK;
rc2 = (rc & RC_FLOAT) ? RC_FLOAT : RC_INT;
#ifndef TCC_TARGET_ARM64
if (rc == RC_IRET)
rc2 = RC_LRET;
#ifdef TCC_TARGET_X86_64
else if (rc == RC_FRET)
rc2 = RC_QRET;
#endif
#endif
/* need to reload if:
@ -817,7 +819,7 @@ ST_FUNC int gv(int rc)
if (r >= VT_CONST
|| (vtop->r & VT_LVAL)
|| !(reg_classes[r] & rc)
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
|| ((vtop->type.t & VT_BTYPE) == VT_QLONG && !(reg_classes[vtop->r2] & rc2))
|| ((vtop->type.t & VT_BTYPE) == VT_QFLOAT && !(reg_classes[vtop->r2] & rc2))
#else
@ -826,7 +828,7 @@ ST_FUNC int gv(int rc)
)
{
r = get_reg(rc);
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
if (((vtop->type.t & VT_BTYPE) == VT_QLONG) || ((vtop->type.t & VT_BTYPE) == VT_QFLOAT)) {
int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE;
#else
@ -838,7 +840,7 @@ ST_FUNC int gv(int rc)
original_type = vtop->type.t;
/* two register type load : expand to two words
temporarily */
#ifndef TCC_TARGET_X86_64
#if !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_X86_64)
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) {
/* load constant */
ll = vtop->c.ull;
@ -890,7 +892,7 @@ ST_FUNC int gv(int rc)
t1 = t;
/* compute memory access type */
if (vtop->r & VT_REF)
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
t = VT_PTR;
#else
t = VT_INT;
@ -952,6 +954,7 @@ ST_FUNC void gv2(int rc1, int rc2)
}
}
#ifndef TCC_TARGET_ARM64
/* wrapper around RC_FRET to return a register by type */
static int rc_fret(int t)
{
@ -962,6 +965,7 @@ static int rc_fret(int t)
#endif
return RC_FRET;
}
#endif
/* wrapper around REG_FRET to return a register by type */
static int reg_fret(int t)
@ -1147,7 +1151,7 @@ ST_FUNC int gvtst(int inv, int t)
return gtst(inv, t);
}
#ifndef TCC_TARGET_X86_64
#if !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_X86_64)
/* generate CPU independent (unsigned) long long operations */
static void gen_opl(int op)
{
@ -1358,7 +1362,7 @@ static void gen_opl(int op)
#elif defined(TCC_TARGET_ARM)
b = ind;
o(0x1A000000 | encbranch(ind, 0, 1));
#elif defined(TCC_TARGET_C67)
#elif defined(TCC_TARGET_C67) || defined(TCC_TARGET_ARM64)
tcc_error("not implemented");
#else
#error not supported
@ -1512,7 +1516,8 @@ static void gen_opic(int op)
general_case:
if (!nocode_wanted) {
/* call low level op generator */
if (t1 == VT_LLONG || t2 == VT_LLONG)
if (t1 == VT_LLONG || t2 == VT_LLONG ||
(PTR_SIZE == 8 && (t1 == VT_PTR || t2 == VT_PTR)))
gen_opl(op);
else
gen_opi(op);
@ -1679,7 +1684,7 @@ ST_FUNC void gen_op(int op)
if (op >= TOK_ULT && op <= TOK_LOR) {
check_comparison_pointer_types(vtop - 1, vtop, op);
/* pointers are handled are unsigned */
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
t = VT_LLONG | VT_UNSIGNED;
#else
t = VT_INT | VT_UNSIGNED;
@ -1700,7 +1705,7 @@ ST_FUNC void gen_op(int op)
vrott(3);
gen_opic(op);
/* set to integer type */
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
vtop->type.t = VT_LLONG;
#else
vtop->type.t = VT_INT;
@ -1724,7 +1729,7 @@ ST_FUNC void gen_op(int op)
u = pointed_size(&vtop[-1].type);
if (u < 0)
tcc_error("unknown array element size");
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
vpushll(u);
#else
/* XXX: cast to int ? (long long case) */
@ -1833,6 +1838,9 @@ ST_FUNC void gen_op(int op)
/* generic itof for unsigned long long case */
static void gen_cvt_itof1(int t)
{
#ifdef TCC_TARGET_ARM64
gen_cvt_itof(t);
#else
if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) ==
(VT_LLONG | VT_UNSIGNED)) {
@ -1851,12 +1859,16 @@ static void gen_cvt_itof1(int t)
} else {
gen_cvt_itof(t);
}
#endif
}
#endif
/* generic ftoi for unsigned long long case */
static void gen_cvt_ftoi1(int t)
{
#ifdef TCC_TARGET_ARM64
gen_cvt_ftoi(t);
#else
int st;
if (t == (VT_LLONG | VT_UNSIGNED)) {
@ -1878,6 +1890,7 @@ static void gen_cvt_ftoi1(int t)
} else {
gen_cvt_ftoi(t);
}
#endif
}
/* force char or short cast */
@ -1968,7 +1981,7 @@ static void gen_cast(CType *type)
vtop->c.ll = vtop->c.ull;
else if (sbt & VT_UNSIGNED)
vtop->c.ll = vtop->c.ui;
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
else if (sbt == VT_PTR)
;
#endif
@ -1979,7 +1992,7 @@ static void gen_cast(CType *type)
vtop->c.ull = vtop->c.ll;
else if (dbt == VT_BOOL)
vtop->c.i = (vtop->c.ll != 0);
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
else if (dbt == VT_PTR)
;
#endif
@ -2024,7 +2037,7 @@ static void gen_cast(CType *type)
gen_cast(type);
}
}
#ifndef TCC_TARGET_X86_64
#if !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_X86_64)
} else if ((dbt & VT_BTYPE) == VT_LLONG) {
if ((sbt & VT_BTYPE) != VT_LLONG) {
/* scalar to long long */
@ -2056,11 +2069,18 @@ static void gen_cast(CType *type)
(sbt & VT_BTYPE) != VT_PTR &&
(sbt & VT_BTYPE) != VT_FUNC) {
/* need to convert from 32bit to 64bit */
int r = gv(RC_INT);
gv(RC_INT);
if (sbt != (VT_INT | VT_UNSIGNED)) {
#if defined(TCC_TARGET_ARM64)
gen_cvt_sxtw();
#elif defined(TCC_TARGET_X86_64)
int r = gv(RC_INT);
/* x86_64 specific: movslq */
o(0x6348);
o(0xc0 + (REG_VALUE(r) << 3) + REG_VALUE(r));
#else
#error
#endif
}
}
#endif
@ -2589,7 +2609,7 @@ ST_FUNC void vstore(void)
if ((vtop[-1].r & VT_VALMASK) == VT_LLOCAL) {
SValue sv;
t = get_reg(RC_INT);
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
sv.type.t = VT_PTR;
#else
sv.type.t = VT_INT;
@ -2600,7 +2620,7 @@ ST_FUNC void vstore(void)
vtop[-1].r = t | VT_LVAL;
}
/* two word case handling : store second register at word + 4 (or +8 for x86-64) */
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
if (((ft & VT_BTYPE) == VT_QLONG) || ((ft & VT_BTYPE) == VT_QFLOAT)) {
int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE;
#else
@ -3098,6 +3118,13 @@ static int parse_btype(CType *type, AttributeDef *ad)
goto basic_type1;
}
break;
#ifdef TCC_TARGET_ARM64
case TOK_UINT128:
/* GCC's __uint128_t appears in some Linux header files. Make it a
synonym for long double to get the size and alignment right. */
u = VT_LDOUBLE;
goto basic_type;
#endif
case TOK_BOOL:
u = VT_BOOL;
goto basic_type;
@ -3233,7 +3260,8 @@ the_end:
/* long is never used as type */
if ((t & VT_BTYPE) == VT_LONG)
#if !defined TCC_TARGET_X86_64 || defined TCC_TARGET_PE
#if (!defined TCC_TARGET_X86_64 && !defined TCC_TARGET_ARM64) || \
defined TCC_TARGET_PE
t = (t & ~VT_BTYPE) | VT_INT;
#else
t = (t & ~VT_BTYPE) | VT_LLONG;
@ -3881,6 +3909,36 @@ ST_FUNC void unary(void)
break;
#endif
#endif
#ifdef TCC_TARGET_ARM64
case TOK___va_start: {
next();
skip('(');
expr_eq();
skip(',');
expr_eq();
skip(')');
//xx check types
gen_va_start();
vpushi(0);
vtop->type.t = VT_VOID;
break;
}
case TOK___va_arg: {
CType type;
next();
skip('(');
expr_eq();
skip(',');
parse_type(&type);
skip(')');
//xx check types
gen_va_arg(&type);
vtop->type = type;
break;
}
#endif
case TOK_INC:
case TOK_DEC:
t = tok;
@ -4071,6 +4129,15 @@ ST_FUNC void unary(void)
if (!ret_nregs) {
/* get some space for the returned structure */
size = type_size(&s->type, &align);
#ifdef TCC_TARGET_ARM64
/* On arm64, a small struct is return in registers.
It is much easier to write it to memory if we know
that we are allowed to write some extra bytes, so
round the allocated space up to a power of 2: */
if (size < 16)
while (size & (size - 1))
size = (size | (size - 1)) + 1;
#endif
loc = (loc - size) & -align;
ret.type = s->type;
ret.r = VT_LOCAL | VT_LVAL;
@ -4094,12 +4161,14 @@ ST_FUNC void unary(void)
ret.r2 = REG_QRET;
#endif
} else {
#ifndef TCC_TARGET_ARM64
#ifdef TCC_TARGET_X86_64
if ((ret.type.t & VT_BTYPE) == VT_QLONG)
#else
if ((ret.type.t & VT_BTYPE) == VT_LLONG)
#endif
ret.r2 = REG_LRET;
#endif
ret.r = REG_IRET;
}
ret.c.i = 0;
@ -4717,6 +4786,10 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
if (tok != ';') {
gexpr();
gen_assign_cast(&func_vt);
#ifdef TCC_TARGET_ARM64
// Perhaps it would be better to use this for all backends:
greturn();
#else
if ((func_vt.t & VT_BTYPE) == VT_STRUCT) {
CType type, ret_type;
int ret_align, ret_nregs;
@ -4770,6 +4843,7 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
} else {
gv(RC_IRET);
}
#endif
vtop--; /* NOT vpop() because on x86 it would flush the fp stack */
}
skip(';');
@ -5160,9 +5234,9 @@ static void init_putv(CType *type, Section *sec, unsigned long c,
/* XXX: generate error if incorrect relocation */
gen_assign_cast(&dtype);
bt = type->t & VT_BTYPE;
/* we'll write at most 12 bytes */
if (c + 12 > sec->data_allocated) {
section_realloc(sec, c + 12);
/* we'll write at most 16 bytes */
if (c + 16 > sec->data_allocated) {
section_realloc(sec, c + 16);
}
ptr = sec->data + c;
/* XXX: make code faster ? */
@ -5184,6 +5258,9 @@ static void init_putv(CType *type, Section *sec, unsigned long c,
(bt == VT_INT && bit_size != 32)))
tcc_error("initializer element is not computable at load time");
switch(bt) {
/* XXX: when cross-compiling we assume that each type has the
same representation on host and target, which is likely to
be wrong in the case of long double */
case VT_BOOL:
vtop->c.i = (vtop->c.i != 0);
case VT_BYTE:
@ -5203,7 +5280,7 @@ static void init_putv(CType *type, Section *sec, unsigned long c,
break;
case VT_PTR: {
addr_t val = (vtop->c.ptr_offset & bit_mask) << bit_pos;
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
if (vtop->r & VT_SYM)
greloca(sec, vtop->sym, c, R_DATA_PTR, val);
else
@ -5217,7 +5294,7 @@ static void init_putv(CType *type, Section *sec, unsigned long c,
}
default: {
int val = (vtop->c.i & bit_mask) << bit_pos;
#ifdef TCC_TARGET_X86_64
#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_X86_64)
if (vtop->r & VT_SYM)
greloca(sec, vtop->sym, c, R_DATA_PTR, val);
else