This patch introduces partial vectorization support for VEC_PERM_EXPR when vector types specified are not supported on some architectures because of their size.
Take, for instance, this vector type: typedef int32_t vnx32si __attribute__ ((vector_size (128))); For -march=rv64gcv_zvl256b GCC doesn't vectorize the following code: __attribute__ ((noipa)) void permute_vnx32si (vnx32si values1, vnx32si values2, vnx32si *out) { vnx32si v = __builtin_shufflevector (values1, values2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); *(vnx32si *) out = v; } GCC produces 1xlw and 32xsw instructions, reading values1[1] and storing it using a scalar instruction. With this patch applied the resulting assembly would look like: permute_vnx32si: vsetivli zero,8,e32,m1,ta,ma vle32.v v2,0(a0) addi sp,sp,-128 addi a3,a2,32 addi a4,a2,64 addi a5,a2,96 vrgather.vi v1,v2,1 vse32.v v1,0(a2) vse32.v v1,0(a3) vse32.v v1,0(a4) vse32.v v1,0(a5) addi sp,sp,128 jr ra With this patch vectorization of VEC_PERM_EXPR is possible for indexes which are bounded by split_type. split_type is the biggest vector type which is supported based on can_vec_perm_var_p. In this case it is 8 elements * 32 element size = 256 bits. The optimization works by iterating through the vector and sorting element values in two groups: values which are in the [0, split_elements) range they can be vectorized by reading from the first address of vector and ones which are greater than split_elements. Since there are two vectors to choose from for lowering the valid values for the second vector are [elements, split_elements). The values which are not in their respectable range are handled using scalar instructions. Values out of range are set to 0 in the original vector and are written to twice: first when the vector store instruction writes the value 0 initially, second when the scalar store corrects the error. There is a condition, however, when this approach produces poor code. The worst case is when all indexes are out of range. This would produce something like: vsetivli zero,8,e32,m1,ta,ma # config vle32.v v2,0(a0) # One load for start of vector addi a3,a2,off # off = i*split_elements, # i = [0,elements/split_elemets - 1) # totaling div - 1 addi for vse32 vrgather.vi # v1,v2,1 # singular vse32.v v1,0(a[i]) # store for each # i = [0,elements/split_elemets - 1) # totaling div vse32 + a scalar load and multiple stores for each element. Counting up the vector code inserted together: 2 * div + 1 if all insns cost 1. This is the reasoning behind arbitrary constraint: s_assignments.length () / 2 > elements - (2 * div + 1) For if there are a greater number of scalar assignments the code would produce redundant vector instructions. Tested on risc-v 64 and risc-v 32, no regressions. PS. In the vector instruction example there are two add instructions working on the stack pointer register. I'm not quite sure about the purpose of these instructions. 2024-31-11 Dusan Stojkovic <dusan.stojko...@rt-rk.com> PR target/116425 gcc/ChangeLog: * tree-vect-generic.cc (split_lower_vec_perm): New function. (lower_vec_perm): New if condition. gcc/testsuite/ChangeLog: * gcc.target/riscv/rvv/autovec/pr116425-run.c: New test. * gcc.target/riscv/rvv/autovec/pr116425.c: New test. CONFIDENTIALITY: The contents of this e-mail are confidential and intended only for the above addressee(s). If you are not the intended recipient, or the person responsible for delivering it to the intended recipient, copying or delivering it to anyone else or using it in any unauthorized manner is prohibited and may be unlawful. If you receive this e-mail by mistake, please notify the sender and the systems administrator at straym...@rt-rk.com immediately.
--- .../riscv/rvv/autovec/pr116425-run.c | 53 +++++ .../gcc.target/riscv/rvv/autovec/pr116425.c | 54 +++++ gcc/tree-vect-generic.cc | 196 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425-run.c create mode 100644 gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425.c diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425-run.c b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425-run.c new file mode 100644 index 00000000000..05a7f5a3824 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425-run.c @@ -0,0 +1,53 @@ +/* { dg-do run } */ +/* { dg-require-effective-target rvv_zvl256b_ok } */ +/* { dg-options "-std=c99 -O3 -march=rv64gcv_zvl256b -mabi=lp64d" } */ + +#include "pr116425.c" + +#define comp(a, b, n) \ + for (unsigned i = 0; i < n; ++i) \ + if ((a)[i] != (b)[i]) \ + __builtin_abort (); \ + +int main () +{ + a8 = (vnx8si) { 1, 2, 3, 4, 5, 6, 7, 8 }; + b8 = (vnx8si) { 8, 7, 6, 5, 4, 3, 2, 1 }; + a16 = (vnx16si) { 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16 }; + b16 = (vnx16si) { 16, 15, 14, 13, 12, 11, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1 }; + a32 = (vnx32si) { 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 }; + b32 = (vnx32si) { 32, 31, 30, 29, 28, 27, 26, 25, + 24, 23, 22, 21, 20, 19, 18, 17, + 16, 15, 14, 13, 12, 11, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1 }; + + permute_vnx8si (); + comp (res8, ((vnx8si) { 2, 2, 2, 2, 2, 2, 2, 2 }), 8); + + permute_vnx16si (); + comp (res16, ((vnx16si) { 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2 }), 16); + + permute_vnx32si (); + comp (res32, ((vnx32si) { 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2 }), 32); + + permute_vnx32si1 (); + comp (res32, ((vnx32si) { 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32 }), 32); + + permute_vnx32si2 (); + comp (res32, ((vnx32si) { 32, 1, 31, 2, 30, 3, 29, 4, + 28, 5, 27, 6, 26, 7, 25, 8, + 24, 9, 23, 10, 22, 11, 21, 12, + 20, 13, 19, 14, 18, 15, 17, 16 }), 32); +} diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425.c b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425.c new file mode 100644 index 00000000000..f394c0e744b --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/pr116425.c @@ -0,0 +1,54 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -march=rv64gcv_zvl256b -ftree-vectorize" } */ + +typedef int int32_t; + +typedef int32_t vnx2si __attribute__ ((vector_size (8))); +typedef int32_t vnx4si __attribute__ ((vector_size (16))); +typedef int32_t vnx8si __attribute__ ((vector_size (32))); +typedef int32_t vnx16si __attribute__ ((vector_size (64))); +typedef int32_t vnx32si __attribute__ ((vector_size (128))); + +vnx8si res8, a8, b8; +vnx16si res16, a16, b16; +vnx32si res32, a32, b32; + +__attribute__ ((noipa)) void permute_vnx8si () { + vnx8si v = + __builtin_shufflevector (a8, b8, 1, 1, 1, 1, 1, 1, 1, 1); + res8 = v; +} +__attribute__ ((noipa)) void permute_vnx16si () { + vnx16si v = + __builtin_shufflevector (a16, b16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + res16 = v; +} +__attribute__ ((noipa)) void permute_vnx32si () { + vnx32si v = + __builtin_shufflevector (a32, b32, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1); + res32 = v; +} +__attribute__ ((noipa)) void permute_vnx32si1 () { + vnx32si v = + __builtin_shufflevector (a32, b32, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32); + res32 = v; +} +__attribute__ ((noipa)) void permute_vnx32si2 () { + vnx32si v = + __builtin_shufflevector (a32, b32, + 32, 0, 33, 1, 34, 2, 35, 3, + 36, 4, 37, 5, 38, 6, 39, 7, + 40, 8, 41, 9, 42, 10, 43, 11, + 44, 12, 45, 13, 46, 14, 47, 15 ); + res32 = v; +} +/* { dg-final { scan-assembler-times {vrgather\.vi} 4 } } */ +/* { dg-final { scan-assembler-times {vrgather\.vv} 4 } } */ diff --git a/gcc/tree-vect-generic.cc b/gcc/tree-vect-generic.cc index 78f6e552cc7..e1498ae7777 100644 --- a/gcc/tree-vect-generic.cc +++ b/gcc/tree-vect-generic.cc @@ -1381,6 +1381,200 @@ vector_element (gimple_stmt_iterator *gsi, tree vect, tree idx, tree *ptmpvec) idx, NULL_TREE, NULL_TREE); } +/* In cases where lower_vec_perm fails to vectorize permutation vectors + this function attempts to split the original vector type into a type + which can be vectorized. It achieves this in multiple steps: + 1. Find the store statement associated with VEC_PERM_EXR; + 2. Find optimal divisor, create split_type = orig_vec_type / divisor; + 3. Split lowering VEC_PERM_EXPR indexes are grouped into two categories: + a) Values v between 0 and split_elements or elements and + elements + split_elements since these values can be vectorized + for the new split_type vector. + b) Values which don't fit in these bounds are emited as scalars. + (if too many values are to be emitted as scalar load/stores, + abandon split_lower_vec_perm). + 4. Create new VEC_PERM_EXPRs and SSA names for sub vector variables. + 5. Build new GIMPLE statements: + a) Temporary BIT_FIELD_REF variables; + b) New VEC_PERM_EXPRs of split_type; + c) MEM_REF stores offsetted by split_type_t + d) Scalar loads/stores of values for indexes outside of the range in 3a. + 6. Cleanup old GIMPLE statements, insert new ones. */ + +static bool +split_lower_vec_perm (gimple_stmt_iterator *gsi, tree orig_vec_type, + tree vec0, tree vec1, + tree mask, unsigned HOST_WIDE_INT elements) +{ + // Try to find related store store stmt before going forward + tree lhs_store = NULL_TREE; + gimple_stmt_iterator store; + tree store_ptr; + tree lhs = gimple_assign_lhs (gsi_stmt (*gsi)); + use_operand_p use_p; + imm_use_iterator iterator; + FOR_EACH_IMM_USE_FAST (use_p, iterator, lhs) + { + gimple *use = USE_STMT (use_p); + if (gimple_code (use) == GIMPLE_ASSIGN) + { + tree tmp = gimple_assign_lhs (use); + lhs_store = tmp; + store = gsi_for_stmt (use); + if (TREE_CODE (tmp) == MEM_REF) + { + store_ptr = TREE_OPERAND_CHECK (lhs_store, 0); + } + else + { + store_ptr = build1 (ADDR_EXPR, orig_vec_type, lhs_store); + } + } + } + if (lhs_store == NULL_TREE) + return false; + + /* Before lowering using scalar instructions, first try to find a type which + can be expanded using SIMD extensions of the CPU. + Try to find a split_type which is a valid vector type given the + constraints of HW, failing to do so will not use VEC_PERM_EXR. */ + tree split_type; + unsigned HOST_WIDE_INT split_elements = elements; + unsigned HOST_WIDE_INT int divisor = 1; + do + { + divisor = divisor << 1; + split_elements = split_elements >> 1; + split_type = build_vector_type (TREE_TYPE (orig_vec_type), + exact_div (TYPE_VECTOR_SUBPARTS (orig_vec_type), divisor)); + if (split_elements == 1) + return false; + }while (!can_vec_perm_var_p (TYPE_MODE (split_type))); + + unsigned HOST_WIDE_INT i, j; + unsigned HOST_WIDE_INT base_type_t; + unsigned HOST_WIDE_INT split_type_t; + auto_vec<gassign*> v_assignments; + auto_vec<gassign*> s_assignments; + auto_vec<vec<constructor_elt, va_gc>*> vcv; + tree t0 = NULL_TREE, t1 = NULL_TREE, t = NULL_TREE, masktmp = NULL_TREE; + tree split_type_ptr = build_pointer_type (split_type); + tree base_type = TREE_TYPE (split_type); + tree base_type_ptr = build_pointer_type (base_type); + tree offset_ptr, offset_int; + base_type_t = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (base_type)); + split_type_t = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (split_type)); + unsigned HOST_WIDE_INT split_type_bits = CHAR_TYPE_SIZE * split_type_t; + tree split_type_width = build_int_cst (bitsizetype, split_type_bits); + for (i = 0; i < divisor; i++) + { + vec<constructor_elt, va_gc> *vc; + vec_alloc (vc, split_elements); + vcv.safe_push (vc); + for (j = 0; j < split_elements; j++) + { + bool scalar = false; + unsigned HOST_WIDE_INT offset_bytes = 0; + tree vec; + tree si = size_int (i * split_elements + j); + tree t = vector_element (gsi, mask, si, &masktmp); + unsigned HOST_WIDE_INT t_i = TREE_INT_CST_LOW (t); + if (t_i >= split_elements && t_i < elements) + { + scalar = true; + t = build_int_cst (base_type, 0); + vec = vec0; + } + else if (t_i >= elements && t_i < elements + split_elements) + { + t_i = t_i - elements + split_elements; + t = build_int_cst (base_type, t_i); + } + else if (t_i >= elements + split_elements) + { + scalar = true; + t_i -= elements; + t = build_int_cst (base_type, 0); + vec = vec1; + } + + CONSTRUCTOR_APPEND_ELT (vcv[i], NULL_TREE, t); + if (scalar) + { + gassign* load_stmt; + offset_bytes = i * split_type_t + j * base_type_t; + offset_ptr = build_int_cst (base_type_ptr, offset_bytes); + offset_bytes = t_i * split_type_t; + offset_int = build_int_cst (bitsizetype, offset_bytes); + tree sub_bit_v0 = build3 (BIT_FIELD_REF, + TREE_TYPE (split_type), + vec, + build_int_cst (bitsizetype, split_type_t), + offset_int); + tree new_lhs = build2 (MEM_REF, + TREE_TYPE (split_type), + store_ptr, + offset_ptr); + t = make_ssa_name (TREE_TYPE (split_type)); + load_stmt = gimple_build_assign (t, sub_bit_v0); + s_assignments.safe_push (load_stmt); + s_assignments.safe_push (gimple_build_assign (new_lhs, t)); + } + } + } + + // If true, too many scalar instructions, abandon split lowering. + if (s_assignments.length () / 2 > elements - 2 * (divisor + 1)) + return false; + + offset_int = build_int_cst (bitsizetype, 0); + for (i = 0; i < divisor; i++) + { + t0 = make_ssa_name (split_type); + t1 = make_ssa_name (split_type); + + /* Calculate the address for storing new lowered vector + Build store of lowered vector to *(out + offset) */ + tree offset_ptr = build_int_cst (split_type_ptr, i * split_type_t); + tree bit_field_ref_t0 = build3 (BIT_FIELD_REF, + split_type, vec0, split_type_width, offset_int); + tree bit_field_ref_t1 = build3 (BIT_FIELD_REF, + split_type, vec1, split_type_width, offset_int); + + v_assignments.safe_push (gimple_build_assign (t0, bit_field_ref_t0)); + v_assignments.safe_push (gimple_build_assign (t1, bit_field_ref_t1)); + tree sel = build_vector_from_ctor (split_type, vcv[i]); + vec_perm_builder sel_int; + if (TREE_CODE (sel) == VECTOR_CST + && tree_to_vec_perm_builder (&sel_int, sel)) + { + vec_perm_indices indices (sel_int, 2, split_elements); + tree vec_mask = vect_gen_perm_mask_checked (split_type, indices); + tree vec_perm_expr = build3 (VEC_PERM_EXPR, + split_type, t0, t1, vec_mask); + + t = make_ssa_name (split_type); + v_assignments.safe_push (gimple_build_assign (t, vec_perm_expr)); + + tree new_lhs = build2 (MEM_REF, + split_type, store_ptr, offset_ptr); + v_assignments.safe_push (gimple_build_assign (new_lhs, t)); + } + } + gsi_remove (gsi, true); + for (i = 0; i < v_assignments.length (); i++) + { + gsi_insert_before (gsi, v_assignments[i], GSI_SAME_STMT); + } + for (i = 0; i < s_assignments.length (); i++) + { + gsi_insert_before (gsi, s_assignments[i], GSI_SAME_STMT); + } + gsi_remove (&store, true); + + return true; +} + /* Check if VEC_PERM_EXPR within the given setting is supported by hardware, or lower it piecewise. @@ -1503,6 +1696,9 @@ lower_vec_perm (gimple_stmt_iterator *gsi) warning_at (loc, OPT_Wvector_operation_performance, "vector shuffling operation will be expanded piecewise"); + if (split_lower_vec_perm (gsi, res_vect_type, vec0, vec1, mask, elements)) + return; + vec_alloc (v, elements); bool constant_p = true; for (i = 0; i < elements; i++) -- 2.43.0