[PATCH] Vector to scalar casting and widening

Andrew Burgess
2012-11-13T14:10:50Z

I'd like to change the way gdb handles scalar to vector widening. I
believe that the changes I propose will bring gdb expression evaluation
into line with how gcc handles these things; this seems a good thing to me,
but I'd be interested to hear why anyone things we should stick with the
current scheme.

The problem I have is the current behaviour when casting from scalar to
vector. We first cast the scalar to the element type of the vector, then
replicate the (truncated) scalar to fill each element of the vector.

The gcc behaviour is to only allow casting from scalar to vector, and from
vector to vector when the lengths of the scalar and the vector, or the two
vectors, match.

On my local target I have instructions that process registers as though
they were vectors of different sizes, so an instruction might treat the
register as a single 64-bit value, two 32-bit values, four 16-bit values,
and so on. We have types for each different vector size, and it's not
uncommon to cast a register between types.

You'll notice the gcc behaviour matches this perfectly while the gdb
behaviour can truncate values when casting between types.

One place where gcc does replicate scalars to create vectors is in binary
operations on vectors, so you can have "vector + scalar", this will then
add 'scalar' to each element of 'vector'. This seems like a good thing,
and seems to be something a lot of vector supporting targets actually have
in hardware.

gcc restricts the scalar to vector replication within binary operations to
only expanding the type to avoid loosing significant bits. So a vector of
type 'int' could for example be added to a scalar 'short' or 'char', but
not to a 'long long'.

I've included a simple test file and gdb command script which shows the
current behaviour of gdb.

The patch does the following:

1. Casting scalar to vector or vector to vector is only allowed when the
lengths of the two types are equal.

2. Binary operations on a vector and a scalar will first create a
temporary vector by replicating scalar, then perform the operation on the
two vectors. We will error if casting the scalar to the element type of
the vector causes truncation of the value.

OK to commit?

Thanks,

Andrew

##### EXAMPLE FILES #####

/* File: vec.c

I'm using gcc 4.7.2 on GNU/Linux (RHEL 5.7) on x86-64.

Compile using: gcc -DBROKEN -g -o vec vec.c
to test constructs that will not compile.

Compile using: gcc -g -o vec vec.c
to test only constructs that do compile.
*/

#include <stdio.h>

#define VEC_TYPE(btype, count) \
typedef btype btype ## count \
__attribute__ ((vector_size (count * sizeof (btype)))); \
void print_ ## btype ## count ( const char *name, btype ## count var ) \
{ \
int i; \
printf ("%s = { %lld", name, var[0]); \
for (i = 1; i < count; ++i) \
{ \
printf (", %lld", var[i]); \
} \
printf (" }\n"); \
}

VEC_TYPE (char, 4)
VEC_TYPE (char, 8)

VEC_TYPE (int, 2)
VEC_TYPE (int, 4)

int ia = 1;
short sa = 1;
long long lla = 1;

void
breakpt ()
{
/* Breakpt */
}

int
main ()
{
char4 c4a = { 1, 2, 3, 4};
char4 c4b = (char4) 1;

#if BROKEN
char4 c4c = 1;
char4 c4d = ia;
char4 c4e = sa;
char4 c4f = lla;
#endif

char4 c4g = (char4) ia;

#if BROKEN
char4 c4h = (char4) sa;
char4 c4i = (char4) lla;
#endif

int4 i4a = { 1, 2, 3, 4};
int2 i2a = { 1, 2};
int2 i2b = (int2) lla;

#if BROKEN
char4 c4j = i4a;
char4 c4k = (char4) i4a;
char8 c8a = i2a;
#endif

char8 c8b = (char8) i2a;

print_char4 ("c4b", c4b);
print_int4 ("i4a", i4a);
print_char8 ("c8b", c8b);
print_int2 ("i2b", i2b);

return 0; /* Break Here */
}

/* END: vec.c */

# START: vec.gdb

set height 0
set trace-commands 1

break 81

run

print c4b
print (char4) 1

print i2b
print lla
print (int2) lla

# The following don't compile
# under gcc 4.7.2
print (char4) sa
print (char4) lla
print (char4) i4a

quit

# END: vec.gdb

##### PATCH #####

gdb/ChangeLog

2012-11-13 Andrew Burgess <aburgess@broadcom.com>

* valarith.c (vector_widen): New function for replicating a scalar
into a vector.
(value_binop): Use vector_widen to convert scalar to vector rather
than value_cast.
* valops.c (value_casst): Update logic for casting between vector
types, and for casting from scalar to vector.

diff --git a/gdb/valarith.c b/gdb/valarith.c
index c457f4a..81d6868 100644
--- a/gdb/valarith.c
+++ b/gdb/valarith.c
@@ -1347,6 +1347,47 @@ scalar_binop (struct value *arg1, struct value *arg2, enum exp_opcode op)
return val;
}

