qemu/tests/tcg/ppc64le/bcdsub.c

131 lines
2.9 KiB
C
Raw Normal View History

target/ppc: Fix bcdsub. emulation when result overflows The commit d03b174a83 (target/ppc: simplify bcdadd/sub functions) meant to simplify some of the code but it inadvertently altered the way the CR6 field is set after the operation has overflowed. The CR6 bits are set based on the *unbounded* result of the operation, so we need to look at the result before returning from bcd_add_mag, otherwise we will look at 0 when it overflows. Consider the following subtraction: v0 = 0x9999999999999999999999999999999c (maximum positive BCD value) v1 = 0x0000000000000000000000000000001d (negative one BCD value) bcdsub. v0,v0,v1,0 The Power ISA 2.07B says: If the unbounded result is greater than zero, do the following. If PS=0, the sign code of the result is set to 0b1100. If PS=1, the sign code of the result is set to 0b1111. If the operation overflows, CR field 6 is set to 0b0101. Otherwise, CR field 6 is set to 0b0100. POWER9 hardware: vr0 = 0x0000000000000000000000000000000c (positive zero BCD value) cr6 = 0b0101 (0x5) (positive, overflow) QEMU: vr0 = 0x0000000000000000000000000000000c (positive zero BCD value) cr6 = 0b0011 (0x3) (zero, overflow) <--- wrong This patch reverts the part of d03b174a83 that introduced the problem and adds a test-case to avoid further regressions: before: $ make run-tcg-tests-ppc64le-linux-user (...) TEST bcdsub on ppc64le bcdsub: qemu/tests/tcg/ppc64le/bcdsub.c:58: test_bcdsub_gt: Assertion `(cr >> 4) == ((1 << 2) | (1 << 0))' failed. Fixes: d03b174a83 (target/ppc: simplify bcdadd/sub functions) Reported-by: Paul Clarke <pc@us.ibm.com> Signed-off-by: Fabiano Rosas <farosas@linux.ibm.com> Message-Id: <20210222194035.2723056-1-farosas@linux.ibm.com> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
2021-02-22 22:40:35 +03:00
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#define CRF_LT (1 << 3)
#define CRF_GT (1 << 2)
#define CRF_EQ (1 << 1)
#define CRF_SO (1 << 0)
#define UNDEF 0
#define BCDSUB(vra, vrb, ps) \
asm ("bcdsub. %1,%2,%3,%4;" \
"mfocrf %0,0b10;" \
: "=r" (cr), "=v" (vrt) \
: "v" (vra), "v" (vrb), "i" (ps) \
: );
#define TEST(vra, vrb, ps, exp_res, exp_cr6) \
do { \
__int128 vrt = 0; \
int cr = 0; \
BCDSUB(vra, vrb, ps); \
if (exp_res) \
assert(vrt == exp_res); \
assert((cr >> 4) == exp_cr6); \
} while (0)
/*
* Unbounded result is equal to zero:
* sign = (PS) ? 0b1111 : 0b1100
* CR6 = 0b0010
*/
void test_bcdsub_eq(void)
{
__int128 a, b;
/* maximum positive BCD value */
a = b = (((__int128) 0x9999999999999999) << 64 | 0x999999999999999c);
TEST(a, b, 0, 0xc, CRF_EQ);
TEST(a, b, 1, 0xf, CRF_EQ);
}
/*
* Unbounded result is greater than zero:
* sign = (PS) ? 0b1111 : 0b1100
* CR6 = (overflow) ? 0b0101 : 0b0100
*/
void test_bcdsub_gt(void)
{
__int128 a, b, c;
/* maximum positive BCD value */
a = (((__int128) 0x9999999999999999) << 64 | 0x999999999999999c);
/* negative one BCD value */
b = (__int128) 0x1d;
TEST(a, b, 0, 0xc, (CRF_GT | CRF_SO));
TEST(a, b, 1, 0xf, (CRF_GT | CRF_SO));
c = (((__int128) 0x9999999999999999) << 64 | 0x999999999999998c);
TEST(c, b, 0, a, CRF_GT);
TEST(c, b, 1, (a | 0x3), CRF_GT);
}
/*
* Unbounded result is less than zero:
* sign = 0b1101
* CR6 = (overflow) ? 0b1001 : 0b1000
*/
void test_bcdsub_lt(void)
{
__int128 a, b;
/* positive zero BCD value */
a = (__int128) 0xc;
/* positive one BCD value */
b = (__int128) 0x1c;
TEST(a, b, 0, 0x1d, CRF_LT);
TEST(a, b, 1, 0x1d, CRF_LT);
/* maximum negative BCD value */
a = (((__int128) 0x9999999999999999) << 64 | 0x999999999999999d);
/* positive one BCD value */
b = (__int128) 0x1c;
TEST(a, b, 0, 0xd, (CRF_LT | CRF_SO));
TEST(a, b, 1, 0xd, (CRF_LT | CRF_SO));
}
void test_bcdsub_invalid(void)
{
__int128 a, b;
/* positive one BCD value */
a = (__int128) 0x1c;
b = 0xf00;
TEST(a, b, 0, UNDEF, CRF_SO);
TEST(a, b, 1, UNDEF, CRF_SO);
TEST(b, a, 0, UNDEF, CRF_SO);
TEST(b, a, 1, UNDEF, CRF_SO);
a = 0xbad;
TEST(a, b, 0, UNDEF, CRF_SO);
TEST(a, b, 1, UNDEF, CRF_SO);
}
int main(void)
{
struct sigaction action;
action.sa_handler = _exit;
sigaction(SIGABRT, &action, NULL);
test_bcdsub_eq();
test_bcdsub_gt();
test_bcdsub_lt();
test_bcdsub_invalid();
return 0;
}