Carl Love 518a2d59ac Fix dfp tests.
Due to changes between the compiler and linker, we need to add .machine
arguments to configure file to properly detect the availability of the
dfp instructions.

Add print statement if HAS_DFP is not enabled to make it
easier to determine when HAS_DFP is not enabled.
2021-09-30 17:30:13 -05:00

644 lines
21 KiB
C

/* Copyright (C) 2012 IBM
Author: Maynard Johnson <maynardj@us.ibm.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
The GNU General Public License is contained in the file COPYING.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#if defined(HAS_DFP)
typedef union stuff {
_Decimal64 dec_val;
_Decimal128 dec_val128;
unsigned long long u64_val;
struct {
#if defined(VGP_ppc64le_linux)
unsigned long long vall;
unsigned long long valu;
#else
unsigned long long valu;
unsigned long long vall;
#endif
} u128;
} dfp_val_t;
typedef unsigned char Bool;
#define True 1
#define False 0
#define ALLCR "cr0","cr1","cr2","cr3","cr4","cr5","cr6","cr7"
#define SET_CR(_arg) \
__asm__ __volatile__ ("mtcr %0" : : "b"(_arg) : ALLCR );
#define SET_XER(_arg) \
__asm__ __volatile__ ("mtxer %0" : : "b"(_arg) : "xer" );
#define GET_CR(_lval) \
__asm__ __volatile__ ("mfcr %0" : "=b"(_lval) )
#define GET_XER(_lval) \
__asm__ __volatile__ ("mfxer %0" : "=b"(_lval) )
#define GET_CR_XER(_lval_cr,_lval_xer) \
do { GET_CR(_lval_cr); GET_XER(_lval_xer); } while (0)
#define SET_CR_ZERO \
SET_CR(0)
#define SET_XER_ZERO \
SET_XER(0)
#define SET_CR_XER_ZERO \
do { SET_CR_ZERO; SET_XER_ZERO; } while (0)
#define SET_FPSCR_ZERO \
do { double _d = 0.0; \
__asm__ __volatile__ ("mtfsf 0xFF, %0" : : "f"(_d) ); \
} while (0)
#define GET_FPSCR(_arg) \
__asm__ __volatile__ ("mffs %0" : "=f"(_arg) )
#define SET_FPSCR_DRN \
__asm__ __volatile__ ("mtfsf 1, %0, 0, 1" : : "f"(f14) )
// The assembly-level instructions being tested
/* In _test_dtstdc[q], DCM can be one of 6 possible data classes, numbered 0-5.
* In reality, DCM is a 6-bit mask field. We just test the individual values
* and assume that masking multiple values would work OK.
* BF is the condition register bit field which can range from 0-7. But for
* testing purposes, we only use BF values of '0' and '5'.
*/
static void _test_dtstdc(int BF, int DCM, dfp_val_t *val1, dfp_val_t *x1 __attribute__((unused)))
{
_Decimal64 f14 = val1->dec_val;
if (DCM < 0 || DCM > 5 || !(BF == 0 || BF == 5)) {
fprintf(stderr, "Invalid inputs to asm test: a=%d, b=%d\n", BF, DCM);
return;
}
switch (DCM) {
case 0:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 1" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 1" : : "f" (f14));
break;
case 1:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 2" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 2" : : "f" (f14));
break;
case 2:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 4" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 4" : : "f" (f14));
break;
case 3:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 8" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 8" : : "f" (f14));
break;
case 4:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 16" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 16" : : "f" (f14));
break;
case 5:
if (BF)
__asm__ __volatile__ ("dtstdc 5, %0, 32" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdc 0, %0, 32" : : "f" (f14));
break;
default:
break;
}
}
static void _test_dtstdcq(int BF, int DCM, dfp_val_t *val1, dfp_val_t *x1 __attribute__((unused)))
{
_Decimal128 f14 = val1->dec_val128;
if (DCM < 0 || DCM > 5 || !(BF == 0 || BF == 5)) {
fprintf(stderr, "Invalid inputs to asm test: a=%d, b=%d\n", BF, DCM);
return;
}
switch (DCM) {
case 0:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 1" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 1" : : "f" (f14));
break;
case 1:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 2" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 2" : : "f" (f14));
break;
case 2:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 4" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 4" : : "f" (f14));
break;
case 3:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 8" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 8" : : "f" (f14));
break;
case 4:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 16" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 16" : : "f" (f14));
break;
case 5:
if (BF)
__asm__ __volatile__ ("dtstdcq 5, %0, 32" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdcq 0, %0, 32" : : "f" (f14));
break;
default:
break;
}
}
/* In _test_dtstdg[q], DGM can be one of 6 possible data groups, numbered 0-5.
* In reality, DGM is a 6-bit mask field. We just test the individual values
* and assume that masking multiple values would work OK.
* BF is the condition register bit field which can range from 0-7. But for
* testing purposes, we only use BF values of '0' and '5'.
*/
static void _test_dtstdg(int BF, int DGM, dfp_val_t *val1, dfp_val_t *x1 __attribute__((unused)))
{
_Decimal64 f14 = val1->dec_val;
if (DGM < 0 || DGM > 5 || !(BF == 0 || BF == 5)) {
fprintf(stderr, "Invalid inputs to asm test: a=%d, b=%d\n", BF, DGM);
return;
}
switch (DGM) {
case 0:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 1" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 1" : : "f" (f14));
break;
case 1:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 2" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 2" : : "f" (f14));
break;
case 2:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 4" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 4" : : "f" (f14));
break;
case 3:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 8" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 8" : : "f" (f14));
break;
case 4:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 16" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 16" : : "f" (f14));
break;
case 5:
if (BF)
__asm__ __volatile__ ("dtstdg 5, %0, 32" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdg 0, %0, 32" : : "f" (f14));
break;
default:
break;
}
}
static void _test_dtstdgq(int BF, int DGM, dfp_val_t *val1, dfp_val_t *x1 __attribute__((unused)))
{
_Decimal128 f14 = val1->dec_val128;
if (DGM < 0 || DGM > 5 || !(BF == 0 || BF == 5)) {
fprintf(stderr, "Invalid inputs to asm test: a=%d, b=%d\n", BF, DGM);
return;
}
switch (DGM) {
case 0:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 1" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 1" : : "f" (f14));
break;
case 1:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 2" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 2" : : "f" (f14));
break;
case 2:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 4" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 4" : : "f" (f14));
break;
case 3:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 8" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 8" : : "f" (f14));
break;
case 4:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 16" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 16" : : "f" (f14));
break;
case 5:
if (BF)
__asm__ __volatile__ ("dtstdgq 5, %0, 32" : : "f" (f14));
else
__asm__ __volatile__ ("dtstdgq 0, %0, 32" : : "f" (f14));
break;
default:
break;
}
}
/* In _test_dtstex[q], BF is the condition register bit field indicating the
* CR field in which the result of the test should be placed. BF can range
* from 0-7, but for testing purposes, we only use BF values of '4' and '7'.
*/
static void
_test_dtstex(int BF, int x __attribute__((unused)), dfp_val_t *val1, dfp_val_t *val2)
{
_Decimal64 f14 = val1->dec_val;
_Decimal64 f16 = val2->dec_val;
if (!(BF == 4 || BF == 7)) {
fprintf(stderr, "Invalid input to asm test: a=%d\n", BF);
return;
}
switch (BF) {
case 4:
__asm__ __volatile__ ("dtstex 4, %0, %1" : : "f" (f14),"f" (f16));
break;
case 7:
__asm__ __volatile__ ("dtstex 7, %0, %1" : : "f" (f14),"f" (f16));
break;
default:
break;
}
}
static void _test_dtstexq(int BF, int x __attribute__((unused)), dfp_val_t *val1, dfp_val_t *val2)
{
_Decimal128 f14 = val1->dec_val128;
_Decimal128 f16 = val2->dec_val128;
if (!(BF == 4 || BF == 7)) {
fprintf(stderr, "Invalid input to asm test: a=%d\n", BF);
return;
}
switch (BF) {
case 4:
__asm__ __volatile__ ("dtstexq 4, %0, %1" : : "f" (f14),"f" (f16));
break;
case 7:
__asm__ __volatile__ ("dtstexq 7, %0, %1" : : "f" (f14),"f" (f16));
break;
default:
break;
}
}
typedef void (*test_funcp_t)(int a, int b, dfp_val_t *val1, dfp_val_t *val2);
typedef void (*test_driver_func_t)(void);
typedef struct test_table
{
test_driver_func_t test_category;
char * name;
} test_table_t;
/*
* 345.0DD (0x2207c00000000000 0xe50)
* 1.2300e+5DD (0x2207c00000000000 0x14c000)
* -16.0DD (0xa207c00000000000 0xe0)
* 0.00189DD (0x2206c00000000000 0xcf)
* -4.1235DD (0xa205c00000000000 0x10a395bcf)
* 9.8399e+20DD (0x2209400000000000 0x253f1f534acdd4)
* 0DD (0x2208000000000000 0x0)
* 0DD (0x2208000000000000 0x0)
* infDD (0x7800000000000000 0x0)
* nanDD (0x7c00000000000000 0x0
*/
static unsigned long long dfp128_vals[] = {
// Some finite numbers
0x2207c00000000000ULL, 0x0000000000000e50ULL,
0x2207c00000000000ULL, 0x000000000014c000ULL,
0xa207c00000000000ULL, 0x00000000000000e0ULL,
0x2206c00000000000ULL, 0x00000000000000cfULL,
0xa205c00000000000ULL, 0x000000010a395bcfULL,
0x6209400000fd0000ULL, 0x00253f1f534acdd4ULL, // huge number
0x000400000089b000ULL, 0x0a6000d000000049ULL, // very small number
// flavors of zero
0x2208000000000000ULL, 0x0000000000000000ULL,
0xa208000000000000ULL, 0x0000000000000000ULL, // negative
0xa248000000000000ULL, 0x0000000000000000ULL,
// flavors of NAN
0x7c00000000000000ULL, 0x0000000000000000ULL, // quiet
0xfc00000000000000ULL, 0xc00100035b007700ULL,
0x7e00000000000000ULL, 0xfe000000d0e0a0d0ULL, // signaling
// flavors of Infinity
0x7800000000000000ULL, 0x0000000000000000ULL,
0xf800000000000000ULL, 0x0000000000000000ULL, // negative
0xf900000000000000ULL, 0x0000000000000000ULL
};
static unsigned long long dfp64_vals[] = {
// various finite numbers
0x2234000000000e50ULL,
0x223400000014c000ULL,
0xa2340000000000e0ULL,// negative
0x22240000000000cfULL,
0xa21400010a395bcfULL,// negative
0x6e4d3f1f534acdd4ULL,// huge number
0x000400000089b000ULL,// very small number
// flavors of zero
0x2238000000000000ULL,
0xa238000000000000ULL,
0x4248000000000000ULL,
// flavors of NAN
0x7e34000000000111ULL,
0xfe000000d0e0a0d0ULL,//signaling
0xfc00000000000000ULL,//quiet
// flavors of Infinity
0x7800000000000000ULL,
0xf800000000000000ULL,//negative
0x7a34000000000000ULL,
};
// Both Long and Quad arrays of DFP values should have the same length, so it
// doesn't matter which array I use for calculating the following #define.
#define NUM_DFP_VALS (sizeof(dfp64_vals)/8)
typedef struct dfp_test_args {
int fra_idx;
int frb_idx;
} dfp_test_args_t;
// Index pairs from dfp64_vals array to be used with dfp_two_arg_tests
static dfp_test_args_t dfp_2args_x1[] = {
{0, 1},
{2, 1},
{4, 3},
{6, 0},
{2, 4},
{5, 1},
{5, 2},
{7, 1},
{7, 2},
{8, 0},
{8, 1},
{8, 2},
{7, 8},
{12, 14},
{12, 1},
{12, 13},
{12, 12},
{12, 11},
{11, 14},
{11, 0},
{11, 13},
{11, 11},
{14, 14},
{14, 3},
{14, 15},
};
typedef enum {
LONG_TEST,
QUAD_TEST
} precision_type_t;
typedef struct dfp_test
{
test_funcp_t test_func;
const char * name;
dfp_test_args_t * targs;
int num_tests;
precision_type_t precision;
const char * op;
} dfp_test_t;
typedef struct dfp_one_arg_test
{
test_funcp_t test_func;
const char * name;
precision_type_t precision;
const char * op;
} dfp_one_arg_test_t;
static dfp_one_arg_test_t
dfp_ClassAndGroupTest_tests[] = {
{ &_test_dtstdc, "dtstdc", LONG_TEST, "[tCls]"},
{ &_test_dtstdcq, "dtstdcq", QUAD_TEST, "[tCls]"},
{ &_test_dtstdg, "dtstdg", LONG_TEST, "[tGrp]"},
{ &_test_dtstdgq, "dtstdgq", QUAD_TEST, "[tGrp]"},
{ NULL, NULL, 0, NULL}
};
static void test_dfp_ClassAndGroupTest_ops(void)
{
test_funcp_t func;
dfp_val_t test_val, dummy;
int k = 0;
while ((func = dfp_ClassAndGroupTest_tests[k].test_func)) {
int i;
dfp_one_arg_test_t test_def = dfp_ClassAndGroupTest_tests[k];
for (i = 0; i < NUM_DFP_VALS; i++) {
int data_class_OR_group, BF = 0;
Bool repeat = True;
if (test_def.precision == LONG_TEST) {
test_val.u64_val = dfp64_vals[i];
} else {
test_val.u128.valu = dfp128_vals[i * 2];
test_val.u128.vall = dfp128_vals[(i * 2) + 1];
}
again:
for (data_class_OR_group = 0; data_class_OR_group < 6; data_class_OR_group++) {
unsigned int condreg;
unsigned int flags;
SET_FPSCR_ZERO;
SET_CR_XER_ZERO;
/* There is an ABI change in how 128 bit arguments are aligned
* with GCC 5.0. The compiler generates a "note" about this
* starting with GCC 4.8. To avoid generating the "note", pass
* the address of the 128-bit arguments rather then the value.
*/
(*func)(BF, data_class_OR_group, &test_val, &dummy);
GET_CR(flags);
condreg = ((flags >> (4 * (7-BF)))) & 0xf;
printf("%s (DC/DG=%d) %s", test_def.name, data_class_OR_group,
test_def.op);
if (test_def.precision == QUAD_TEST) {
printf("%016llx %016llx", test_val.u128.valu, test_val.u128.vall);
} else {
printf("%016llx", test_val.u64_val);
}
//%016llx
printf(" => %x (BF=%d)\n", condreg, BF);
}
if (repeat) {
repeat = False;
BF = 5;
goto again;
}
}
k++;
printf( "\n" );
}
}
static dfp_test_t
dfp_ExpTest_tests[] = {
{ &_test_dtstex, "dtstex", dfp_2args_x1, 25, LONG_TEST, "[tExp]"},
{ &_test_dtstexq, "dtstexq", dfp_2args_x1, 25, QUAD_TEST, "[tExp]"},
{ NULL, NULL, NULL, 0, 0, NULL}
};
static void test_dfp_ExpTest_ops(void)
{
dfp_val_t test_val1, test_val2;
test_funcp_t func;
int k = 0;
while ((func = dfp_ExpTest_tests[k].test_func)) {
/* BF is a 3-bit instruction field that indicates the CR field in which the
* result of the test should be placed. We won't iterate through all
* 8 possible BF values since storing compare results to a given field is
* a well-tested mechanism in VEX. But we will test two BF values, just as
* a sniff-test.
*/
int i, repeat = 1, BF = 4;
dfp_test_t test_def = dfp_ExpTest_tests[k];
again:
for (i = 0; i < test_def.num_tests; i++) {
unsigned int condreg;
unsigned int flags;
if (test_def.precision == LONG_TEST) {
test_val1.u64_val = dfp64_vals[test_def.targs[i].fra_idx];
test_val2.u64_val = dfp64_vals[test_def.targs[i].frb_idx];
} else {
test_val1.u128.valu = dfp128_vals[test_def.targs[i].fra_idx * 2];
test_val1.u128.vall = dfp128_vals[(test_def.targs[i].fra_idx * 2) + 1];
test_val2.u128.valu = dfp128_vals[test_def.targs[i].frb_idx * 2];
test_val2.u128.vall = dfp128_vals[(test_def.targs[i].frb_idx * 2) + 1];
}
SET_FPSCR_ZERO;
SET_CR_XER_ZERO;
/* There is an ABI change in how 128 bit arguments are aligned
* with GCC 5.0. The compiler generates a "note" about this
* starting with GCC 4.8. To avoid generating the "note", pass
* the address of the 128-bit arguments rather then the value.
*/
(*func)(BF, 0, &test_val1, &test_val2);
GET_CR(flags);
condreg = ((flags >> (4 * (7-BF)))) & 0xf;
printf("%s ", test_def.name);
if (test_def.precision == LONG_TEST) {
printf("%016llx %s %016llx ",
test_val1.u64_val, test_def.op, test_val2.u64_val);
} else {
printf("%016llx %016llx %s %016llx %016llx ",
test_val1.u128.valu, test_val1.u128.vall, test_def.op, test_val2.u128.valu, test_val2.u128.vall);
}
printf(" => %x (BF=%d)\n", condreg, BF);
}
if (repeat) {
repeat = 0;
BF = 7;
goto again;
}
k++;
printf( "\n" );
}
}
static test_table_t
all_tests[] =
{
{ &test_dfp_ExpTest_ops,
"Test DFP exponent test instructions"},
{ &test_dfp_ClassAndGroupTest_ops,
"Test DFP class and group test instructions"},
{ NULL, NULL }
};
#endif // HAS_DFP
int main() {
#if defined(HAS_DFP)
test_table_t aTest;
test_driver_func_t func;
int i = 0;
while ((func = all_tests[i].test_category)) {
aTest = all_tests[i];
printf( "%s\n", aTest.name );
(*func)();
i++;
}
#else
printf("HAS_DFP not detected.\n");
#endif // HAS_DFP
return 0;
}