Types
The supported data types and their aliases are listed below:
Scalar:
int8 (i8)
uint8 (u8)
int16 (i16)
uint16 (u16)
int32 (i32)
uint32 (u32)
float16 (fp16)
float32 (fp32)
bool
Vector:
int8x32 (i8x32)
uint8x32 (u8x32)
int16x16 (i16x16)
uint16x16 (u16x16)
int32x8 (i32x8)
uint32x8 (u32x8)
float16x16 (fp16x16)
float32x8 (fp32x8)
boolx8
boolx16
boolx32
When vector data is visualized in one line, the sequence from the leftmost element to the rightmost element means the vector data from the lowest one to the highest one. See below:
x: 1 2 3 4 5 6 7 8
y: 1 2 3 4 5 6 7 8
mask: T T T T F F T T
z: 9 8 7 6 4 3 2 1
As mentioned above, the leftmost element of “x”, “y”, “mask” and “z” is the lowest element of them.
Compass DSL is a strongly typed language. You can use the following APIs for type casting or type reinterpretation.
Conversion
Scalar type casting:
s1 = S.cast(s0, new_dtype)
s1_signed = S.i(s0_unsigned)
s2_unsigned = S.u(s1_signed)
Vector type casting:
Explicit cast/conversion
Cast to same bits:
v1 = S.cast(v0, new_dtype), v1_signed = S.i(v0_unsigned), v1_unsigned = S.u(v0_signed)
Cast to wider bits:
v0_low = S.cast(v0, new_dtype, "low"), v1 = S.cast(v0, new_dtype)
Cast to narrower bits:
v0 = S.cast((v0_low, v0_high), new_dtype), v1 = S.cast(v0, new_dtype)
The S.i
and S.u
are two convenient interfaces used for integer data, converting unsigned numbers to signed types and signed to unsigned respectively. Its internal implementation is based on S.cast
.
The S.cast(x, dtype, part)
has abilities beyond basic type casting:
Support for broadcast when input is scalar and dtype is vector dtype.
Argument
part
(only for vector conversion): used to specify which part data of the given expression or value needs to be converted.all: Represents all data, and this is the default value.
low, high, even, odd: Represent the corresponding half data.
ll, lh, hl, hh: Represent the corresponding quarter data.
More examples about S.cast
:
Cast input x to type which has same bitwidth.
# int8_to_uint8
# integer signed and unsigned conversion
v0 = a_int8[0:32]
v1 = S.cast(v0, "uint8") # a_int8[0:32] with u8 dtype
# float16_to_int16
# float and int conversion
v0 = a_fp16[0:16]
v1 = S.cast(v0, "int16") # a_fp16[0:16] with i16 dtype
Cast input x to type which has double bitwidth.
# int8_to_int16
v0 = a_int8[0:32]
v0_all_i16 = S.cast(v0, "int16") # a_int8[0:32] with i16 dtype
v0_low_i16 = S.cast(v0, "int16", "low") # a_int8[0:16] with i16 dtype
v0_high_i16 = S.cast(v0, "int16", "high") # a_int8[16:32] with i16 dtype
# int8_to_int32
v0 = a_int8[0:32]
v0_all_i32 = S.cast(v0, "int32") # a_int8[0:32] with i32 dtype
v0_ll_i32 = S.cast(v0, "int32", "ll") # a_int8[0:8] with i32 dtype
v0_lh_i32 = S.cast(v0, "int32", "lh") # a_int8[8:16] with i32 dtype
v0_hl_i32 = S.cast(v0, "int32", "hl") # a_int8[16:24] with i32 dtype
v0_hh_i32 = S.cast(v0, "int32", "hh") # a_int8[24:32] with i32 dtype
# float16_to_float32
v0_fp16 = a_fp16[0:16]
v0_all_fp32 = S.cast(v0_fp16, "fp32") # a_fp16[0:16] with fp32 dtype
v0_low_fp32 = S.cast(v0_fp16, "fp32", "low") # a_fp16[:8] with fp32 dtype
v0_high_fp32 = S.cast(v0_fp16, "fp32", "high") # a_fp16[8:16] with fp32 dtype
v0_even_fp32 = S.cast(v0_fp16, "fp32", "even") # a_fp16[0:16:2] with fp32 dtype
v0_odd_fp32 = S.cast(v0_fp16, "fp32", "odd") # a_fp16[1:16:2] with fp32 dtype
Cast input x to type which has half bitwidth.
# int16_to_int8
vl, vh = a_int16[0:16], a_int16[16:32]
v0_i8 = S.cast((vl, vh), "int8") # a_int16[0:32] with i8 dtype
v0_i8 = S.cast(a_int16, "int8") # a_int16[0:32] with i8 dtype
# float32_to_float16
vl, vh = a_fp32[0:8], a_fp32[8:16]
v0_fp16 = S.cast((vl, vh), "fp16") # a_fp32[0:16] with fp16 dtype
v0_fp16 = S.cast(a_fp32, "fp16") # a_fp32[0:16] with fp16 dtype
The S.cast
has limitations below:
Unsupported direct cast pairs (first is from type, second is to type):
“uint32”, “float32”.
“float32”, “uint32”. If precision loss is tolerated, you need to call
S.cast
twice manually, such as “uint32” -> “int32” -> “float32”.
No hardware instructions for direct conversion:
“float16”, “int8”/”uint8”/”int16”/”uint16”
“int8”/”uint8”/”int16”/”uint16”, “float16”. You can call
S.cast
twice manually, such as “float16” -> “float32” -> “uint16”.
Reinterpret
The S.reinterpret
just takes the address of variable and reinterprets as new_dtype, while the binary of data is unchanged.
v1 = S.reinterpret(v0, "int16x16")