Conversions and Type Casting

Implicit Conversions

Implicit conversions are supported between scalar built-in types that are defined in Built-in Scalar Data Types. When performing an implicit conversion, it is not only a re-interpretation of the expression’s value but also a conversion of that value to an equivalent value in the new type. For example, the integer value 5 is converted to the floating-point value 5.0.

  • Implicit conversions from a scalar type to a vector type are allowed. In this case, the scalar may be subject to the usual arithmetic conversion to the element type that the vector uses. The scalar type is then widened to the vector.

  • Implicit conversions between built-in vector data types are not allowed.

  • Implicit conversions for pointer types follow the rules described in the C99 specification.

See the examples below:

float f = 1.0f;

int i;

i = f; // allowed: i = 1

float fl = 1.234567f;

_Bfloat16 bf_16 = fl; // allowed

_Bool isOne = i; // allowed

int64 int_vec = 2; // allowed

uint64 uint_vec = int_vec; // not allowed

float64 fl_vec = 1.0f; // allowed

int_vec = fl_vec + 3; // not allowed

Explicit Conversions (Casts)

Standard typecasts for built-in scalar data types, defined in Built-in Scalar Data Type, perform appropriate conversion. In the example below, fl stores 1.234567f and bf_16 stores 1.234f, which is the floating-point value 1.234567f in fl converted (reduced) to a _Bfloat16 value:

float f = 1.0f; // allowed

int i = (int)f; // allowed

float fl = 1.234567f; // allowed

_Bfloat16 bf_16 = (_Bfloat16) fl; // allowed

Scalar to vector conversions may be performed by casting the scalar to the desired vector data type. Type casting also performs appropriate arithmetic conversion. The round to zero rounding mode is used to convert to built-in integer vector types. The default rounding mode is used to convert to floating-point vector types. When casting a _Bool to a vector integer data type, the vector components are set to 1 if the _Bool value is true, and set to 0 otherwise.

Explicit casts between vector types are legal and can be performed in the usual way. See below more examples of explicit and implicit casts:

float f = 1.0f;

int i;

i = (int)f; // allowed

float64 fl_vec = 1.234567f; // allowed

int64 int_vec;

int_vec = (int_vec) fl_vec; // allowed

bfloat128 bf16_vec = (bfloat128) fl_vec; // allowed
_Bool isOne = 1; // allowed

int64 int_vec = isOne; // allowed

uint64 uint_vec = (uint64)int_vec; // allowed
float fl = 1.0f;

int64 int_vec;

int_vec = 2; //allowed

float64 fl_vec = 1.0f; // allowed

int_vec = fl + 3; // allowed

int_vec = (int64) fl_vec + 3; // allowed

Explicit Conversions

Explicit conversions can be performed by the set of corresponding intrinscs. These provide a full set of type conversions between supported types (see Built-in Scalar, Built-in Vector and Other Built-in Data Types).

In general, a convert intrinsic has the following name:

s_convert_<sourceType>_to_<destType> // in case of scalar
v_convert_<sourceType>_to_<destType>_[<b/vb>] // in case of vector

Examples:

s_convert_i16_to_f32

v_convert_int16_to_i8_vb_b

The first s states the scalar conversion and v – for vector.

For the full set of available conversion intrinsics, refer to TPC Intrinsics Guide.

The behavior of the conversion may be modified by a set of switches that specify various conversion options, including rounding modes (see examples in Explicit Conversion Examples).

Data Types

Conversions are available for the following scalar types: char, unsigned char, short, unsigned short, int, unsigned int, _Bfloat16, float, and built-in vector types derived from that. The operand and result type must have the same number of elements. The operand and result type may be the same type, in which case the conversion has no effect on the type or value of an expression.

Conversions between integer types follow the conversion rules specified in sections 6.3.1.1 and 6.3.1.3 of the C99 specification, except for out-of-range behavior and saturated conversions, which are described in Out-of-Range Behavior and Saturated Conversions below.

Rounding Modes

Conversions to and from floating-point types conform to IEEE-754 rounding rules. Conversions may have an optional rounding mode modifier, described in the table below:

Note

The default modifier value is zero which corresponds to the default round mode RHNE.

Modifier

Rounding Mode Description

SW_RHNE

Round to nearest even

SW_RZ

Round toward zero

SW_RU

Round toward positive infinity

SW_RD

Round toward negative infinity

SW_SR

Stochastic Rounding

SW_RHAZ

Round away from Zero (used for convert instruction)

Out-of-Range Behavior and Saturated Conversions

The conversion operand is said to be out-of-range when it is either greater than the greatest representable destination value or less than the least representable destination value. The conversion rules specified by the C99 specification in section 6.3. determine the result of out-of-range conversion when converting from a floating-point type to integer type other than _Bool. The fractional part is discarded (i.e., the value is truncated toward zero). The behavior is undefined when the value of the integer type cannot represent the integer part.