+/* Widen a scalar value SCALAR_VALUE to vector type VECTOR_TYPE by
+ replicating SCALAR_VALUE for each element of the vector. Only scalar
+ types that can be cast to the type of one element of the vector are
+ acceptable. The newly created vector value is returned upon success,
+ otherwise an error is thrown. */
+
+static struct value *
+vector_widen (struct value *scalar_value, struct type *vector_type)
+{
+ /* Widen the scalar to a vector. */
+ struct type *eltype, *scalar_type;
+ struct value *val, *elval;
+ LONGEST low_bound, high_bound;
+ int i;
+
+ gdb_assert (TYPE_CODE (check_typedef (vector_type)) == TYPE_CODE_ARRAY);
+ gdb_assert (TYPE_VECTOR (vector_type));
+
+ if (!get_array_bounds (vector_type, &low_bound, &high_bound))
+ error (_("Could not determine the vector bounds"));
+
+ eltype = check_typedef (TYPE_TARGET_TYPE (vector_type));
+ elval = value_cast (eltype, scalar_value);
+
+ scalar_type = check_typedef (value_type (scalar_value));
+
+ /* If we reduced the length of the scalar then check we didn't loose any
+ important bits. */
+ if (TYPE_LENGTH (eltype) < TYPE_LENGTH (scalar_type)
+ && !value_equal (elval, scalar_value))
+ error (_("conversion of scalar to vector involves truncation"));
+
+ val = allocate_value (vector_type);
+ for (i = 0; i < high_bound - low_bound + 1; i++)
+ /* Duplicate the contents of elval into the destination vector. */
+ memcpy (value_contents_writeable (val) + (i * TYPE_LENGTH (eltype)),
+ value_contents_all (elval), TYPE_LENGTH (eltype));
+
+ return val;
+}
+
/* Performs a binary operation on two vector operands by calling scalar_binop
for each pair of vector components. */

@@ -1426,7 +1467,9 @@ value_binop (struct value *arg1, struct value *arg2, enum exp_opcode op)
&& !is_integral_type (t))
error (_("Argument to operation not a number or boolean."));

- *v = value_cast (t1_is_vec ? type1 : type2, *v);
+ /* Replicate the scalar value to make a vector value. */
+ *v = vector_widen (*v, t1_is_vec ? type1 : type2);
+
val = vector_binop (arg1, arg2, op);
}

