[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] tccgen: fix casts using code from upstream
From: |
Ekaitz Zarraga |
Subject: |
[PATCH] tccgen: fix casts using code from upstream |
Date: |
Wed, 8 May 2024 12:45:37 +0200 |
We detected issues: tcc-mob built with us was failing to compile code
like:
unsigned long x = 0xcafecafe;
It was applying sign extension, producing a `x == 0xffffffffcafecafe`.
Instead, when built with GCC tcc-mob produced the proper
`x == 0xcafecafe`. This is because our casts are faulty in RISC-V
because it always sign-extends and we need to clear those high bits
after the sign-extension.
Proper compilation should produce this after the value is loaded:
slli reg, reg, 0x20 // shift left 32 times
srli reg, reg, 0x20 // and back
Meaning the highest 32 bits are cleared.
Our casts didn't detect the need for this, and many appearances of
integer constants in tcc-mob were miscompiled, producing future errors
in the chain: `load`, code generation, elf...
This commit backports the `gen_cast` function from tcc-mob and
manipulates the code around it to make it fit in our internals. It's not
superclean, but it should work.
* arm64-gen.c (gen_cvt_csti): New function for char to int conversion.
* i386-gen.c (gen_cvt_csti): Likewise.
* x86_64-gen.c (gen_cvt_csti): Likewise.
(gen_cvt_sxtw): New function for word sign-extension.
* tcc.h: Add them to headers.
* tccgen.c (gen_cast): Update from upstream.
(btype_size): Add function.
---
arm64-gen.c | 10 ++
i386-gen.c | 13 +++
tcc.h | 3 +
tccgen.c | 285 ++++++++++++++++++++++++++++-----------------------
x86_64-gen.c | 23 +++++
5 files changed, 208 insertions(+), 126 deletions(-)
diff --git a/arm64-gen.c b/arm64-gen.c
index c7a71e64..42958169 100644
--- a/arm64-gen.c
+++ b/arm64-gen.c
@@ -1663,6 +1663,16 @@ ST_FUNC void gen_cvt_sxtw(void)
o(0x93407c00 | r | r << 5); // sxtw x(r),w(r)
}
+/* char/short to int conversion */
+ST_FUNC void gen_cvt_csti(int t)
+{
+ int r = intr(gv(RC_INT));
+ o(0x13001c00
+ | ((t & VT_BTYPE) == VT_SHORT) << 13
+ | (uint32_t)!!(t & VT_UNSIGNED) << 30
+ | r | r << 5); // [su]xt[bh] w(r),w(r)
+}
+
ST_FUNC void gen_cvt_itof(int t)
{
if (t == VT_LDOUBLE) {
diff --git a/i386-gen.c b/i386-gen.c
index 361f60a0..2a1543d1 100644
--- a/i386-gen.c
+++ b/i386-gen.c
@@ -1066,6 +1066,19 @@ ST_FUNC void gen_cvt_ftof(int t)
gv(RC_FLOAT);
}
+/* char/short to int conversion */
+ST_FUNC void gen_cvt_csti(int t)
+{
+ int r, sz, xl;
+ r = gv(RC_INT);
+ sz = !(t & VT_UNSIGNED);
+ xl = (t & VT_BTYPE) == VT_SHORT;
+ o(0xc0b60f /* mov[sz] %a[xl], %eax */
+ | (sz << 3 | xl) << 8
+ | (r << 3 | r) << 16
+ );
+}
+
/* computed goto support */
ST_FUNC void ggoto(void)
{
diff --git a/tcc.h b/tcc.h
index 43ebdd0a..f9285fe3 100644
--- a/tcc.h
+++ b/tcc.h
@@ -1564,6 +1564,7 @@ ST_FUNC void gen_le16(int c);
ST_FUNC void gen_le32(int c);
ST_FUNC void gen_addr32(int r, Sym *sym, long c);
ST_FUNC void gen_addrpc32(int r, Sym *sym, long c);
+ST_FUNC void gen_cvt_csti(int t);
#endif
#ifdef CONFIG_TCC_BCHECK
@@ -1573,6 +1574,7 @@ ST_FUNC void gen_bounded_ptr_deref(void);
/* ------------ x86_64-gen.c ------------ */
#ifdef TCC_TARGET_X86_64
+ST_FUNC void gen_cvt_sxtw(void);
ST_FUNC void gen_addr64(int r, Sym *sym, int64_t c);
ST_FUNC void gen_opl(int op);
#endif
@@ -1589,6 +1591,7 @@ ST_FUNC void gen_cvt_itof1(int t);
/* ------------ arm64-gen.c ------------ */
#ifdef TCC_TARGET_ARM64
ST_FUNC void gen_cvt_sxtw(void);
+ST_FUNC void gen_cvt_csti(int t);
ST_FUNC void gen_opl(int op);
ST_FUNC void gfunc_return(CType *func_type);
ST_FUNC void gen_va_start(void);
diff --git a/tccgen.c b/tccgen.c
index 8423fcf2..35552ec4 100644
--- a/tccgen.c
+++ b/tccgen.c
@@ -2313,10 +2313,15 @@ static void force_charshort_cast(int t)
}
}
+static void type_to_str(char *buf, int buf_size,
+ CType *type, const char *varstr);
+
/* cast 'vtop' to 'type'. Casting to bitfields is forbidden. */
static void gen_cast(CType *type)
{
- int sbt, dbt, sf, df, c, p;
+ int sbt, dbt, sf, df, c, ignored;
+ int dbt_bt, sbt_bt, ds, ss, bits, trunc;
+ char errbuf1[256], errbuf2[256];
/* special delayed cast for char/short */
/* XXX: in some cases (multiple cascaded casts), it may still
@@ -2333,12 +2338,29 @@ static void gen_cast(CType *type)
dbt = type->t & (VT_BTYPE | VT_UNSIGNED);
sbt = vtop->type.t & (VT_BTYPE | VT_UNSIGNED);
+ if (sbt == VT_FUNC)
+ sbt = VT_PTR;
+again:
if (sbt != dbt) {
sf = is_float(sbt);
df = is_float(dbt);
+ dbt_bt = dbt & VT_BTYPE;
+ sbt_bt = sbt & VT_BTYPE;
+ if (dbt_bt == VT_VOID)
+ goto done;
+ if (sbt_bt == VT_VOID) {
+error:
+ type_to_str(errbuf1, sizeof(errbuf1), &vtop->type, NULL);
+ type_to_str(errbuf2, sizeof(errbuf2), type, NULL);
+ tcc_error("cannot convert '%s' to '%s'", errbuf1, errbuf2);
+ }
+
c = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
- p = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == (VT_CONST | VT_SYM);
+#if !defined TCC_IS_NATIVE && !defined TCC_IS_NATIVE_387
+ if (dbt_bt == VT_LDOUBLE && !nocode_wanted && (sf || vtop->c.i != 0))
+ c = 0;
+#endif
if (c) {
/* constant case: we can do it now */
/* XXX: in ISOC, cannot do it if error in convert */
@@ -2349,7 +2371,7 @@ static void gen_cast(CType *type)
vtop->c.ld = vtop->c.d;
if (df) {
- if ((sbt & VT_BTYPE) == VT_LLONG) {
+ if (sbt_bt == VT_LLONG) {
if ((sbt & VT_UNSIGNED) || !(vtop->c.i >> 63))
vtop->c.ld = vtop->c.i;
else
@@ -2365,9 +2387,6 @@ static void gen_cast(CType *type)
vtop->c.f = (float)vtop->c.ld;
else if (dbt == VT_DOUBLE)
vtop->c.d = (double)vtop->c.ld;
- }
- else if (sf && dbt == (VT_LLONG|VT_UNSIGNED)) {
- vtop->c.i = vtop->c.ld;
} else if (sf && dbt == VT_BOOL) {
vtop->c.i = (vtop->c.ld != 0);
} else
@@ -2381,151 +2400,165 @@ static void gen_cast(CType *type)
if (sbt == (VT_LLONG|VT_UNSIGNED))
;
else if (sbt & VT_UNSIGNED)
-#if defined(TCC_TARGET_RISCV64)
- {
- /* RISC-V keeps 32bit vals in registers sign-extended.
- So here we need a zero-extension. */
- vtop->type.t = VT_LLONG;
- vpushi(32);
- gen_op(TOK_SHL);
- vpushi(32);
- gen_op(TOK_SHR);
- }
-#else
- vtop->c.i = (uint32_t)vtop->c.i; // ERROR IS HERE
-#endif
-#if PTR_SIZE == 8
- else if (sbt == VT_PTR)
- ;
-#endif
- else if (sbt != VT_LLONG)
- vtop->c.i = ((uint32_t)vtop->c.i |
- -(vtop->c.i & 0x80000000));
+ vtop->c.i = (uint32_t)vtop->c.i;
+ else
+ vtop->c.i = ((uint32_t)vtop->c.i | -(vtop->c.i &
0x80000000));
- if (dbt == (VT_LLONG|VT_UNSIGNED))
+ if (dbt_bt == VT_LLONG || (PTR_SIZE == 8 && dbt == VT_PTR))
;
else if (dbt == VT_BOOL)
vtop->c.i = (vtop->c.i != 0);
-#if PTR_SIZE == 8
- else if (dbt == VT_PTR)
- ;
-#endif
- else if (dbt != VT_LLONG) {
- uint32_t m = ((dbt & VT_BTYPE) == VT_BYTE ? 0xff :
- (dbt & VT_BTYPE) == VT_SHORT ? 0xffff :
- 0xffffffff);
+ else {
+ uint32_t m = dbt_bt == VT_BYTE ? 0xff :
+ dbt_bt == VT_SHORT ? 0xffff :
+ 0xffffffff;
vtop->c.i &= m;
if (!(dbt & VT_UNSIGNED))
vtop->c.i |= -(vtop->c.i & ((m >> 1) + 1));
}
}
- } else if (p && dbt == VT_BOOL) {
+ goto done;
+ } else if (dbt == VT_BOOL
+ && (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM))
+ == (VT_CONST | VT_SYM)) {
+ /* addresses are considered non-zero (see tcctest.c:sinit23) */
vtop->r = VT_CONST;
vtop->c.i = 1;
- } else {
- /* non constant case: generate code */
+ goto done;
+ }
+
+ /* cannot generate code for global or static initializers */
+ if (nocode_wanted)
+ goto done;
+
+ /* non constant case: generate code */
+ if (dbt == VT_BOOL) {
+ vpushi(0);
+ gen_op(TOK_NE);
+ goto done;
+ }
+
+ if (sf || df) {
if (sf && df) {
/* convert from fp to fp */
gen_cvt_ftof(dbt);
} else if (df) {
/* convert int to fp */
gen_cvt_itof1(dbt);
- } else if (sf) {
+ } else {
/* convert fp to int */
- if (dbt == VT_BOOL) {
- vpushi(0);
- gen_op(TOK_NE);
- } else {
- /* we handle char/short/etc... with generic code */
- if (dbt != (VT_INT | VT_UNSIGNED) &&
- dbt != (VT_LLONG | VT_UNSIGNED) &&
- dbt != VT_LLONG)
- dbt = VT_INT;
- gen_cvt_ftoi1(dbt);
- if (dbt == VT_INT && (type->t & (VT_BTYPE | VT_UNSIGNED))
!= dbt) {
- /* additional cast for char/short... */
- vtop->type.t = dbt;
- gen_cast(type);
- }
- }
+ sbt = dbt;
+ if (dbt_bt != VT_LLONG && dbt_bt != VT_INT)
+ sbt = VT_INT;
+ gen_cvt_ftoi1(sbt);
+ goto again; /* may need char/short cast */
+ }
+ goto done;
+ }
+
+ ds = type_size(type, &ignored);
+ ss = type_size(&vtop->type, &ignored);
+ if (ds == 0 || ss == 0)
+ goto error;
+
+ if ((type->t & VT_BTYPE) == VT_ENUM && type->ref->c < 0)
+ tcc_error("cast to incomplete type");
+
+ /* same size and no sign conversion needed */
+ if (ds == ss && ds >= 4)
+ goto done;
+ if (dbt_bt == VT_PTR || sbt_bt == VT_PTR) {
+ tcc_warning("cast between pointer and integer of different size");
+ if (sbt_bt == VT_PTR) {
+ /* put integer type to allow logical operations below */
+ vtop->type.t = (PTR_SIZE == 8 ? VT_LLONG : VT_INT);
+ }
+ }
+
+ /* processor allows { int a = 0, b = *(char*)&a; }
+ That means that if we cast to less width, we can just
+ change the type and read it still later. */
+ #define ALLOW_SUBTYPE_ACCESS 1
+
+ if (ALLOW_SUBTYPE_ACCESS && (vtop->r & VT_LVAL)) {
+ /* value still in memory */
+ if (ds <= ss)
+ goto done;
+ /* ss <= 4 here */
+ if (ds <= 4 && !(dbt == (VT_SHORT | VT_UNSIGNED) && sbt ==
VT_BYTE)) {
+ gv(RC_INT);
+ goto done; /* no 64bit envolved */
+ }
+ }
+ gv(RC_INT);
+
+ trunc = 0;
#if PTR_SIZE == 4
- } else if ((dbt & VT_BTYPE) == VT_LLONG) {
- if ((sbt & VT_BTYPE) != VT_LLONG) {
- /* scalar to long long */
- /* machine independent conversion */
- gv(RC_INT);
- /* generate high word */
- if (sbt == (VT_INT | VT_UNSIGNED)) {
- vpushi(0);
- gv(RC_INT);
- } else {
- if (sbt == VT_PTR) {
- /* cast from pointer to int before we apply
- shift operation, which pointers don't support*/
- gen_cast(&int_type);
- }
- gv_dup();
- vpushi(31);
- gen_op(TOK_SAR);
- }
- /* patch second register */
- vtop[-1].r2 = vtop->r;
- vpop();
- }
-#else
- } else if ((dbt & VT_BTYPE) == VT_LLONG ||
- (dbt & VT_BTYPE) == VT_PTR ||
- (dbt & VT_BTYPE) == VT_FUNC) {
- if ((sbt & VT_BTYPE) != VT_LLONG &&
- (sbt & VT_BTYPE) != VT_PTR &&
- (sbt & VT_BTYPE) != VT_FUNC) {
- /* need to convert from 32bit to 64bit */
- gv(RC_INT);
- if (sbt != (VT_INT | VT_UNSIGNED)) {
-#if defined(TCC_TARGET_ARM64) || defined(TCC_TARGET_RISCV64)
- 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));
+ if (ds == 8) {
+ /* generate high word */
+ if (sbt & VT_UNSIGNED) {
+ vpushi(0);
+ gv(RC_INT);
+ } else {
+ gv_dup();
+ vpushi(31);
+ gen_op(TOK_SAR);
+ }
+ lbuild(dbt);
+ } else if (ss == 8) {
+ /* from long long: just take low order word */
+ lexpand();
+ vpop();
+ }
+ ss = 4;
+
+#elif PTR_SIZE == 8
+ if (ds == 8) {
+ /* need to convert from 32bit to 64bit */
+ if (sbt & VT_UNSIGNED) {
+#if defined(TCC_TARGET_RISCV64)
+ /* RISC-V keeps 32bit vals in registers sign-extended.
+ So here we need a zero-extension. */
+ trunc = 32;
#else
-#error
+ goto done;
#endif
- }
- }
+ } else {
+ gen_cvt_sxtw();
+ goto done;
+ }
+ ss = ds, ds = 4, dbt = sbt;
+ } else if (ss == 8) {
+ /* RISC-V keeps 32bit vals in registers sign-extended.
+ So here we need a sign-extension for signed types and
+ zero-extension. for unsigned types. */
+#if !defined(TCC_TARGET_RISCV64)
+ trunc = 32; /* zero upper 32 bits for non RISC-V targets */
#endif
- } else if (dbt == VT_BOOL) {
- /* scalar to bool */
- vpushi(0);
- gen_op(TOK_NE);
- } else if ((dbt & VT_BTYPE) == VT_BYTE ||
- (dbt & VT_BTYPE) == VT_SHORT) {
- if (sbt == VT_PTR) {
- vtop->type.t = VT_INT;
- tcc_warning("nonportable conversion from pointer to
char/short");
- }
- force_charshort_cast(dbt);
-#if PTR_SIZE == 4
- } else if ((dbt & VT_BTYPE) == VT_INT) {
- /* scalar to int */
- if ((sbt & VT_BTYPE) == VT_LLONG) {
- /* from long long: just take low order word */
- lexpand();
- vpop();
- }
- /* if lvalue and single word type, nothing to do because
- the lvalue already contains the real type size (see
- VT_LVAL_xxx constants) */
+ } else {
+ ss = 4;
+ }
#endif
- }
+
+ if (ds >= ss)
+ goto done;
+#if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 || defined
TCC_TARGET_ARM64
+ if (ss == 4) {
+ gen_cvt_csti(dbt);
+ goto done;
}
- } else if ((dbt & VT_BTYPE) == VT_PTR && !(vtop->r & VT_LVAL)) {
- /* if we are casting between pointer types,
- we must update the VT_LVAL_xxx size */
- vtop->r = (vtop->r & ~VT_LVAL_TYPE)
- | (lvalue_type(type->ref->type.t) & VT_LVAL_TYPE);
+#endif
+ bits = (ss - ds) * 8;
+ /* for unsigned, gen_op will convert SAR to SHR */
+ vtop->type.t = (ss == 8 ? VT_LLONG : VT_INT) | (dbt & VT_UNSIGNED);
+ vpushi(bits);
+ gen_op(TOK_SHL);
+ vpushi(bits - trunc);
+ gen_op(TOK_SAR);
+ vpushi(trunc);
+ gen_op(TOK_SHR);
}
+done:
vtop->type = *type;
vtop->type.t &= ~ ( VT_CONSTANT | VT_VOLATILE | VT_ARRAY );
}
diff --git a/x86_64-gen.c b/x86_64-gen.c
index 9245e2dc..d5a183fa 100644
--- a/x86_64-gen.c
+++ b/x86_64-gen.c
@@ -2260,6 +2260,29 @@ void gen_cvt_ftoi(int t)
vtop->r = r;
}
+// Generate sign extension from 32 to 64 bits:
+ST_FUNC void gen_cvt_sxtw(void)
+{
+ int r = gv(RC_INT);
+ /* x86_64 specific: movslq */
+ o(0x6348);
+ o(0xc0 + (REG_VALUE(r) << 3) + REG_VALUE(r));
+}
+
+/* char/short to int conversion */
+ST_FUNC void gen_cvt_csti(int t)
+{
+ int r, sz, xl, ll;
+ r = gv(RC_INT);
+ sz = !(t & VT_UNSIGNED);
+ xl = (t & VT_BTYPE) == VT_SHORT;
+ ll = (vtop->type.t & VT_BTYPE) == VT_LLONG;
+ orex(ll, r, 0, 0xc0b60f /* mov[sz] %a[xl], %eax */
+ | (sz << 3 | xl) << 8
+ | (REG_VALUE(r) << 3 | REG_VALUE(r)) << 16
+ );
+}
+
/* computed goto support */
void ggoto(void)
{
--
2.41.0
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [PATCH] tccgen: fix casts using code from upstream,
Ekaitz Zarraga <=