Conversions to floating-point type conform to IEEE-754 rounding rules. When a value of integer type is converted to a real floating type, it is unchanged if the value being converted can be represented exactly in the new type. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen according to the rounding mode in use. If the value being converted is outside the range of values that can be represented, the behavior is undefined.

The intrinsics implementing some operations on integer types (add, mac, sub) may use the optional saturated mode by setting the saturation modifier. When in saturated mode, values that are outside the representable range will clamp to the nearest representable value in the destination format. (NaN is converted to 0). The saturation modifiers may not be used for operations on floating-point formats. Listed below the available saturation modes:

Modifier

Saturation Mode Description

SW_SAT

Use saturation in conversion

int64 arg1, arg2, sum;

uint64 sum = v_i32_add_b(arg1, arg2, SW_SAT);

// sum[i] = MaxInt for all i: arg1[i] + arg2[i] > MaxInt


* by default no saturation is apply on instructions.

Explicit Conversion Examples

The below are examples of different use cases:

   float64 f = -3.5;

   ...

   int64 i = v_convert_f32_to_i32_vb(f ,SW_RHNE ,i);

// all elements of i = -4
char256 ch256;

...

uint64 u64 = v_convert_i8_to_u32_vb(ch256);

Reinterpreting Data as Another Type

It is frequently necessary to reinterpret bits in a data type as another data type in TPC C. This is typically required when direct access to the bits in a floating-point type is needed, such as to mask off the sign bit, or make use of the result of a vector relational operator on floating-point data (see Explicit Conversions.).

Reinterpreting Types Using Unions

The TPC-C language extends the union, to allow the program to access a member of a union object using a member of a different type. The relevant bytes of the representation of the object are treated as an object of the type used for the access. As stated in Structures and Unions, the data types of union object members cannot be from different memory classes. See the examples below:

union Vec_values { // allowed

int64 int_vec;

float64 float_vec;

union Vec_Sc_values { // not allowed

float element_0;

float64 float_vec;

union Index_array { // not allowed

int5 indexes;

int64 int_vec;

index Index_array { // allowed

int5 indexes;

int depth

Reinterpreting Types Using as_type() Operator

Scalar and vector data types described in Built-in Scalar Data Types and Built-in Aggregate Data Types (except _Bool) may be also reinterpreted as another data type of the same size using the as_type() construct both for scalar and vector data types. The bits in the operand are returned directly without modification as the new type. Byte order is little-endian. The usual type promotion for function arguments is not performed. It is an error to use as_type() operator to reinterpret data to a type with a different number of bytes. See the examples below:

Pointer Casting

Pointers to standard C99 types and new types may be cast back and forth to each other. Casting a pointer to a new type represents an unchecked assertion that the address is correctly aligned. Cast between pointers in different address spaces is not allowed.

The developer must also know the endianness of the TPC device and the endianness of the data, to determine how the scalar and vector data elements are stored in memory.

Usual Arithmetic Conversions

The general rules of arithmetic type conversions to use for scalar expressions are described in Section 6.3.1.8 of the C99 Language Specification. The following rules apply for arithmetic expressions with vector data types.

All vector types are considered with higher conversion ranks than scalars. If there is only a single vector type, and all other operands are scalar types. The scalar types are converted to the type of the vector element, then widened into a new vector containing the same number of elements as the vector by duplicating the scalar value across the width of the new vector. An error occurs when the operands are of more than one vector type. Implicit conversions between vector types are not permitted, per Implicit Conversions.

An error occurs if any scalar operand has greater rank than the type of the vector element. For this purpose, the following rank order is defined:

  1. The rank of a floating-point type is greater than the rank of another floating-point type, if the first floating-point type can exactly represent all numeric values in the second floating-point type. (For this purpose, the encoding of the floating-point value is used, rather than the subset of the encoding usable by the device.)

  2. The rank of any floating-point type is greater than the rank of any integer type.

  3. The rank of an integer type is greater than the rank of an integer type with less precision.

  4. The rank of an unsigned integer type is greater than the rank of a signed integer type with the same precision. (This is different from the standard integer conversion rank described in C99 TC2, section 6.3.1.1)

  5. The rank of the _Bool type is less than the rank of any other type.

  6. The rank of an enumerated type is equal to the rank of the compatible integer type.

  7. For all types, T1, T2 and T3, if T1 has greater rank than T2, and T2 has greater rank than T3, then T1 has greater rank than T3.

Otherwise, if all operands are scalar, the usual arithmetic conversions apply, per section 6.3.1.8 of the C99 standard.