diff --git a/gdb/valops.c b/gdb/valops.c
index 502fb0d..c8d7c8b 100644
--- a/gdb/valops.c
+++ b/gdb/valops.c
@@ -546,29 +546,14 @@ value_cast (struct type *type, struct value *arg2)
minus one, instead of biasing the normal case. */
return value_from_longest (type, -1);
}
- else if (code1 == TYPE_CODE_ARRAY && TYPE_VECTOR (type) && scalar)
- {
- /* Widen the scalar to a vector. */
- struct type *eltype;
- struct value *val;
- LONGEST low_bound, high_bound;
- int i;
-
- if (!get_array_bounds (type, &low_bound, &high_bound))
- error (_("Could not determine the vector bounds"));
-
- eltype = check_typedef (TYPE_TARGET_TYPE (type));
- arg2 = value_cast (eltype, arg2);
- val = allocate_value (type);
-
- for (i = 0; i < high_bound - low_bound + 1; i++)
- {
- /* Duplicate the contents of arg2 into the destination vector. */
- memcpy (value_contents_writeable (val) + (i * TYPE_LENGTH (eltype)),
- value_contents_all (arg2), TYPE_LENGTH (eltype));
- }
- return val;
- }
+ else if (code1 == TYPE_CODE_ARRAY && TYPE_VECTOR (type)
+ && code2 == TYPE_CODE_ARRAY && TYPE_VECTOR (type2)
+ && TYPE_LENGTH (type) != TYPE_LENGTH (type2))
+ error (_("can't convert between vector values of different size"));
+ else if (code1 == TYPE_CODE_ARRAY && TYPE_VECTOR (type)
+ && scalar
+ && TYPE_LENGTH (type) != TYPE_LENGTH (type2))
+ error (_("can only cast scalar to vector of same size"));
else if (TYPE_LENGTH (type) == TYPE_LENGTH (type2))
{
if (code1 == TYPE_CODE_PTR && code2 == TYPE_CODE_PTR)

gdb/testsuite/ChangeLog

2012-11-13 Andrew Burgess <aburgess@broadcom.com>

* gdb.base/gnu_vector.c: New variable for use in tests.
* gdb.base/gnu_vector.exp: Update and extend tests to reflect
changes in scalar to vector casting and widening.
* gdb.python/py-type.c: New variables for use in tests.
* gdb.python/py-type.exp: Update vector related tests to reflect
changes in scalar to vector casting and widening.

diff --git a/gdb/testsuite/gdb.base/gnu_vector.c b/gdb/testsuite/gdb.base/gnu_vector.c
index a2a218f..5b7c98b 100644
--- a/gdb/testsuite/gdb.base/gnu_vector.c
+++ b/gdb/testsuite/gdb.base/gnu_vector.c
@@ -31,6 +31,7 @@ int ia = 2;
int ib = 1;
float fa = 2;
float fb = 1;
+long long lla = 0x0000000100000001ll;
char4 c4 = {1, 2, 3, 4};
int4 i4a = {2, 4, 8, 16};
int4 i4b = {1, 2, 8, 4};
diff --git a/gdb/testsuite/gdb.base/gnu_vector.exp b/gdb/testsuite/gdb.base/gnu_vector.exp
index baba119..eaba6f2 100644
--- a/gdb/testsuite/gdb.base/gnu_vector.exp
+++ b/gdb/testsuite/gdb.base/gnu_vector.exp
@@ -82,32 +82,52 @@ gdb_test "print f4a / f4b" "\\\$$decimal = \\{2, 2, 1, 4\\}"
gdb_test "print +f4a" "\\\$$decimal = \\{2, 4, 8, 16\\}"
gdb_test "print -f4a" "\\\$$decimal = \\{-2, -4, -8, -16\\}"

-# Test scalar to vector widening
-gdb_test "print (int2) 1" "\\\$$decimal = \\{1, 1\\}"
-gdb_test "print (longlong2) 2" "\\\$$decimal = \\{2, 2\\}"
-gdb_test "print (float2) 3" "\\\$$decimal = \\{3, 3\\}"
-gdb_test "print (double2) 4" "\\\$$decimal = \\{4, 4\\}"
-gdb_test "print (char4) 12" "\\\$$decimal = \\{12, 12, 12, 12\\}"
-gdb_test "print (uint4) ia" "\\\$$decimal = \\{2, 2, 2, 2\\}"
-gdb_test "print (int4) -3" "\\\$$decimal = \\{-3, -3, -3, -3\\}"
-gdb_test "print (float4) 4" "\\\$$decimal = \\{4, 4, 4, 4\\}"
-
+# When casting to vector the input type must have the same length as
+# the total length of the vector.
+gdb_test "print (char4) 0x01010101" "\\\$$decimal = \\{1, 1, 1, 1\\}"
+gdb_test "print (char4) ia" "\\\$$decimal = \\{2, 0, 0, 0\\}"
+gdb_test "print (int2) lla" "\\\$$decimal = \\{1, 1\\}"
+
+gdb_test "print (int2) 1" "can only cast scalar to vector of same size"
+gdb_test "print (longlong2) 2" "can only cast scalar to vector of same size"
+gdb_test "print (float2) 3" "can only cast scalar to vector of same size"
+gdb_test "print (double2) 4" "can only cast scalar to vector of same size"
+gdb_test "print (uint4) ia" "can only cast scalar to vector of same size"
+gdb_test "print (int4) -3" "can only cast scalar to vector of same size"
+gdb_test "print (float4) 4" "can only cast scalar to vector of same size"
+
+gdb_test "print i4b = ia" "can only cast scalar to vector of same size"
+gdb_test "print i4a = 3" "can only cast scalar to vector of same size"
+gdb_test "print f4a = fb" "can only cast scalar to vector of same size"
+gdb_test "print f4b = 2" "can only cast scalar to vector of same size"
+
+gdb_test "print c4 + lla" "conversion of scalar to vector involves truncation"
+gdb_test "print i4a + lla" "conversion of scalar to vector involves truncation"
+gdb_test "print lla + c4" "conversion of scalar to vector involves truncation"
+gdb_test "print lla + i4a" "conversion of scalar to vector involves truncation"
+
+gdb_test "print c4 + ib" "\\\$$decimal = \\{2, 3, 4, 5\\}"
gdb_test "print i4a + ib" "\\\$$decimal = \\{3, 5, 9, 17\\}"
+gdb_test "print i4a + 1" "\\\$$decimal = \\{3, 5, 9, 17\\}"
+gdb_test "print 1 + i4a" "\\\$$decimal = \\{3, 5, 9, 17\\}"
gdb_test "print fa - f4b" "\\\$$decimal = \\{1, 0, -6, -2\\}"
+gdb_test "print 2 - f4b" "\\\$$decimal = \\{1, 0, -6, -2\\}"
gdb_test "print f4a * fb" "\\\$$decimal = \\{2, 4, 8, 16\\}"
+gdb_test "print f4a * 1" "\\\$$decimal = \\{2, 4, 8, 16\\}"
gdb_test "print ia / i4b" "\\\$$decimal = \\{2, 1, 0, 0\\}"
+gdb_test "print 2 / i4b" "\\\$$decimal = \\{2, 1, 0, 0\\}"
gdb_test "print i4a % ib" "\\\$$decimal = \\{0, 0, 0, 0\\}"
-
+gdb_test "print i4a % 1" "\\\$$decimal = \\{0, 0, 0, 0\\}"
gdb_test "print ia & i4b" "\\\$$decimal = \\{0, 2, 0, 0\\}"
+gdb_test "print 2 & i4b" "\\\$$decimal = \\{0, 2, 0, 0\\}"
gdb_test "print i4a | ib" "\\\$$decimal = \\{3, 5, 9, 17\\}"
+gdb_test "print i4a | 1" "\\\$$decimal = \\{3, 5, 9, 17\\}"
gdb_test "print ia ^ i4b" "\\\$$decimal = \\{3, 0, 10, 6\\}"
+gdb_test "print 2 ^ i4b" "\\\$$decimal = \\{3, 0, 10, 6\\}"
gdb_test "print i4a << ib" "\\\$$decimal = \\{4, 8, 16, 32\\}"
+gdb_test "print i4a << 1" "\\\$$decimal = \\{4, 8, 16, 32\\}"
gdb_test "print i4a >> ib" "\\\$$decimal = \\{1, 2, 4, 8\\}"
-
-gdb_test "print i4b = ia" "\\\$$decimal = \\{2, 2, 2, 2\\}"
-gdb_test "print i4a = 3" "\\\$$decimal = \\{3, 3, 3, 3\\}"
-gdb_test "print f4a = fb" "\\\$$decimal = \\{1, 1, 1, 1\\}"
-gdb_test "print f4b = 2" "\\\$$decimal = \\{2, 2, 2, 2\\}"
+gdb_test "print i4a >> 1" "\\\$$decimal = \\{1, 2, 4, 8\\}"

gdb_test "print i4a = \{2, 4, 8, 16\}" "\\\$$decimal = \\{2, 4, 8, 16\\}"
gdb_test "print i4a <<= ib" "\\\$$decimal = \\{4, 8, 16, 32\\}"
diff --git a/gdb/testsuite/gdb.python/py-type.c b/gdb/testsuite/gdb.python/py-type.c
index bf39443..c57a2e8 100644
--- a/gdb/testsuite/gdb.python/py-type.c
+++ b/gdb/testsuite/gdb.python/py-type.c
@@ -15,6 +15,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

+#include <stdint.h>
+
struct s
{
int a;
@@ -50,6 +52,9 @@ enum E
{ v1, v2, v3
};

+uint64_t vec_data_1 = 0x0000000100000001ull;
+uint64_t vec_data_2 = 0x0000000100000002ull;
+
int
main ()
{
diff --git a/gdb/testsuite/gdb.python/py-type.exp b/gdb/testsuite/gdb.python/py-type.exp
index b997c51..5791bb6 100644
--- a/gdb/testsuite/gdb.python/py-type.exp
+++ b/gdb/testsuite/gdb.python/py-type.exp
@@ -125,15 +125,18 @@ proc test_fields {lang} {
# Test gdb.Type.vector.
# Note: vectors cast differently than arrays. Here ar[0] is replicated
# for the size of the vector.
- gdb_py_test_silent_cmd \
- "python vec1 = ar\[0\].cast(ar\[0\].type.vector(1))" "set vec1" 1
+ gdb_py_test_silent_cmd "print vec_data_1" "print value (vec_data_1)" 1
+ gdb_py_test_silent_cmd "python vec_data_1 = gdb.history (0)" "get value (vec_data_1) from history" 1
+
+ gdb_py_test_silent_cmd "print vec_data_2" "print value (vec_data_2)" 1
+ gdb_py_test_silent_cmd "python vec_data_2 = gdb.history (0)" "get value (vec_data_2) from history" 1
+
+ gdb_py_test_silent_cmd "python vec1 = vec_data_1.cast(ar\[0\].type.vector(1))" "set vec1" 1
gdb_test "python print vec1" ".1, 1." "cast to vector with one argument"
- gdb_py_test_silent_cmd \
- "python vec2 = ar\[0\].cast(ar\[0\].type.vector(0, 1))" "set vec2" 1
+ gdb_py_test_silent_cmd "python vec2 = vec_data_1.cast(ar\[0\].type.vector(0, 1))" "set vec2" 1
gdb_test "python print vec2" ".1, 1." "cast to vector with two arguments"
gdb_test "python print vec1 == vec2" "True"
- gdb_py_test_silent_cmd \
- "python vec3 = ar\[1\].cast(ar\[1\].type.vector(1))" "set vec3" 1
+ gdb_py_test_silent_cmd "python vec3 = vec_data_2.cast(ar\[0\].type.vector(1))" "set vec3" 1
gdb_test "python print vec1 == vec3" "False"
}