On Mon, Nov 20, 2017 at 12:31 PM, Bin.Cheng <amker.ch...@gmail.com> wrote: > On Fri, Nov 17, 2017 at 3:03 PM, Richard Sandiford > <richard.sandif...@linaro.org> wrote: >> ivopts previously treated pointer arguments to internal functions >> like IFN_MASK_LOAD and IFN_MASK_STORE as normal gimple values. >> This patch makes it treat them as addresses instead. This makes >> a significant difference to the code quality for SVE loops, >> since we can then use loads and stores with scaled indices. > Thanks for working on this. This can be extended to other internal > functions which eventually > are expanded into memory references. I believe (at least) both x86 > and AArch64 has such > requirement.
In addition to Bins comments I only have a single one (the rest of the middle-end changes look OK). The alias type of MEM_REFs and TARGET_MEM_REFs in ADDR_EXPR context is meaningless so you don't need to jump through hoops to get at it or preserve it in any way, likewise for CLIQUE/BASE if it were present. Maybe you can simplify code with this. As you're introducing &TARGET_MEM_REF as a valid construct (it weren't before) you'll run into missing / misguided foldings eventually. So be prepared to fix up fallout. Thanks, Richard. >> >> The patch also adds support for ADDR_EXPRs of TARGET_MEM_REFs, >> which are the natural way of representing the result of the >> ivopts transformation. >> >> Tested on aarch64-linux-gnu (with and without SVE), x86_64-linux-gnu >> and powerpc64le-linux-gnu. OK to install? >> >> Richard >> >> >> 2017-11-17 Richard Sandiford <richard.sandif...@linaro.org> >> Alan Hayward <alan.hayw...@arm.com> >> David Sherwood <david.sherw...@arm.com> >> >> gcc/ >> * expr.c (expand_expr_addr_expr_1): Handle ADDR_EXPRs of >> TARGET_MEM_REFs. >> * gimple-expr.h (is_gimple_addressable: Likewise. >> * gimple-expr.c (is_gimple_address): Likewise. >> * internal-fn.c (expand_call_mem_ref): New function. >> (expand_mask_load_optab_fn): Use it. >> (expand_mask_store_optab_fn): Likewise. >> * tree-ssa-loop-ivopts.c (USE_ADDRESS): Split into... >> (USE_REF_ADDRESS, USE_PTR_ADDRESS): ...these new use types. >> (dump_groups): Update accordingly. >> (iv_use::mem_type): New member variable. >> (address_p): New function. >> (record_use): Add a mem_type argument and initialize the new >> mem_type field. >> (record_group_use): Add a mem_type argument. Use address_p. >> Update call to record_use. >> (find_interesting_uses_op): Update call to record_group_use. >> (find_interesting_uses_cond): Likewise. >> (find_interesting_uses_address): Likewise. >> (get_mem_type_for_internal_fn): New function. >> (find_address_like_use): Likewise. >> (find_interesting_uses_stmt): Try find_address_like_use before >> calling find_interesting_uses_op. >> (addr_offset_valid_p): Use the iv mem_type field as the type >> of the addressed memory. >> (add_autoinc_candidates): Likewise. >> (get_address_cost): Likewise. >> (split_small_address_groups_p): Use address_p. >> (split_address_groups): Likewise. >> (add_iv_candidate_for_use): Likewise. >> (autoinc_possible_for_pair): Likewise. >> (rewrite_groups): Likewise. >> (get_use_type): Check for USE_REF_ADDRESS instead of USE_ADDRESS. >> (determine_group_iv_cost): Update after split of USE_ADDRESS. >> (get_alias_ptr_type_for_ptr_address): New function. >> (rewrite_use_address): Rewrite address uses in calls that were >> identified by find_address_like_use. >> >> gcc/testsuite/ >> * gcc.dg/tree-ssa/scev-9.c: Expected REFERENCE ADDRESS >> instead of just ADDRESS. >> * gcc.dg/tree-ssa/scev-10.c: Likewise. >> * gcc.dg/tree-ssa/scev-11.c: Likewise. >> * gcc.dg/tree-ssa/scev-12.c: Likewise. >> * gcc.target/aarch64/sve_index_offset_1.c: New test. >> * gcc.target/aarch64/sve_index_offset_1_run.c: Likewise. >> * gcc.target/aarch64/sve_loop_add_2.c: Likewise. >> * gcc.target/aarch64/sve_loop_add_3.c: Likewise. >> * gcc.target/aarch64/sve_while_1.c: Check for indexed addressing >> modes. >> * gcc.target/aarch64/sve_while_2.c: Likewise. >> * gcc.target/aarch64/sve_while_3.c: Likewise. >> * gcc.target/aarch64/sve_while_4.c: Likewise. >> >> Index: gcc/expr.c >> =================================================================== >> --- gcc/expr.c 2017-11-17 09:49:36.191354637 +0000 >> +++ gcc/expr.c 2017-11-17 15:02:12.868132458 +0000 >> @@ -7814,6 +7814,9 @@ expand_expr_addr_expr_1 (tree exp, rtx t >> return expand_expr (tem, target, tmode, modifier); >> } >> >> + case TARGET_MEM_REF: >> + return addr_for_mem_ref (exp, as, true); >> + >> case CONST_DECL: >> /* Expand the initializer like constants above. */ >> result = XEXP (expand_expr_constant (DECL_INITIAL (exp), >> Index: gcc/gimple-expr.h >> =================================================================== >> --- gcc/gimple-expr.h 2017-11-17 09:40:43.520567009 +0000 >> +++ gcc/gimple-expr.h 2017-11-17 15:02:12.868132458 +0000 >> @@ -119,6 +119,7 @@ virtual_operand_p (tree op) >> is_gimple_addressable (tree t) >> { >> return (is_gimple_id (t) || handled_component_p (t) >> + || TREE_CODE (t) == TARGET_MEM_REF >> || TREE_CODE (t) == MEM_REF); >> } >> >> Index: gcc/gimple-expr.c >> =================================================================== >> --- gcc/gimple-expr.c 2017-10-13 10:23:39.845432950 +0100 >> +++ gcc/gimple-expr.c 2017-11-17 15:02:12.868132458 +0000 >> @@ -631,7 +631,9 @@ is_gimple_address (const_tree t) >> op = TREE_OPERAND (op, 0); >> } >> >> - if (CONSTANT_CLASS_P (op) || TREE_CODE (op) == MEM_REF) >> + if (CONSTANT_CLASS_P (op) >> + || TREE_CODE (op) == TARGET_MEM_REF >> + || TREE_CODE (op) == MEM_REF) >> return true; >> >> switch (TREE_CODE (op)) >> Index: gcc/internal-fn.c >> =================================================================== >> --- gcc/internal-fn.c 2017-11-17 14:57:36.436527536 +0000 >> +++ gcc/internal-fn.c 2017-11-17 15:02:12.869042409 +0000 >> @@ -2367,15 +2367,47 @@ expand_LOOP_DIST_ALIAS (internal_fn, gca >> gcc_unreachable (); >> } >> >> +/* Return a memory reference of type TYPE for argument INDEX of STMT. >> + Use argument INDEX + 1 to derive the second (TBAA) operand. */ >> + >> +static tree >> +expand_call_mem_ref (tree type, gcall *stmt, int index) >> +{ >> + tree addr = gimple_call_arg (stmt, index); >> + tree alias_ptr_type = TREE_TYPE (gimple_call_arg (stmt, index + 1)); >> + unsigned int align = tree_to_shwi (gimple_call_arg (stmt, index + 1)); >> + if (TYPE_ALIGN (type) != align) >> + type = build_aligned_type (type, align); >> + >> + tree tmp = addr; >> + if (TREE_CODE (tmp) == SSA_NAME) >> + { >> + gimple *def = SSA_NAME_DEF_STMT (tmp); >> + if (gimple_assign_single_p (def)) >> + tmp = gimple_assign_rhs1 (def); >> + } >> + >> + if (TREE_CODE (tmp) == ADDR_EXPR) >> + { >> + tree mem = TREE_OPERAND (tmp, 0); >> + if (TREE_CODE (mem) == TARGET_MEM_REF >> + && types_compatible_p (TREE_TYPE (mem), type) >> + && alias_ptr_type == TREE_TYPE (TMR_OFFSET (mem)) >> + && integer_zerop (TMR_OFFSET (mem))) >> + return mem; >> + } >> + >> + return fold_build2 (MEM_REF, type, addr, build_int_cst (alias_ptr_type, >> 0)); >> +} >> + >> /* Expand MASK_LOAD{,_LANES} call STMT using optab OPTAB. */ >> >> static void >> expand_mask_load_optab_fn (internal_fn, gcall *stmt, convert_optab optab) >> { >> struct expand_operand ops[3]; >> - tree type, lhs, rhs, maskt, ptr; >> + tree type, lhs, rhs, maskt; >> rtx mem, target, mask; >> - unsigned align; >> insn_code icode; >> >> maskt = gimple_call_arg (stmt, 2); >> @@ -2383,11 +2415,7 @@ expand_mask_load_optab_fn (internal_fn, >> if (lhs == NULL_TREE) >> return; >> type = TREE_TYPE (lhs); >> - ptr = build_int_cst (TREE_TYPE (gimple_call_arg (stmt, 1)), 0); >> - align = tree_to_shwi (gimple_call_arg (stmt, 1)); >> - if (TYPE_ALIGN (type) != align) >> - type = build_aligned_type (type, align); >> - rhs = fold_build2 (MEM_REF, type, gimple_call_arg (stmt, 0), ptr); >> + rhs = expand_call_mem_ref (type, stmt, 0); >> >> if (optab == vec_mask_load_lanes_optab) >> icode = get_multi_vector_move (type, optab); >> @@ -2413,19 +2441,14 @@ #define expand_mask_load_lanes_optab_fn >> expand_mask_store_optab_fn (internal_fn, gcall *stmt, convert_optab optab) >> { >> struct expand_operand ops[3]; >> - tree type, lhs, rhs, maskt, ptr; >> + tree type, lhs, rhs, maskt; >> rtx mem, reg, mask; >> - unsigned align; >> insn_code icode; >> >> maskt = gimple_call_arg (stmt, 2); >> rhs = gimple_call_arg (stmt, 3); >> type = TREE_TYPE (rhs); >> - ptr = build_int_cst (TREE_TYPE (gimple_call_arg (stmt, 1)), 0); >> - align = tree_to_shwi (gimple_call_arg (stmt, 1)); >> - if (TYPE_ALIGN (type) != align) >> - type = build_aligned_type (type, align); >> - lhs = fold_build2 (MEM_REF, type, gimple_call_arg (stmt, 0), ptr); >> + lhs = expand_call_mem_ref (type, stmt, 0); >> >> if (optab == vec_mask_store_lanes_optab) >> icode = get_multi_vector_move (type, optab); > Support for TARGET_MEM_REF and refactoring of expand_call_* in above > code looks independent > to IVOPTs change? If so, coule you split it into two patches? I can > approve IVOPTs part patch with > below comments. > >> Index: gcc/tree-ssa-loop-ivopts.c >> =================================================================== >> --- gcc/tree-ssa-loop-ivopts.c 2017-11-17 09:05:59.900349210 +0000 >> +++ gcc/tree-ssa-loop-ivopts.c 2017-11-17 15:02:12.870862310 +0000 >> @@ -166,7 +166,11 @@ struct version_info >> enum use_type >> { >> USE_NONLINEAR_EXPR, /* Use in a nonlinear expression. */ >> - USE_ADDRESS, /* Use in an address. */ >> + USE_REF_ADDRESS, /* Use is an address for an explicit memory >> + reference. */ >> + USE_PTR_ADDRESS, /* Use is a pointer argument to a function in >> + cases where the expansion of the function >> + will turn the argument into a normal address. */ >> USE_COMPARE /* Use is a compare. */ >> }; >> >> @@ -362,6 +366,9 @@ struct iv_use >> unsigned id; /* The id of the use. */ >> unsigned group_id; /* The group id the use belongs to. */ >> enum use_type type; /* Type of the use. */ >> + tree mem_type; /* The memory type to use when testing whether an >> + address is legitimate, and what the address's >> + cost is. */ >> struct iv *iv; /* The induction variable it is based on. */ >> gimple *stmt; /* Statement in that it occurs. */ >> tree *op_p; /* The place where it occurs. */ >> @@ -506,6 +513,14 @@ struct iv_inv_expr_hasher : free_ptr_has >> static inline bool equal (const iv_inv_expr_ent *, const iv_inv_expr_ent >> *); >> }; >> >> +/* Return true if uses of type TYPE represent some form of address. */ >> + >> +inline bool >> +address_p (use_type type) >> +{ >> + return type == USE_REF_ADDRESS || type == USE_PTR_ADDRESS; >> +} >> + >> /* Hash function for loop invariant expressions. */ >> >> inline hashval_t >> @@ -768,8 +783,10 @@ dump_groups (FILE *file, struct ivopts_d >> fprintf (file, "Group %d:\n", group->id); >> if (group->type == USE_NONLINEAR_EXPR) >> fprintf (file, " Type:\tGENERIC\n"); >> - else if (group->type == USE_ADDRESS) >> - fprintf (file, " Type:\tADDRESS\n"); >> + else if (group->type == USE_REF_ADDRESS) >> + fprintf (file, " Type:\tREFERENCE ADDRESS\n"); >> + else if (group->type == USE_PTR_ADDRESS) >> + fprintf (file, " Type:\tPOINTER ARGUMENT ADDRESS\n"); >> else >> { >> gcc_assert (group->type == USE_COMPARE); >> @@ -1502,19 +1519,21 @@ find_induction_variables (struct ivopts_ >> >> /* Records a use of TYPE at *USE_P in STMT whose value is IV in GROUP. >> For address type use, ADDR_BASE is the stripped IV base, ADDR_OFFSET >> - is the const offset stripped from IV base; for other types use, both >> - are zero by default. */ >> + is the const offset stripped from IV base and MEM_TYPE is the type >> + of the memory being addressed. For uses of other types, ADDR_BASE >> + and ADDR_OFFSET are zero by default and MEM_TYPE is NULL_TREE. */ >> >> static struct iv_use * >> record_use (struct iv_group *group, tree *use_p, struct iv *iv, >> - gimple *stmt, enum use_type type, tree addr_base, >> - poly_uint64 addr_offset) >> + gimple *stmt, enum use_type type, tree mem_type, >> + tree addr_base, poly_uint64 addr_offset) >> { >> struct iv_use *use = XCNEW (struct iv_use); >> >> use->id = group->vuses.length (); >> use->group_id = group->id; >> use->type = type; >> + use->mem_type = mem_type; >> use->iv = iv; >> use->stmt = stmt; >> use->op_p = use_p; >> @@ -1569,18 +1588,21 @@ record_group (struct ivopts_data *data, >> } >> >> /* Record a use of TYPE at *USE_P in STMT whose value is IV in a group. >> - New group will be created if there is no existing group for the use. */ >> + New group will be created if there is no existing group for the use. >> + MEM_TYPE is the type of memory being addressed, or NULL if this >> + isn't an address reference. */ >> >> static struct iv_use * >> record_group_use (struct ivopts_data *data, tree *use_p, >> - struct iv *iv, gimple *stmt, enum use_type type) >> + struct iv *iv, gimple *stmt, enum use_type type, >> + tree mem_type) >> { >> tree addr_base = NULL; >> struct iv_group *group = NULL; >> poly_uint64 addr_offset = 0; >> >> /* Record non address type use in a new group. */ >> - if (type == USE_ADDRESS && iv->base_object) >> + if (address_p (type) && iv->base_object) > I forgot to somplify this condition given (address_p (type) && > !iv->base_object) is not allowed now. > You can simplify this after below comment on base_object if you want. >> { >> unsigned int i; >> >> @@ -1591,7 +1613,7 @@ record_group_use (struct ivopts_data *da >> >> group = data->vgroups[i]; >> use = group->vuses[0]; >> - if (use->type != USE_ADDRESS || !use->iv->base_object) > And here. > >> + if (!address_p (use->type) || !use->iv->base_object) >> continue; >> >> /* Check if it has the same stripped base and step. */ >> @@ -1607,7 +1629,8 @@ record_group_use (struct ivopts_data *da >> if (!group) >> group = record_group (data, type); >> >> - return record_use (group, use_p, iv, stmt, type, addr_base, addr_offset); >> + return record_use (group, use_p, iv, stmt, type, mem_type, >> + addr_base, addr_offset); >> } >> >> /* Checks whether the use OP is interesting and if so, records it. */ >> @@ -1641,7 +1664,7 @@ find_interesting_uses_op (struct ivopts_ >> stmt = SSA_NAME_DEF_STMT (op); >> gcc_assert (gimple_code (stmt) == GIMPLE_PHI || is_gimple_assign (stmt)); >> >> - use = record_group_use (data, NULL, iv, stmt, USE_NONLINEAR_EXPR); >> + use = record_group_use (data, NULL, iv, stmt, USE_NONLINEAR_EXPR, >> NULL_TREE); >> iv->nonlin_use = use; >> return use; >> } >> @@ -1757,10 +1780,10 @@ find_interesting_uses_cond (struct ivopt >> return; >> } >> >> - record_group_use (data, var_p, var_iv, stmt, USE_COMPARE); >> + record_group_use (data, var_p, var_iv, stmt, USE_COMPARE, NULL_TREE); >> /* Record compare type iv_use for iv on the other side of comparison. */ >> if (ret == COMP_IV_EXPR_2) >> - record_group_use (data, bound_p, bound_iv, stmt, USE_COMPARE); >> + record_group_use (data, bound_p, bound_iv, stmt, USE_COMPARE, >> NULL_TREE); >> } >> >> /* Returns the outermost loop EXPR is obviously invariant in >> @@ -2375,7 +2398,7 @@ find_interesting_uses_address (struct iv >> if (civ->base_object == NULL_TREE) >> goto fail; >> >> - record_group_use (data, op_p, civ, stmt, USE_ADDRESS); >> + record_group_use (data, op_p, civ, stmt, USE_REF_ADDRESS, TREE_TYPE >> (*op_p)); >> return; >> >> fail: >> @@ -2398,6 +2421,51 @@ find_invariants_stmt (struct ivopts_data >> } >> } >> >> +/* CALL calls an internal function. If operand *OP_P will become an >> + address when the call is expanded, return the type of the memory >> + being addressed, otherwise return null. */ >> + >> +static tree >> +get_mem_type_for_internal_fn (gcall *call, tree *op_p) >> +{ >> + switch (gimple_call_internal_fn (call)) >> + { >> + case IFN_MASK_LOAD: >> + if (op_p == gimple_call_arg_ptr (call, 0)) >> + return TREE_TYPE (gimple_call_lhs (call)); >> + return NULL_TREE; >> + >> + case IFN_MASK_STORE: >> + if (op_p == gimple_call_arg_ptr (call, 0)) >> + return TREE_TYPE (gimple_call_arg (call, 3)); >> + return NULL_TREE; >> + >> + default: >> + return NULL_TREE; >> + } >> +} >> + >> +/* IV is a (non-address) iv that describes operand *OP_P of STMT. >> + Return true if the operand will become an address when STMT >> + is expanded and record the associated address use if so. */ >> + >> +static bool >> +find_address_like_use (struct ivopts_data *data, gimple *stmt, tree *op_p, >> + struct iv *iv) >> +{ >> + tree mem_type = NULL_TREE; >> + if (gcall *call = dyn_cast <gcall *> (stmt)) >> + if (gimple_call_internal_p (call)) >> + mem_type = get_mem_type_for_internal_fn (call, op_p); >> + if (mem_type) >> + { >> + iv = alloc_iv (data, iv->base, iv->step); > We now don't allow address type iv_use without base_object. So > checking code like below > is needed here: > > /* Fail if base object of this memory reference is unknown. */ > if (iv->base_object == NULL_TREE) > return false; > > IVOPTs part is OK with this change. > > Thanks, > bin >> + record_group_use (data, op_p, iv, stmt, USE_PTR_ADDRESS, mem_type); >> + return true; >> + } >> + return false; >> +} >> + >> /* Finds interesting uses of induction variables in the statement STMT. */ >> >> static void >> @@ -2482,7 +2550,8 @@ find_interesting_uses_stmt (struct ivopt >> if (!iv) >> continue; >> >> - find_interesting_uses_op (data, op); >> + if (!find_address_like_use (data, stmt, use_p->use, iv)) >> + find_interesting_uses_op (data, op); >> } >> } >> >> @@ -2516,7 +2585,7 @@ addr_offset_valid_p (struct iv_use *use, >> rtx reg, addr; >> unsigned list_index; >> addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (use->iv->base)); >> - machine_mode addr_mode, mem_mode = TYPE_MODE (TREE_TYPE (*use->op_p)); >> + machine_mode addr_mode, mem_mode = TYPE_MODE (use->mem_type); >> >> list_index = (unsigned) as * MAX_MACHINE_MODE + (unsigned) mem_mode; >> if (list_index >= vec_safe_length (addr_list)) >> @@ -2573,7 +2642,7 @@ split_small_address_groups_p (struct ivo >> if (group->vuses.length () == 1) >> continue; >> >> - gcc_assert (group->type == USE_ADDRESS); >> + gcc_assert (address_p (group->type)); >> if (group->vuses.length () == 2) >> { >> if (compare_sizes_for_sort (group->vuses[0]->addr_offset, >> @@ -2625,7 +2694,7 @@ split_address_groups (struct ivopts_data >> if (group->vuses.length () == 1) >> continue; >> >> - gcc_assert (group->type == USE_ADDRESS); >> + gcc_assert (address_p (use->type)); >> >> for (j = 1; j < group->vuses.length ();) >> { >> @@ -3145,7 +3214,7 @@ add_autoinc_candidates (struct ivopts_da >> >> cstepi = int_cst_value (step); >> >> - mem_mode = TYPE_MODE (TREE_TYPE (*use->op_p)); >> + mem_mode = TYPE_MODE (use->mem_type); >> if (((USE_LOAD_PRE_INCREMENT (mem_mode) >> || USE_STORE_PRE_INCREMENT (mem_mode)) >> && must_eq (GET_MODE_SIZE (mem_mode), cstepi)) >> @@ -3436,7 +3505,7 @@ add_iv_candidate_for_use (struct ivopts_ >> /* At last, add auto-incremental candidates. Make such variables >> important since other iv uses with same base object may be based >> on it. */ >> - if (use != NULL && use->type == USE_ADDRESS) >> + if (use != NULL && address_p (use->type)) >> add_autoinc_candidates (data, iv->base, iv->step, true, use); >> } >> >> @@ -3903,7 +3972,7 @@ get_use_type (struct iv_use *use) >> tree base_type = TREE_TYPE (use->iv->base); >> tree type; >> >> - if (use->type == USE_ADDRESS) >> + if (use->type == USE_REF_ADDRESS) >> { >> /* The base_type may be a void pointer. Create a pointer type based >> on >> the mem_ref instead. */ >> @@ -4331,7 +4400,7 @@ get_address_cost (struct ivopts_data *da >> struct mem_address parts = {NULL_TREE, integer_one_node, >> NULL_TREE, NULL_TREE, NULL_TREE}; >> machine_mode addr_mode = TYPE_MODE (type); >> - machine_mode mem_mode = TYPE_MODE (TREE_TYPE (*use->op_p)); >> + machine_mode mem_mode = TYPE_MODE (use->mem_type); >> addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (use->iv->base)); >> /* Only true if ratio != 1. */ >> bool ok_with_ratio_p = false; >> @@ -5220,7 +5289,8 @@ determine_group_iv_cost (struct ivopts_d >> case USE_NONLINEAR_EXPR: >> return determine_group_iv_cost_generic (data, group, cand); >> >> - case USE_ADDRESS: >> + case USE_REF_ADDRESS: >> + case USE_PTR_ADDRESS: >> return determine_group_iv_cost_address (data, group, cand); >> >> case USE_COMPARE: >> @@ -5238,7 +5308,7 @@ determine_group_iv_cost (struct ivopts_d >> autoinc_possible_for_pair (struct ivopts_data *data, struct iv_use *use, >> struct iv_cand *cand) >> { >> - if (use->type != USE_ADDRESS) >> + if (!address_p (use->type)) >> return false; >> >> bool can_autoinc = false; >> @@ -6997,6 +7067,27 @@ adjust_iv_update_pos (struct iv_cand *ca >> cand->incremented_at = use->stmt; >> } >> >> +/* Return the alias pointer type that should be used for a MEM_REF >> + associated with USE, which has type USE_PTR_ADDRESS. */ >> + >> +static tree >> +get_alias_ptr_type_for_ptr_address (iv_use *use) >> +{ >> + gcall *call = as_a <gcall *> (use->stmt); >> + switch (gimple_call_internal_fn (call)) >> + { >> + case IFN_MASK_LOAD: >> + case IFN_MASK_STORE: >> + /* The second argument contains the correct alias type. */ >> + gcc_assert (use->op_p = gimple_call_arg_ptr (call, 0)); >> + return TREE_TYPE (gimple_call_arg (call, 1)); >> + >> + default: >> + gcc_unreachable (); >> + } >> +} >> + >> + >> /* Rewrites USE (address that is an iv) using candidate CAND. */ >> >> static void >> @@ -7025,16 +7116,31 @@ rewrite_use_address (struct ivopts_data >> tree iv = var_at_stmt (data->current_loop, cand, use->stmt); >> tree base_hint = (cand->iv->base_object) ? iv : NULL_TREE; >> gimple_stmt_iterator bsi = gsi_for_stmt (use->stmt); >> - tree type = TREE_TYPE (*use->op_p); >> - unsigned int align = get_object_alignment (*use->op_p); >> - if (align != TYPE_ALIGN (type)) >> - type = build_aligned_type (type, align); >> - >> - tree ref = create_mem_ref (&bsi, type, &aff, >> - reference_alias_ptr_type (*use->op_p), >> + tree type = use->mem_type; >> + tree alias_ptr_type; >> + if (use->type == USE_PTR_ADDRESS) >> + alias_ptr_type = get_alias_ptr_type_for_ptr_address (use); >> + else >> + { >> + gcc_assert (type == TREE_TYPE (*use->op_p)); >> + unsigned int align = get_object_alignment (*use->op_p); >> + if (align != TYPE_ALIGN (type)) >> + type = build_aligned_type (type, align); >> + alias_ptr_type = reference_alias_ptr_type (*use->op_p); >> + } >> + tree ref = create_mem_ref (&bsi, type, &aff, alias_ptr_type, >> iv, base_hint, data->speed); >> >> - copy_ref_info (ref, *use->op_p); >> + if (use->type == USE_PTR_ADDRESS) >> + { >> + ref = fold_build1 (ADDR_EXPR, build_pointer_type (use->mem_type), >> ref); >> + ref = fold_convert (get_use_type (use), ref); >> + ref = force_gimple_operand_gsi (&bsi, ref, true, NULL_TREE, >> + true, GSI_SAME_STMT); >> + } >> + else >> + copy_ref_info (ref, *use->op_p); >> + >> *use->op_p = ref; >> } >> >> @@ -7110,7 +7216,7 @@ rewrite_groups (struct ivopts_data *data >> update_stmt (group->vuses[j]->stmt); >> } >> } >> - else if (group->type == USE_ADDRESS) >> + else if (address_p (group->type)) >> { >> for (j = 0; j < group->vuses.length (); j++) >> { >> Index: gcc/testsuite/gcc.dg/tree-ssa/scev-9.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tree-ssa/scev-9.c 2016-05-02 >> 10:44:33.000000000 +0100 >> +++ gcc/testsuite/gcc.dg/tree-ssa/scev-9.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -18,5 +18,5 @@ foo (unsigned char s, unsigned char l) >> } >> >> /* Address of array reference is scev. */ >> -/* { dg-final { scan-tree-dump-times " Type:\\tADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> +/* { dg-final { scan-tree-dump-times " Type:\\tREFERENCE ADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> >> Index: gcc/testsuite/gcc.dg/tree-ssa/scev-10.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tree-ssa/scev-10.c 2016-05-02 >> 10:44:33.000000000 +0100 >> +++ gcc/testsuite/gcc.dg/tree-ssa/scev-10.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -18,5 +18,5 @@ foo (signed char s, signed char l) >> } >> >> /* Address of array reference is scev. */ >> -/* { dg-final { scan-tree-dump-times " Type:\\tADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> +/* { dg-final { scan-tree-dump-times " Type:\\tREFERENCE ADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> >> Index: gcc/testsuite/gcc.dg/tree-ssa/scev-11.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tree-ssa/scev-11.c 2016-05-02 >> 10:44:33.000000000 +0100 >> +++ gcc/testsuite/gcc.dg/tree-ssa/scev-11.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -23,4 +23,4 @@ foo (int n) >> } >> >> /* Address of array reference to b is scev. */ >> -/* { dg-final { scan-tree-dump-times " Type:\\tADDRESS\n Use >> \[0-9\].\[0-9\]:" 2 "ivopts" } } */ >> +/* { dg-final { scan-tree-dump-times " Type:\\tREFERENCE ADDRESS\n Use >> \[0-9\].\[0-9\]:" 2 "ivopts" } } */ >> Index: gcc/testsuite/gcc.dg/tree-ssa/scev-12.c >> =================================================================== >> --- gcc/testsuite/gcc.dg/tree-ssa/scev-12.c 2016-05-02 >> 10:44:33.000000000 +0100 >> +++ gcc/testsuite/gcc.dg/tree-ssa/scev-12.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -24,4 +24,4 @@ foo (int x, int n) >> } >> >> /* Address of array reference to b is not scev. */ >> -/* { dg-final { scan-tree-dump-times " Type:\\tADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> +/* { dg-final { scan-tree-dump-times " Type:\\tREFERENCE ADDRESS\n Use >> \[0-9\].\[0-9\]:" 1 "ivopts" } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_index_offset_1.c >> =================================================================== >> --- /dev/null 2017-11-14 14:28:07.424493901 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_index_offset_1.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -0,0 +1,49 @@ >> +/* { dg-do compile } */ >> +/* { dg-options "-std=c99 -ftree-vectorize -O2 -fno-inline >> -march=armv8-a+sve -msve-vector-bits=256" } */ >> + >> +#define SIZE 15*8+3 >> + >> +#define INDEX_OFFSET_TEST_1(SIGNED, TYPE, ITERTYPE) \ >> +void set_##SIGNED##_##TYPE##_##ITERTYPE (SIGNED TYPE *__restrict out, \ >> + SIGNED TYPE *__restrict in) \ >> +{ \ >> + SIGNED ITERTYPE i; \ >> + for (i = 0; i < SIZE; i++) \ >> + { \ >> + out[i] = in[i]; \ >> + } \ >> +} \ >> +void set_##SIGNED##_##TYPE##_##ITERTYPE##_var (SIGNED TYPE *__restrict out, >> \ >> + SIGNED TYPE *__restrict in, \ >> + SIGNED ITERTYPE n) \ >> +{\ >> + SIGNED ITERTYPE i;\ >> + for (i = 0; i < n; i++)\ >> + {\ >> + out[i] = in[i];\ >> + }\ >> +} >> + >> +#define INDEX_OFFSET_TEST(SIGNED, TYPE)\ >> + INDEX_OFFSET_TEST_1 (SIGNED, TYPE, char) \ >> + INDEX_OFFSET_TEST_1 (SIGNED, TYPE, short) \ >> + INDEX_OFFSET_TEST_1 (SIGNED, TYPE, int) \ >> + INDEX_OFFSET_TEST_1 (SIGNED, TYPE, long) >> + >> +INDEX_OFFSET_TEST (signed, long) >> +INDEX_OFFSET_TEST (unsigned, long) >> +INDEX_OFFSET_TEST (signed, int) >> +INDEX_OFFSET_TEST (unsigned, int) >> +INDEX_OFFSET_TEST (signed, short) >> +INDEX_OFFSET_TEST (unsigned, short) >> +INDEX_OFFSET_TEST (signed, char) >> +INDEX_OFFSET_TEST (unsigned, char) >> + >> +/* { dg-final { scan-assembler-times "ld1d\\tz\[0-9\]+.d, p\[0-9\]+/z, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 3\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "st1d\\tz\[0-9\]+.d, p\[0-9\]+, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 3\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "ld1w\\tz\[0-9\]+.s, p\[0-9\]+/z, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 2\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "st1w\\tz\[0-9\]+.s, p\[0-9\]+, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 2\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "ld1h\\tz\[0-9\]+.h, p\[0-9\]+/z, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 1\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "st1h\\tz\[0-9\]+.h, p\[0-9\]+, >> \\\[x\[0-9\]+, x\[0-9\]+, lsl 1\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "ld1b\\tz\[0-9\]+.b, p\[0-9\]+/z, >> \\\[x\[0-9\]+, x\[0-9\]+\\\]" 16 } } */ >> +/* { dg-final { scan-assembler-times "st1b\\tz\[0-9\]+.b, p\[0-9\]+, >> \\\[x\[0-9\]+, x\[0-9\]+\\\]" 16 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_index_offset_1_run.c >> =================================================================== >> --- /dev/null 2017-11-14 14:28:07.424493901 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_index_offset_1_run.c 2017-11-17 >> 15:02:12.869042409 +0000 >> @@ -0,0 +1,48 @@ >> +/* { dg-do run { target aarch64_sve_hw } } */ >> +/* { dg-options "-std=c99 -ftree-vectorize -O2 -fno-inline >> -march=armv8-a+sve" } */ >> +/* { dg-options "-std=c99 -ftree-vectorize -O2 -fno-inline >> -march=armv8-a+sve -msve-vector-bits=256" { target aarch64_sve256_hw } } */ >> + >> +#include "sve_index_offset_1_run.c" >> + >> +#include <string.h> >> + >> +#define CALL_INDEX_OFFSET_TEST_1(SIGNED, TYPE, ITERTYPE)\ >> +{\ >> + SIGNED TYPE out[SIZE + 1];\ >> + SIGNED TYPE in1[SIZE + 1];\ >> + SIGNED TYPE in2[SIZE + 1];\ >> + for (int i = 0; i < SIZE + 1; ++i)\ >> + {\ >> + in1[i] = (i * 4) ^ i;\ >> + in2[i] = (i * 2) ^ i;\ >> + }\ >> + out[SIZE] = 42;\ >> + set_##SIGNED##_##TYPE##_##ITERTYPE (out, in1); \ >> + if (0 != memcmp (out, in1, SIZE * sizeof (TYPE)))\ >> + return 1;\ >> + set_##SIGNED##_##TYPE##_##ITERTYPE##_var (out, in2, SIZE); \ >> + if (0 != memcmp (out, in2, SIZE * sizeof (TYPE)))\ >> + return 1;\ >> + if (out[SIZE] != 42)\ >> + return 1;\ >> +} >> + >> +#define CALL_INDEX_OFFSET_TEST(SIGNED, TYPE)\ >> + CALL_INDEX_OFFSET_TEST_1 (SIGNED, TYPE, char) \ >> + CALL_INDEX_OFFSET_TEST_1 (SIGNED, TYPE, short) \ >> + CALL_INDEX_OFFSET_TEST_1 (SIGNED, TYPE, int) \ >> + CALL_INDEX_OFFSET_TEST_1 (SIGNED, TYPE, long) >> + >> +int >> +main (void) >> +{ >> + CALL_INDEX_OFFSET_TEST (signed, long) >> + CALL_INDEX_OFFSET_TEST (unsigned, long) >> + CALL_INDEX_OFFSET_TEST (signed, int) >> + CALL_INDEX_OFFSET_TEST (unsigned, int) >> + CALL_INDEX_OFFSET_TEST (signed, short) >> + CALL_INDEX_OFFSET_TEST (unsigned, short) >> + CALL_INDEX_OFFSET_TEST (signed, char) >> + CALL_INDEX_OFFSET_TEST (unsigned, char) >> + return 0; >> +} >> Index: gcc/testsuite/gcc.target/aarch64/sve_loop_add_2.c >> =================================================================== >> --- /dev/null 2017-11-14 14:28:07.424493901 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_loop_add_2.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -0,0 +1,12 @@ >> +/* { dg-do compile } */ >> +/* { dg-options "-std=c99 -O3 -march=armv8-a+sve" } */ >> + >> +void >> +foo (int *__restrict a, int *__restrict b) >> +{ >> + for (int i = 0; i < 512; ++i) >> + a[i] += b[i]; >> +} >> + >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+.s, p[0-7]+/z, >> \[x[0-9]+, x[0-9]+, lsl 2\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+.s, p[0-7]+, >> \[x[0-9]+, x[0-9]+, lsl 2\]\n} 1 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_loop_add_3.c >> =================================================================== >> --- /dev/null 2017-11-14 14:28:07.424493901 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_loop_add_3.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -0,0 +1,20 @@ >> +/* { dg-do compile } */ >> +/* { dg-options "-std=c99 -O3 -march=armv8-a+sve" } */ >> + >> +void >> +f (int *__restrict a, >> + int *__restrict b, >> + int *__restrict c, >> + int *__restrict d, >> + int *__restrict e, >> + int *__restrict f, >> + int *__restrict g, >> + int *__restrict h, >> + int count) >> +{ >> + for (int i = 0; i < count; ++i) >> + a[i] = b[i] + c[i] + d[i] + e[i] + f[i] + g[i] + h[i]; >> +} >> + >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+.s, p[0-7]+/z, >> \[x[0-9]+, x[0-9]+, lsl 2\]\n} 7 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+.s, p[0-7]+, >> \[x[0-9]+, x[0-9]+, lsl 2\]\n} 1 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_while_1.c >> =================================================================== >> --- gcc/testsuite/gcc.target/aarch64/sve_while_1.c 2017-11-17 >> 14:54:06.035305786 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_while_1.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -34,3 +34,11 @@ TEST_ALL (ADD_LOOP) >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s, x[0-9]+,} 3 } } >> */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, xzr,} 3 } } */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, x[0-9]+,} 3 } } >> */ >> +/* { dg-final { scan-assembler-times {\tld1b\tz[0-9]+\.b, p[0-7]/z, \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1b\tz[0-9]+\.b, p[0-7], \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1h\tz[0-9]+\.h, p[0-7]/z, \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1h\tz[0-9]+\.h, p[0-7], \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+\.s, p[0-7]/z, \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+\.s, p[0-7], \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tld1d\tz[0-9]+\.d, p[0-7]/z, \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1d\tz[0-9]+\.d, p[0-7], \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_while_2.c >> =================================================================== >> --- gcc/testsuite/gcc.target/aarch64/sve_while_2.c 2017-11-17 >> 14:54:06.035305786 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_while_2.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -34,3 +34,11 @@ TEST_ALL (ADD_LOOP) >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s, x[0-9]+,} 3 } } >> */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, xzr,} 3 } } */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, x[0-9]+,} 3 } } >> */ >> +/* { dg-final { scan-assembler-times {\tld1b\tz[0-9]+\.b, p[0-7]/z, \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1b\tz[0-9]+\.b, p[0-7], \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1h\tz[0-9]+\.h, p[0-7]/z, \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1h\tz[0-9]+\.h, p[0-7], \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+\.s, p[0-7]/z, \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+\.s, p[0-7], \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tld1d\tz[0-9]+\.d, p[0-7]/z, \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1d\tz[0-9]+\.d, p[0-7], \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_while_3.c >> =================================================================== >> --- gcc/testsuite/gcc.target/aarch64/sve_while_3.c 2017-11-17 >> 14:54:06.035305786 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_while_3.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -34,3 +34,11 @@ TEST_ALL (ADD_LOOP) >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s, x[0-9]+,} 3 } } >> */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, xzr,} 3 } } */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, x[0-9]+,} 3 } } >> */ >> +/* { dg-final { scan-assembler-times {\tld1b\tz[0-9]+\.b, p[0-7]/z, \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1b\tz[0-9]+\.b, p[0-7], \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1h\tz[0-9]+\.h, p[0-7]/z, \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1h\tz[0-9]+\.h, p[0-7], \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+\.s, p[0-7]/z, \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+\.s, p[0-7], \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tld1d\tz[0-9]+\.d, p[0-7]/z, \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1d\tz[0-9]+\.d, p[0-7], \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> Index: gcc/testsuite/gcc.target/aarch64/sve_while_4.c >> =================================================================== >> --- gcc/testsuite/gcc.target/aarch64/sve_while_4.c 2017-11-17 >> 14:54:06.035305786 +0000 >> +++ gcc/testsuite/gcc.target/aarch64/sve_while_4.c 2017-11-17 >> 15:02:12.869952359 +0000 >> @@ -35,3 +35,11 @@ TEST_ALL (ADD_LOOP) >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.s, x[0-9]+,} 3 } } >> */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, xzr,} 3 } } */ >> /* { dg-final { scan-assembler-times {\twhilelo\tp[0-7]\.d, x[0-9]+,} 3 } } >> */ >> +/* { dg-final { scan-assembler-times {\tld1b\tz[0-9]+\.b, p[0-7]/z, \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1b\tz[0-9]+\.b, p[0-7], \[x0, >> x[0-9]+\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1h\tz[0-9]+\.h, p[0-7]/z, \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tst1h\tz[0-9]+\.h, p[0-7], \[x0, >> x[0-9]+, lsl 1\]\n} 2 } } */ >> +/* { dg-final { scan-assembler-times {\tld1w\tz[0-9]+\.s, p[0-7]/z, \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1w\tz[0-9]+\.s, p[0-7], \[x0, >> x[0-9]+, lsl 2\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tld1d\tz[0-9]+\.d, p[0-7]/z, \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */ >> +/* { dg-final { scan-assembler-times {\tst1d\tz[0-9]+\.d, p[0-7], \[x0, >> x[0-9]+, lsl 3\]\n} 3 } } */