This implements error handling for hard register constraints including potential conflicts with register asm operands.
In contrast to register asm operands, hard register constraints allow more than just one register per operand. Even more than just one register per alternative. For example, a valid constraint for an operand is "{r0}{r1}m,{r2}". However, this also means that we have to make sure that each register is used at most once in each alternative over all outputs and likewise over all inputs. For asm statements this is done by this patch during gimplification. For hard register constraints used in machine description, error handling is still a todo and I haven't investigated this so far and consider this rather a low priority. gcc/ada/ChangeLog: * gcc-interface/trans.cc (gnat_to_gnu): Pass null pointer to parse_{input,output}_constraint(). gcc/analyzer/ChangeLog: * region-model-asm.cc (region_model::on_asm_stmt): Pass null pointer to parse_{input,output}_constraint(). gcc/c/ChangeLog: * c-typeck.cc (build_asm_expr): Pass null pointer to parse_{input,output}_constraint(). gcc/ChangeLog: * cfgexpand.cc (n_occurrences): Move this ... (check_operand_nalternatives): and this ... (expand_asm_stmt): and the call to gimplify.cc. * config/s390/s390.cc (s390_md_asm_adjust): Pass null pointer to parse_{input,output}_constraint(). * gimple-walk.cc (walk_gimple_asm): Pass null pointer to parse_{input,output}_constraint(). (walk_stmt_load_store_addr_ops): Ditto. * gimplify-me.cc (gimple_regimplify_operands): Ditto. * gimplify.cc (num_occurrences): Moved from cfgexpand.cc. (num_alternatives): Ditto. (gimplify_asm_expr): Deal with hard register constraints. * stmt.cc (eliminable_regno_p): New helper. (hardreg_ok_p): Perform a similar check as done in make_decl_rtl(). (parse_output_constraint): Add parameter for gimplify_reg_info and validate hard register constrained operands. (parse_input_constraint): Ditto. * stmt.h (class gimplify_reg_info): Forward declaration. (parse_output_constraint): Add parameter. (parse_input_constraint): Ditto. * tree-ssa-operands.cc (operands_scanner::get_asm_stmt_operands): Pass null pointer to parse_{input,output}_constraint(). * tree-ssa-structalias.cc (find_func_aliases): Pass null pointer to parse_{input,output}_constraint(). * varasm.cc (assemble_asm): Pass null pointer to parse_{input,output}_constraint(). * gimplify_reg_info.h: New file. gcc/cp/ChangeLog: * semantics.cc (finish_asm_stmt): Pass null pointer to parse_{input,output}_constraint(). gcc/d/ChangeLog: * toir.cc: Pass null pointer to parse_{input,output}_constraint(). gcc/testsuite/ChangeLog: * gcc.dg/pr87600-2.c: Split test into two files since errors for functions test{0,1} are thrown during expand, and for test{2,3} during gimplification. * lib/scanasm.exp: On s390, skip lines beginning with #. * gcc.dg/asm-hard-reg-error-1.c: New test. * gcc.dg/asm-hard-reg-error-2.c: New test. * gcc.dg/asm-hard-reg-error-3.c: New test. * gcc.dg/asm-hard-reg-error-4.c: New test. * gcc.dg/asm-hard-reg-error-5.c: New test. * gcc.dg/pr87600-3.c: New test. * gcc.target/aarch64/asm-hard-reg-2.c: New test. * gcc.target/s390/asm-hard-reg-7.c: New test. --- gcc/ada/gcc-interface/trans.cc | 9 +- gcc/analyzer/region-model-asm.cc | 7 +- gcc/c/c-typeck.cc | 6 +- gcc/cfgexpand.cc | 53 +--- gcc/config/s390/s390.cc | 5 +- gcc/cp/semantics.cc | 6 +- gcc/d/toir.cc | 6 +- gcc/gimple-walk.cc | 11 +- gcc/gimplify-me.cc | 5 +- gcc/gimplify.cc | 142 +++++++++-- gcc/gimplify_reg_info.h | 182 ++++++++++++++ gcc/stmt.cc | 238 +++++++++++++++++- gcc/stmt.h | 8 +- gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c | 83 ++++++ gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c | 26 ++ gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c | 27 ++ gcc/testsuite/gcc.dg/asm-hard-reg-error-4.c | 21 ++ gcc/testsuite/gcc.dg/asm-hard-reg-error-5.c | 13 + gcc/testsuite/gcc.dg/pr87600-2.c | 19 -- gcc/testsuite/gcc.dg/pr87600-3.c | 26 ++ .../gcc.target/aarch64/asm-hard-reg-2.c | 17 ++ .../gcc.target/s390/asm-hard-reg-7.c | 34 +++ gcc/testsuite/lib/scanasm.exp | 4 + gcc/tree-ssa-operands.cc | 4 +- gcc/tree-ssa-structalias.cc | 4 +- gcc/varasm.cc | 5 +- 26 files changed, 843 insertions(+), 118 deletions(-) create mode 100644 gcc/gimplify_reg_info.h create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-4.c create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-5.c create mode 100644 gcc/testsuite/gcc.dg/pr87600-3.c create mode 100644 gcc/testsuite/gcc.target/aarch64/asm-hard-reg-2.c create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-7.c diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc index 23fc814f9de..891e6c3f0b4 100644 --- a/gcc/ada/gcc-interface/trans.cc +++ b/gcc/ada/gcc-interface/trans.cc @@ -8447,7 +8447,8 @@ gnat_to_gnu (Node_Id gnat_node) oconstraints[i] = constraint; if (parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &fake)) + &allows_mem, &allows_reg, &fake, + nullptr)) { /* If the operand is going to end up in memory, mark it addressable. Note that we don't test @@ -8475,9 +8476,9 @@ gnat_to_gnu (Node_Id gnat_node) constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail))); - if (parse_input_constraint (&constraint, i, ninputs, noutputs, - 0, oconstraints, - &allows_mem, &allows_reg)) + if (parse_input_constraint (&constraint, i, ninputs, noutputs, 0, + oconstraints, &allows_mem, + &allows_reg, nullptr)) { /* If the operand is going to end up in memory, mark it addressable. */ diff --git a/gcc/analyzer/region-model-asm.cc b/gcc/analyzer/region-model-asm.cc index 7d7e3b9258d..030fe4e2105 100644 --- a/gcc/analyzer/region-model-asm.cc +++ b/gcc/analyzer/region-model-asm.cc @@ -171,7 +171,8 @@ region_model::on_asm_stmt (const gasm *stmt, region_model_context *ctxt) no point in going further. */ constraint = constraints[i]; if (!parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) { if (logger) logger->log ("error parsing constraint for output %i: %qs", @@ -204,8 +205,8 @@ region_model::on_asm_stmt (const gasm *stmt, region_model_context *ctxt) const char *constraint = constraints[i + noutputs]; bool allows_reg, allows_mem; if (! parse_input_constraint (&constraint, i, ninputs, noutputs, 0, - constraints.address (), - &allows_mem, &allows_reg)) + constraints.address (), &allows_mem, + &allows_reg, nullptr)) { if (logger) logger->log ("error parsing constraint for input %i: %qs", diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index e24629be918..b7fa5b98805 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -12615,7 +12615,8 @@ build_asm_expr (location_t loc, tree string, tree outputs, tree inputs, oconstraints[i] = constraint; if (parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) { /* If the operand is going to end up in memory, mark it addressable. */ @@ -12676,7 +12677,8 @@ build_asm_expr (location_t loc, tree string, tree outputs, tree inputs, input = TREE_VALUE (tail); if (parse_input_constraint (&constraint, i, ninputs, noutputs, 0, - oconstraints, &allows_mem, &allows_reg)) + oconstraints, &allows_mem, &allows_reg, + nullptr)) { /* If the operand is going to end up in memory, mark it addressable. */ diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc index 33649d43f71..a8a9f9137a1 100644 --- a/gcc/cfgexpand.cc +++ b/gcc/cfgexpand.cc @@ -3275,44 +3275,6 @@ expand_asm_loc (tree string, int vol, location_t locus) emit_insn (body); } -/* Return the number of times character C occurs in string S. */ -static int -n_occurrences (int c, const char *s) -{ - int n = 0; - while (*s) - n += (*s++ == c); - return n; -} - -/* A subroutine of expand_asm_operands. Check that all operands have - the same number of alternatives. Return true if so. */ - -static bool -check_operand_nalternatives (const vec<const char *> &constraints) -{ - unsigned len = constraints.length(); - if (len > 0) - { - int nalternatives = n_occurrences (',', constraints[0]); - - if (nalternatives + 1 > MAX_RECOG_ALTERNATIVES) - { - error ("too many alternatives in %<asm%>"); - return false; - } - - for (unsigned i = 1; i < len; ++i) - if (n_occurrences (',', constraints[i]) != nalternatives) - { - error ("operand constraints for %<asm%> differ " - "in number of alternatives"); - return false; - } - } - return true; -} - /* Check for overlap between registers marked in CLOBBERED_REGS and anything inappropriate in T. Emit error and return the register variable definition for error, NULL_TREE for ok. */ @@ -3478,10 +3440,6 @@ expand_asm_stmt (gasm *stmt) = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); } - /* ??? Diagnose during gimplification? */ - if (! check_operand_nalternatives (constraints)) - return; - /* Count the number of meaningful clobbered registers, ignoring what we would ignore later. */ auto_vec<rtx> clobber_rvec; @@ -3555,7 +3513,8 @@ expand_asm_stmt (gasm *stmt) no point in going further. */ constraint = constraints[i]; if (!parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) return; /* If the output is a hard register, verify it doesn't conflict with @@ -3633,8 +3592,8 @@ expand_asm_stmt (gasm *stmt) constraint = constraints[i + noutputs]; if (! parse_input_constraint (&constraint, i, ninputs, noutputs, 0, - constraints.address (), - &allows_mem, &allows_reg)) + constraints.address (), &allows_mem, + &allows_reg, nullptr)) return; if (! allows_reg && allows_mem) @@ -3664,7 +3623,7 @@ expand_asm_stmt (gasm *stmt) ok = parse_output_constraint (&constraints[i], i, ninputs, noutputs, &allows_mem, &allows_reg, - &is_inout); + &is_inout, nullptr); gcc_assert (ok); /* If an output operand is not a decl or indirect ref and our constraint @@ -3769,7 +3728,7 @@ expand_asm_stmt (gasm *stmt) constraint = constraints[i + noutputs]; ok = parse_input_constraint (&constraint, i, ninputs, noutputs, 0, constraints.address (), - &allows_mem, &allows_reg); + &allows_mem, &allows_reg, nullptr); gcc_assert (ok); /* EXPAND_INITIALIZER will not generate code for valid initializer diff --git a/gcc/config/s390/s390.cc b/gcc/config/s390/s390.cc index 19564947ee8..21c0f252acf 100644 --- a/gcc/config/s390/s390.cc +++ b/gcc/config/s390/s390.cc @@ -17926,7 +17926,8 @@ s390_md_asm_adjust (vec<rtx> &outputs, vec<rtx> &inputs, continue; bool allows_mem, allows_reg, is_inout; bool ok = parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout); + &allows_mem, &allows_reg, &is_inout, + nullptr); gcc_assert (ok); if (!f_constraint_p (constraint)) /* Long double with a constraint other than "=f" - nothing to do. */ @@ -17971,7 +17972,7 @@ s390_md_asm_adjust (vec<rtx> &outputs, vec<rtx> &inputs, bool allows_mem, allows_reg; bool ok = parse_input_constraint (&constraint, i, ninputs, noutputs, 0, constraints.address (), &allows_mem, - &allows_reg); + &allows_reg, nullptr); gcc_assert (ok); if (!f_constraint_p (constraint)) /* Long double with a constraint other than "f" (or "=f" for inout diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 7bc346b0f29..7d39cebe17d 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -2338,7 +2338,8 @@ finish_asm_stmt (location_t loc, int volatile_p, tree string, oconstraints[i] = constraint; if (parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) { /* If the operand is going to end up in memory, mark it addressable. */ @@ -2397,7 +2398,8 @@ finish_asm_stmt (location_t loc, int volatile_p, tree string, constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); bool constraint_parsed = parse_input_constraint (&constraint, i, ninputs, noutputs, 0, - oconstraints, &allows_mem, &allows_reg); + oconstraints, &allows_mem, &allows_reg, + nullptr); /* If the operand is going to end up in memory, don't call decay_conversion. */ if (constraint_parsed && !allows_reg && allows_mem) diff --git a/gcc/d/toir.cc b/gcc/d/toir.cc index b70db7a0e50..554399b2b0c 100644 --- a/gcc/d/toir.cc +++ b/gcc/d/toir.cc @@ -1450,7 +1450,8 @@ public: oconstraints[i] = constraint; if (parse_output_constraint (&constraint, i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) { /* If the output argument is going to end up in memory. */ if (!allows_reg) @@ -1469,7 +1470,8 @@ public: = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); if (parse_input_constraint (&constraint, i, ninputs, noutputs, 0, - oconstraints, &allows_mem, &allows_reg)) + oconstraints, &allows_mem, &allows_reg, + nullptr)) { /* If the input argument is going to end up in memory. */ if (!allows_reg && allows_mem) diff --git a/gcc/gimple-walk.cc b/gcc/gimple-walk.cc index eceda41778d..b8b107789bb 100644 --- a/gcc/gimple-walk.cc +++ b/gcc/gimple-walk.cc @@ -118,7 +118,7 @@ walk_gimple_asm (gasm *stmt, walk_tree_fn callback_op, if (wi) { if (parse_output_constraint (&constraint, i, 0, 0, &allows_mem, - &allows_reg, &is_inout)) + &allows_reg, &is_inout, nullptr)) wi->val_only = (allows_reg || !allows_mem); } if (wi) @@ -137,7 +137,8 @@ walk_gimple_asm (gasm *stmt, walk_tree_fn callback_op, if (wi) { if (parse_input_constraint (&constraint, 0, 0, noutputs, 0, - oconstraints, &allows_mem, &allows_reg)) + oconstraints, &allows_mem, &allows_reg, + nullptr)) { wi->val_only = (allows_reg || !allows_mem); /* Although input "m" is not really a LHS, we need a lvalue. */ @@ -897,7 +898,7 @@ walk_stmt_load_store_addr_ops (gimple *stmt, void *data, (TREE_VALUE (TREE_PURPOSE (link))); oconstraints[i] = constraint; parse_output_constraint (&constraint, i, 0, 0, &allows_mem, - &allows_reg, &is_inout); + &allows_reg, &is_inout, nullptr); if (op && !allows_reg && allows_mem) ret |= visit_addr (stmt, op, TREE_VALUE (link), data); } @@ -922,8 +923,8 @@ walk_stmt_load_store_addr_ops (gimple *stmt, void *data, constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); parse_input_constraint (&constraint, 0, 0, noutputs, - 0, oconstraints, - &allows_mem, &allows_reg); + 0, oconstraints, &allows_mem, + &allows_reg, nullptr); if (!allows_reg && allows_mem) ret |= visit_addr (stmt, op, TREE_VALUE (link), data); diff --git a/gcc/gimplify-me.cc b/gcc/gimplify-me.cc index 740782281ef..5b4b9876373 100644 --- a/gcc/gimplify-me.cc +++ b/gcc/gimplify-me.cc @@ -194,7 +194,7 @@ gimple_regimplify_operands (gimple *stmt, gimple_stmt_iterator *gsi_p) constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (op))); oconstraints[i] = constraint; parse_output_constraint (&constraint, i, 0, 0, &allows_mem, - &allows_reg, &is_inout); + &allows_reg, &is_inout, nullptr); gimplify_expr (&TREE_VALUE (op), &pre, NULL, is_inout ? is_gimple_min_lval : is_gimple_lvalue, fb_lvalue | fb_mayfail); @@ -204,7 +204,8 @@ gimple_regimplify_operands (gimple *stmt, gimple_stmt_iterator *gsi_p) tree op = gimple_asm_input_op (asm_stmt, i); constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (op))); parse_input_constraint (&constraint, 0, 0, noutputs, 0, - oconstraints, &allows_mem, &allows_reg); + oconstraints, &allows_mem, &allows_reg, + nullptr); if (TREE_ADDRESSABLE (TREE_TYPE (TREE_VALUE (op))) && allows_mem) allows_reg = 0; if (!allows_reg && allows_mem) diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc index 9f9ff92d064..d39d11811dc 100644 --- a/gcc/gimplify.cc +++ b/gcc/gimplify.cc @@ -71,6 +71,10 @@ along with GCC; see the file COPYING3. If not see #include "context.h" #include "tree-nested.h" #include "gcc-urlifier.h" +#include "insn-config.h" +#include "recog.h" +#include "output.h" +#include "gimplify_reg_info.h" /* Identifier for a basic condition, mapping it to other basic conditions of its Boolean expression. Basic conditions given the same uid (in the same @@ -7822,6 +7826,42 @@ gimplify_addr_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) return ret; } +/* Return the number of times character C occurs in string S. */ + +static int +num_occurrences (int c, const char *s) +{ + int n = 0; + while (*s) + n += (*s++ == c); + return n; +} + +/* A subroutine of gimplify_asm_expr. Check that all operands have + the same number of alternatives. Return -1 if this is violated. Otherwise + return the number of alternatives. */ + +static int +num_alternatives (const_tree link) +{ + if (link == nullptr) + return 0; + + const char *constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); + int num = num_occurrences (',', constraint); + + if (num + 1 > MAX_RECOG_ALTERNATIVES) + return -1; + + for (link = TREE_CHAIN (link); link; link = TREE_CHAIN (link)) + { + constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); + if (num_occurrences (',', constraint) != num) + return -1; + } + return num + 1; +} + /* Gimplify the operands of an ASM_EXPR. Input operands should be a gimple value; output operands should be a gimple lvalue. */ @@ -7852,6 +7892,36 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) clobbers = NULL; labels = NULL; + int num_alternatives_out = num_alternatives (ASM_OUTPUTS (expr)); + int num_alternatives_in = num_alternatives (ASM_INPUTS (expr)); + if (num_alternatives_out == -1 || num_alternatives_in == -1 + || (num_alternatives_out > 0 && num_alternatives_in > 0 + && num_alternatives_out != num_alternatives_in)) + { + error ("operand constraints for %<asm%> differ " + "in number of alternatives"); + return GS_ERROR; + } + int num_alternatives = MAX (num_alternatives_out, num_alternatives_in); + + gimplify_reg_info reg_info (num_alternatives, noutputs); + + link_next = NULL_TREE; + for (link = ASM_CLOBBERS (expr); link; link = link_next) + { + /* The clobber entry could also be an error marker. */ + if (TREE_CODE (TREE_VALUE (link)) == STRING_CST) + { + const char *regname= TREE_STRING_POINTER (TREE_VALUE (link)); + int regno = decode_reg_name (regname); + if (regno >= 0) + reg_info.set_clobbered (regno); + } + link_next = TREE_CHAIN (link); + TREE_CHAIN (link) = NULL_TREE; + vec_safe_push (clobbers, link); + } + ret = GS_ALL_DONE; link_next = NULL_TREE; for (i = 0, link = ASM_OUTPUTS (expr); link; ++i, link = link_next) @@ -7868,8 +7938,9 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) if (constraint_len == 0) continue; - ok = parse_output_constraint (&constraint, i, 0, 0, - &allows_mem, &allows_reg, &is_inout); + reg_info.operand = TREE_VALUE (link); + ok = parse_output_constraint (&constraint, i, 0, 0, &allows_mem, + &allows_reg, &is_inout, ®_info); if (!ok) { ret = GS_ERROR; @@ -8000,8 +8071,8 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) *end = '\0'; beg[-1] = '='; tem = beg - 1; - parse_output_constraint (&tem, i, 0, 0, - &mem_p, ®_p, &inout_p); + parse_output_constraint (&tem, i, 0, 0, &mem_p, ®_p, + &inout_p, nullptr); if (dst != str) *dst++ = ','; if (reg_p) @@ -8040,13 +8111,60 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) } } + /* After all output operands have been gimplified, verify that each output + operand is used at most once in case of hard register constraints. Thus, + error out in cases like + asm ("" : "={0}" (x), "={1}" (x)); + or even for + asm ("" : "=r" (x), "={1}" (x)); + + FIXME: Ideally we would also error out for cases like + int x; + asm ("" : "=r" (x), "=r" (x)); + However, since code like that was previously accepted, erroring out now might + break existing code. On the other hand, we already error out for register + asm like + register int x asm ("0"); + asm ("" : "=r" (x), "=r" (x)); + Thus, maybe it wouldn't be too bad to also error out in the former + non-register-asm case. + */ + for (unsigned i = 0; i < vec_safe_length (outputs); ++i) + { + tree link = (*outputs)[i]; + tree op1 = TREE_VALUE (link); + const char *constraint + = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); + if (strchr (constraint, '{') != nullptr) + for (unsigned j = 0; j < vec_safe_length (outputs); ++j) + { + if (i == j) + continue; + tree link2 = (*outputs)[j]; + tree op2 = TREE_VALUE (link2); + if (op1 == op2) + { + error ("multiple outputs to lvalue %qE", op2); + return GS_ERROR; + } + } + } + link_next = NULL_TREE; - for (link = ASM_INPUTS (expr); link; ++i, link = link_next) + int input_num = 0; + for (link = ASM_INPUTS (expr); link; ++input_num, ++i, link = link_next) { link_next = TREE_CHAIN (link); constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); - parse_input_constraint (&constraint, 0, 0, noutputs, 0, - oconstraints, &allows_mem, &allows_reg); + reg_info.operand = TREE_VALUE (link); + bool ok = parse_input_constraint (&constraint, input_num, 0, noutputs, 0, + oconstraints, &allows_mem, &allows_reg, + ®_info); + if (!ok) + { + ret = GS_ERROR; + is_inout = false; + } /* If we can't make copies, we can only accept memory. */ tree intype = TREE_TYPE (TREE_VALUE (link)); @@ -8128,15 +8246,7 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) } link_next = NULL_TREE; - for (link = ASM_CLOBBERS (expr); link; ++i, link = link_next) - { - link_next = TREE_CHAIN (link); - TREE_CHAIN (link) = NULL_TREE; - vec_safe_push (clobbers, link); - } - - link_next = NULL_TREE; - for (link = ASM_LABELS (expr); link; ++i, link = link_next) + for (link = ASM_LABELS (expr); link; link = link_next) { link_next = TREE_CHAIN (link); TREE_CHAIN (link) = NULL_TREE; diff --git a/gcc/gimplify_reg_info.h b/gcc/gimplify_reg_info.h new file mode 100644 index 00000000000..b56b225ac01 --- /dev/null +++ b/gcc/gimplify_reg_info.h @@ -0,0 +1,182 @@ +/* gimplify_reg_info is used during gimplification while walking over + operands and their corresponding constraints of asm statements in order to + detect errors. + + m_alt_output is a mapping describing which registers are potentially used in + which alternative over all outputs. Similarly for m_alt_input but over all + inputs. + + m_early_clobbered_alt is a mapping describing which register is early + clobbered in which alternative over all outputs. + + m_early_clobbered_output is the counter part to the prior one, i.e., it + is a mapping describing which register is early clobbered in which operand + over all alternatives. + + m_reg_asm_output is the set of registers (including register pairs) used for + register asm output operands. + + m_reg_asm_input similar as m_reg_asm_output but for inputs. */ + +#include "regs.h" + +class gimplify_reg_info +{ + HARD_REG_SET *m_buf; + HARD_REG_SET *m_alt_output; + HARD_REG_SET *m_alt_input; + HARD_REG_SET *m_early_clobbered_alt; + HARD_REG_SET *m_early_clobbered_output; + HARD_REG_SET m_reg_asm_output; + HARD_REG_SET m_reg_asm_input; + const unsigned m_num_alternatives; + const unsigned m_num_outputs; + /* Member m_clobbered describes all the registers marked as clobbered in an + asm statement, i.e., this is the clobbers list of an extended asm + + asm asm-qualifiers ( AssemblerTemplate + : OutputOperands + [ : InputOperands + [ : Clobbers ] ]) + + and is not to be confused with the early clobbers sets. */ + HARD_REG_SET m_clobbered; + + /* Return the first overlapping register of REGS and REGNO:MODE or -1. */ + int test (const HARD_REG_SET ®s, int regno) const + { + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + + if (TEST_HARD_REG_BIT (regs, regno)) + return regno; + + int end_regno = end_hard_regno (mode, regno); + while (++regno < end_regno) + if (TEST_HARD_REG_BIT (regs, regno)) + return regno; + + return -1; + } + +public: + tree operand; + + gimplify_reg_info (unsigned num_alternatives, + unsigned num_outputs) + : m_num_alternatives{num_alternatives} + , m_num_outputs{num_outputs} + { + CLEAR_HARD_REG_SET (m_reg_asm_output); + CLEAR_HARD_REG_SET (m_reg_asm_input); + CLEAR_HARD_REG_SET (m_clobbered); + + /* If there are no alternatives, then there are no outputs/inputs and there + is nothing to do on our end. Thus, we are dealing most likely with a + basic asm. */ + if (num_alternatives == 0) + return; + + unsigned buf_size = num_alternatives * 3 + num_outputs; + m_buf = new HARD_REG_SET[buf_size]; + for (unsigned i = 0; i < buf_size; ++i) + CLEAR_HARD_REG_SET (m_buf[i]); + m_alt_output = &m_buf[0]; + m_alt_input = &m_buf[num_alternatives]; + m_early_clobbered_alt = &m_buf[num_alternatives * 2]; + if (num_outputs > 0) + m_early_clobbered_output = &m_buf[num_alternatives * 3]; + else + m_early_clobbered_output = nullptr; + } + + ~gimplify_reg_info () + { + if (m_num_alternatives > 0) + delete[] m_buf; + } + + void set_output (unsigned alt, int regno) + { + gcc_checking_assert (alt < m_num_alternatives); + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + add_to_hard_reg_set (&m_alt_output[alt], mode, regno); + } + + void set_input (unsigned alt, int regno) + { + gcc_checking_assert (alt < m_num_alternatives); + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + add_to_hard_reg_set (&m_alt_input[alt], mode, regno); + } + + int test_alt_output (unsigned alt, int regno) const + { + gcc_checking_assert (alt < m_num_alternatives); + return test (m_alt_output[alt], regno); + } + + int test_alt_input (unsigned alt, int regno) const + { + gcc_checking_assert (alt < m_num_alternatives); + return test (m_alt_input[alt], regno); + } + + void set_reg_asm_output (int regno) + { + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + add_to_hard_reg_set (&m_reg_asm_output, mode, regno); + } + + int test_reg_asm_output (int regno) const + { + return test (m_reg_asm_output, regno); + } + + void set_reg_asm_input (int regno) + { + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + add_to_hard_reg_set (&m_reg_asm_input, mode, regno); + } + + int test_reg_asm_input (int regno) const + { + return test (m_reg_asm_input, regno); + } + + void set_early_clobbered (unsigned alt, unsigned output, int regno) + { + gcc_checking_assert (alt < m_num_alternatives); + gcc_checking_assert (output < m_num_outputs); + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + add_to_hard_reg_set (&m_early_clobbered_alt[alt], mode, regno); + add_to_hard_reg_set (&m_early_clobbered_output[output], mode, regno); + } + + bool test_early_clobbered_alt (unsigned alt, int regno) const + { + gcc_checking_assert (alt < m_num_alternatives); + return TEST_HARD_REG_BIT (m_early_clobbered_alt[alt], regno); + } + + bool is_early_clobbered_in_any_output_unequal (unsigned operand, + int regno) const + { + gcc_checking_assert (operand < m_num_outputs); + for (unsigned op = 0; op < m_num_outputs; ++op) + if (op != operand + && TEST_HARD_REG_BIT (m_early_clobbered_output[op], regno)) + return true; + return false; + } + + void set_clobbered (int regno) + { + SET_HARD_REG_BIT (m_clobbered, regno); + } + + bool is_clobbered (int regno) const + { + machine_mode mode = TYPE_MODE (TREE_TYPE (operand)); + return overlaps_hard_reg_set_p (m_clobbered, mode, regno); + } +}; diff --git a/gcc/stmt.cc b/gcc/stmt.cc index 58aa8edd5f3..7942aa3e484 100644 --- a/gcc/stmt.cc +++ b/gcc/stmt.cc @@ -39,6 +39,8 @@ along with GCC; see the file COPYING3. If not see #include "emit-rtl.h" #include "pretty-print.h" #include "diagnostic-core.h" +#include "output.h" +#include "gimplify_reg_info.h" #include "fold-const.h" #include "varasm.h" @@ -202,6 +204,51 @@ decode_hard_reg_constraint (const char *begin) return regno; } +static bool +eliminable_regno_p (int regnum) +{ + static const struct + { + const int from; + const int to; + } eliminables[] = ELIMINABLE_REGS; + for (size_t i = 0; i < ARRAY_SIZE (eliminables); i++) + if (regnum == eliminables[i].from) + return true; + return false; +} + +/* Perform a similar check as done in make_decl_rtl(). */ + +static bool +hardreg_ok_p (int reg_number, machine_mode mode, int operand_num) +{ + if (mode == BLKmode) + error ("data type isn%'t suitable for register %s of operand %i", + reg_names[reg_number], operand_num); + else if (!in_hard_reg_set_p (accessible_reg_set, mode, reg_number)) + error ("register %s for operand %i cannot be accessed" + " by the current target", reg_names[reg_number], operand_num); + else if (!in_hard_reg_set_p (operand_reg_set, mode, reg_number)) + error ("register %s for operand %i is not general enough" + " to be used as a register variable", reg_names[reg_number], operand_num); + else if (!targetm.hard_regno_mode_ok (reg_number, mode)) + error ("register %s for operand %i isn%'t suitable for data type", + reg_names[reg_number], operand_num); + else if (reg_number != HARD_FRAME_POINTER_REGNUM + && (reg_number == FRAME_POINTER_REGNUM +#ifdef RETURN_ADDRESS_POINTER_REGNUM + || reg_number == RETURN_ADDRESS_POINTER_REGNUM +#endif + || reg_number == ARG_POINTER_REGNUM) + && eliminable_regno_p (reg_number)) + error ("register for operand %i is an internal GCC " + "implementation detail", operand_num); + else + return true; + return false; +} + /* Parse the output constraint pointed to by *CONSTRAINT_P. It is the OPERAND_NUMth output operand, indexed from zero. There are NINPUTS inputs and NOUTPUTS outputs to this extended-asm. Upon return, @@ -218,7 +265,8 @@ decode_hard_reg_constraint (const char *begin) bool parse_output_constraint (const char **constraint_p, int operand_num, int ninputs, int noutputs, bool *allows_mem, - bool *allows_reg, bool *is_inout) + bool *allows_reg, bool *is_inout, + gimplify_reg_info *reg_info) { const char *constraint = *constraint_p; const char *p; @@ -272,6 +320,9 @@ parse_output_constraint (const char **constraint_p, int operand_num, constraint = *constraint_p; } + unsigned int alt = 0; + bool early_clobbered = false; + /* Loop through the constraint string. */ for (p = constraint + 1; *p; ) { @@ -291,12 +342,21 @@ parse_output_constraint (const char **constraint_p, int operand_num, } break; - case '?': case '!': case '*': case '&': case '#': + case '&': + early_clobbered = true; + break; + + case '?': case '!': case '*': case '#': case '$': case '^': case 'E': case 'F': case 'G': case 'H': case 's': case 'i': case 'n': case 'I': case 'J': case 'K': case 'L': case 'M': - case 'N': case 'O': case 'P': case ',': case '-': + case 'N': case 'O': case 'P': case '-': + break; + + case ',': + ++alt; + early_clobbered = false; break; case '0': case '1': case '2': case '3': case '4': @@ -323,6 +383,54 @@ parse_output_constraint (const char **constraint_p, int operand_num, case '{': { + if (!targetm.lra_p ()) + { + error ("hard register constraints are only supported while using LRA"); + return false; + } + if (reg_info) + { + int regno = decode_hard_reg_constraint (p); + if (regno < 0) + { + error ("invalid output constraint: %s", p); + return false; + } + int overlap_regno = reg_info->test_alt_output (alt, regno); + if (overlap_regno < 0) + overlap_regno = reg_info->test_reg_asm_output (regno); + if (overlap_regno >= 0) + { + error ("multiple outputs to hard register: %s", + reg_names[overlap_regno]); + return false; + } + reg_info->set_output (alt, regno); + if (early_clobbered) + reg_info->set_early_clobbered (alt, operand_num, regno); + if (reg_info->is_clobbered (regno)) + { + error ("hard register constraint for output %i conflicts " + "with %<asm%> clobber list", operand_num); + return false; + } + if (VAR_P (reg_info->operand) + && DECL_HARD_REGISTER (reg_info->operand)) + { + tree id = DECL_ASSEMBLER_NAME (reg_info->operand); + const char *asmspec = IDENTIFIER_POINTER (id) + 1; + int regno_op = decode_reg_name (asmspec); + if (regno != regno_op) + { + error ("constraint and register %<asm%> for output " + "operand %i are unsatisfiable", operand_num); + return false; + } + } + machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand)); + if (!hardreg_ok_p (regno, mode, operand_num)) + return false; + } *allows_reg = true; break; } @@ -338,6 +446,31 @@ parse_output_constraint (const char **constraint_p, int operand_num, *allows_mem = true; else insn_extra_constraint_allows_reg_mem (cn, allows_reg, allows_mem); + if (reg_info && *allows_reg + && VAR_P (reg_info->operand) + && DECL_HARD_REGISTER (reg_info->operand)) + { + tree id = DECL_ASSEMBLER_NAME (reg_info->operand); + const char *asmspec = IDENTIFIER_POINTER (id) + 1; + int regno = decode_reg_name (asmspec); + if (regno < 0) + { + location_t loc = DECL_SOURCE_LOCATION (reg_info->operand); + error_at (loc, "invalid register name for %q+D", + reg_info->operand); + return false; + } + int overlap_regno = reg_info->test_alt_output (alt, regno); + if (overlap_regno >= 0) + { + error ("multiple outputs to hard register: %s", + reg_names[overlap_regno]); + return false; + } + reg_info->set_reg_asm_output (regno); + if (early_clobbered) + reg_info->set_early_clobbered (alt, operand_num, regno); + } break; } @@ -355,7 +488,8 @@ bool parse_input_constraint (const char **constraint_p, int input_num, int ninputs, int noutputs, int ninout, const char * const * constraints, - bool *allows_mem, bool *allows_reg) + bool *allows_mem, bool *allows_reg, + gimplify_reg_info *reg_info) { const char *constraint = *constraint_p; const char *orig_constraint = constraint; @@ -371,6 +505,9 @@ parse_input_constraint (const char **constraint_p, int input_num, /* Make sure constraint has neither `=', `+', nor '&'. */ + unsigned int alt = 0; + unsigned long match = 0; + for (j = 0; j < c_len; j += CONSTRAINT_LEN (constraint[j], constraint+j)) switch (constraint[j]) { @@ -397,7 +534,7 @@ parse_input_constraint (const char **constraint_p, int input_num, case 'E': case 'F': case 'G': case 'H': case 's': case 'i': case 'n': case 'I': case 'J': case 'K': case 'L': case 'M': - case 'N': case 'O': case 'P': case ',': case '-': + case 'N': case 'O': case 'P': case '-': break; case ':': @@ -415,6 +552,10 @@ parse_input_constraint (const char **constraint_p, int input_num, } break; + case ',': + ++alt; + break; + /* Whether or not a numeric constraint allows a register is decided by the matching constraint, and so there is no need to do anything special with them. We must handle them in @@ -424,7 +565,6 @@ parse_input_constraint (const char **constraint_p, int input_num, case '5': case '6': case '7': case '8': case '9': { char *end; - unsigned long match; saw_match = true; @@ -464,6 +604,59 @@ parse_input_constraint (const char **constraint_p, int input_num, case '{': { + if (!targetm.lra_p ()) + { + error ("hard register constraints are only supported while using LRA"); + return false; + } + if (reg_info) + { + int regno = decode_hard_reg_constraint (constraint + j); + if (regno < 0) + { + error ("invalid input constraint: %s", constraint + j); + return false; + } + int overlap_regno = reg_info->test_alt_input (alt, regno); + if (overlap_regno < 0) + overlap_regno = reg_info->test_reg_asm_input (regno); + if (overlap_regno >= 0) + { + error ("multiple inputs to hard register: %s", + reg_names[overlap_regno]); + return false; + } + reg_info->set_input (alt, regno); + if (reg_info->is_clobbered (regno)) + { + error ("hard register constraint for input %i conflicts " + "with %<asm%> clobber list", input_num); + return false; + } + if (constraint == orig_constraint + && reg_info->test_early_clobbered_alt (alt, regno)) + { + error ("invalid hard register usage between earlyclobber " + "operand and input operand"); + return false; + } + if (VAR_P (reg_info->operand) + && DECL_HARD_REGISTER (reg_info->operand)) + { + tree id = DECL_ASSEMBLER_NAME (reg_info->operand); + const char *asmspec = IDENTIFIER_POINTER (id) + 1; + int regno_op = decode_reg_name (asmspec); + if (regno != regno_op) + { + error ("constraint and register %<asm%> for input " + "operand %i are unsatisfiable", input_num); + return false; + } + } + machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand)); + if (!hardreg_ok_p (regno, mode, input_num)) + return false; + } *allows_reg = true; break; } @@ -484,6 +677,39 @@ parse_input_constraint (const char **constraint_p, int input_num, *allows_mem = true; else insn_extra_constraint_allows_reg_mem (cn, allows_reg, allows_mem); + if (reg_info && *allows_reg + && VAR_P (reg_info->operand) + && DECL_HARD_REGISTER (reg_info->operand)) + { + tree id = DECL_ASSEMBLER_NAME (reg_info->operand); + const char *asmspec = IDENTIFIER_POINTER (id) + 1; + int regno = decode_reg_name (asmspec); + if (regno < 0) + { + location_t loc = DECL_SOURCE_LOCATION (reg_info->operand); + error_at (loc, "invalid register name for %q+D", + reg_info->operand); + return false; + } + int overlap_regno = reg_info->test_alt_input (alt, regno); + if (overlap_regno >= 0) + { + error ("multiple inputs to hard register: %s", + reg_names[overlap_regno]); + return false; + } + reg_info->set_reg_asm_input (regno); + if ((constraint == orig_constraint + && reg_info->test_early_clobbered_alt (alt, regno)) + || (constraint != orig_constraint + && reg_info->is_early_clobbered_in_any_output_unequal + (match, regno))) + { + error ("invalid hard register usage between earlyclobber " + "operand and input operand"); + return false; + } + } break; } diff --git a/gcc/stmt.h b/gcc/stmt.h index 1195a0bca62..c178036ba7f 100644 --- a/gcc/stmt.h +++ b/gcc/stmt.h @@ -20,11 +20,15 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_STMT_H #define GCC_STMT_H +class gimplify_reg_info; + extern void expand_label (tree); extern bool parse_output_constraint (const char **, int, int, int, - bool *, bool *, bool *); + bool *, bool *, bool *, + gimplify_reg_info *); extern bool parse_input_constraint (const char **, int, int, int, int, - const char * const *, bool *, bool *); + const char * const *, bool *, bool *, + gimplify_reg_info *); extern int decode_hard_reg_constraint (const char *); extern tree resolve_asm_operand_names (tree, tree, tree, tree); #ifdef HARD_CONST diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c new file mode 100644 index 00000000000..0d7c2f210d8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c @@ -0,0 +1,83 @@ +/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */ + +#if defined (__aarch64__) +# define GPR1_RAW "x0" +# define GPR2 "{x1}" +# define GPR3 "{x2}" +# define INVALID_GPR_A "{x31}" +#elif defined (__arm__) +# define GPR1_RAW "r0" +# define GPR2 "{r1}" +# define GPR3 "{r2}" +# define INVALID_GPR_A "{r16}" +#elif defined (__i386__) +# define GPR1_RAW "%eax" +# define GPR2 "{%ebx}" +# define GPR3 "{%edx}" +# define INVALID_GPR_A "{%eex}" +#elif defined (__powerpc__) || defined (__POWERPC__) +# define GPR1_RAW "r4" +# define GPR2 "{r5}" +# define GPR3 "{r6}" +# define INVALID_GPR_A "{r33}" +#elif defined (__riscv) +# define GPR1_RAW "t4" +# define GPR2 "{t5}" +# define GPR3 "{t6}" +# define INVALID_GPR_A "{t7}" +#elif defined (__s390__) +# define GPR1_RAW "r4" +# define GPR2 "{r5}" +# define GPR3 "{r6}" +# define INVALID_GPR_A "{r17}" +#elif defined (__x86_64__) +# define GPR1_RAW "rax" +# define GPR2 "{rbx}" +# define GPR3 "{rcx}" +# define INVALID_GPR_A "{rex}" +#endif + +#define GPR1 "{"GPR1_RAW"}" +#define INVALID_GPR_B "{"GPR1_RAW + +struct { int a[128]; } s = {0}; + +void +test (void) +{ + int x, y; + register int gpr1 __asm__ (GPR1_RAW) = 0; + + __asm__ ("" :: "{}" (42)); /* { dg-error "invalid input constraint: \{\}" } */ + __asm__ ("" :: INVALID_GPR_A (42)); /* { dg-error "invalid input constraint" } */ + __asm__ ("" :: INVALID_GPR_B (42)); /* { dg-error "invalid input constraint" } */ + + __asm__ ("" :: GPR1 (s)); /* { dg-error "data type isn't suitable for register .* of operand 0" } */ + + __asm__ ("" :: "r" (gpr1), GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" :: GPR1 (42), "r" (gpr1)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" :: GPR1 (42), GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" :: GPR1","GPR2 (42), GPR2","GPR3 (42)); + __asm__ ("" :: GPR1","GPR2 (42), GPR3","GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" :: GPR1","GPR2 (42), GPR1","GPR3 (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" :: GPR1 GPR2 (42), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" : "+"GPR1 (x), "="GPR1 (y)); /* { dg-error "multiple outputs to hard register" } */ + __asm__ ("" : "="GPR1 (y) : GPR1 (42), "0" (42)); /* { dg-error "multiple inputs to hard register" } */ + __asm__ ("" : "+"GPR1 (x) : GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */ + + __asm__ ("" : "="GPR1 (gpr1)); + __asm__ ("" : "="GPR2 (gpr1)); /* { dg-error "constraint and register 'asm' for output operand 0 are unsatisfiable" } */ + __asm__ ("" :: GPR2 (gpr1)); /* { dg-error "constraint and register 'asm' for input operand 0 are unsatisfiable" } */ + __asm__ ("" : "="GPR1 (x) : "0" (gpr1)); + __asm__ ("" : "="GPR1 GPR2 (x) : "0" (gpr1)); /* { dg-error "constraint and register 'asm' for input operand 0 are unsatisfiable" } */ + + __asm__ ("" : "=&"GPR1 (x) : "0" (gpr1)); + __asm__ ("" : "=&"GPR1 (x) : "0" (42)); + __asm__ ("" : "=&"GPR2","GPR1 (x) : "r,"GPR1 (42)); + __asm__ ("" : "="GPR2",&"GPR1 (x) : "r,"GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + __asm__ ("" : "=&"GPR1 (x) : GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + __asm__ ("" : "=&"GPR2","GPR1 (x) : "r,r" (gpr1)); + __asm__ ("" : "="GPR2",&"GPR1 (x) : "r,r" (gpr1)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + __asm__ ("" : "=&r" (gpr1) : GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + __asm__ ("" : "=&"GPR1 (x), "=r" (y) : "1" (gpr1)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ +} diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c new file mode 100644 index 00000000000..d0d5cfe14ea --- /dev/null +++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c @@ -0,0 +1,26 @@ +/* { dg-do compile { target { { aarch64*-*-* s390x-*-* } && int128 } } } */ +/* { dg-options "-O2" } get rid of -ansi since we use __int128 */ + +/* Test register pairs. */ + +#if defined (__aarch64__) +# define GPR1 "{x4}" +# define GPR2_RAW "x5" +#elif defined (__s390__) +# define GPR1 "{r4}" +# define GPR2_RAW "r5" +#endif + +#define GPR2 "{"GPR2_RAW"}" + +void +test (void) +{ + __asm__ ("" :: GPR1 ((__int128) 42)); + __asm__ ("" :: GPR2 ((__int128) 42)); /* { dg-error "register .* for operand 0 isn't suitable for data type" } */ + __asm__ ("" :: GPR1 ((__int128) 42), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */ + + __int128 x; + __asm__ ("" : "="GPR1 (x) :: GPR2_RAW); /* { dg-error "hard register constraint for output 0 conflicts with 'asm' clobber list" } */ + __asm__ ("" : "=r" (x) : GPR1 (x) : GPR2_RAW); /* { dg-error "hard register constraint for input 0 conflicts with 'asm' clobber list" } */ +} diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c new file mode 100644 index 00000000000..17b2317db9b --- /dev/null +++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c @@ -0,0 +1,27 @@ +/* { dg-do compile { target arm-*-* s390-*-* } } */ +/* { dg-options "-std=c99" } we need long long */ +/* { dg-additional-options "-march=armv7-a" { target arm-*-* } } */ + +/* Test register pairs. */ + +#if defined (__arm__) +# define GPR1 "{r4}" +# define GPR2_RAW "r5" +#elif defined (__s390__) +# define GPR1 "{r4}" +# define GPR2_RAW "r5" +#endif + +#define GPR2 "{"GPR2_RAW"}" + +void +test (void) +{ + __asm__ ("" :: GPR1 (42ll)); + __asm__ ("" :: GPR2 (42ll)); /* { dg-error "register .* for operand 0 isn't suitable for data type" } */ + __asm__ ("" :: GPR1 (42ll), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */ + + long long x; + __asm__ ("" : "="GPR1 (x) :: GPR2_RAW); /* { dg-error "hard register constraint for output 0 conflicts with 'asm' clobber list" } */ + __asm__ ("" : "=r" (x) : GPR1 (x) : GPR2_RAW); /* { dg-error "hard register constraint for input 0 conflicts with 'asm' clobber list" } */ +} diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-4.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-4.c new file mode 100644 index 00000000000..465f24b1f71 --- /dev/null +++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-4.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ + +/* Verify output operands. */ + +int +test (void) +{ + int x; + register int y __asm__ ("0"); + + /* Preserve status quo and don't error out. */ + __asm__ ("" : "=r" (x), "=r" (x)); + + /* Be more strict for hard register constraints and error out. */ + __asm__ ("" : "={0}" (x), "={1}" (x)); /* { dg-error "multiple outputs to lvalue 'x'" } */ + + /* Still error out in case of a mixture. */ + __asm__ ("" : "=r" (x), "={1}" (x)); /* { dg-error "multiple outputs to lvalue 'x'" } */ + + return x + y; +} diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-5.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-5.c new file mode 100644 index 00000000000..85398f04cc8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-5.c @@ -0,0 +1,13 @@ +/* { dg-do compile } */ + +/* Test clobbers. + See asm-hard-reg-error-{2,3}.c for tests involving register pairs. */ + +int +test (void) +{ + int x, y; + __asm__ ("" : "={0}" (x), "={1}" (y) : : "1"); /* { dg-error "hard register constraint for output 1 conflicts with 'asm' clobber list" } */ + __asm__ ("" : "={0}" (x) : "{0}" (y), "{1}" (y) : "1"); /* { dg-error "hard register constraint for input 1 conflicts with 'asm' clobber list" } */ + return x + y; +} diff --git a/gcc/testsuite/gcc.dg/pr87600-2.c b/gcc/testsuite/gcc.dg/pr87600-2.c index e8a9f194b73..16a7145cc98 100644 --- a/gcc/testsuite/gcc.dg/pr87600-2.c +++ b/gcc/testsuite/gcc.dg/pr87600-2.c @@ -23,22 +23,3 @@ test1 (void) asm ("blah %0 %1" : "=r" (var1) : "0" (var2)); /* { dg-error "invalid hard register usage between output operand and matching constraint operand" } */ return var1; } - -long -test2 (void) -{ - register long var1 asm (REG1); - register long var2 asm (REG1); - asm ("blah %0 %1" : "=&r" (var1) : "r" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ - return var1; -} - -long -test3 (void) -{ - register long var1 asm (REG1); - register long var2 asm (REG1); - long var3; - asm ("blah %0 %1" : "=&r" (var1), "=r" (var3) : "1" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ - return var1 + var3; -} diff --git a/gcc/testsuite/gcc.dg/pr87600-3.c b/gcc/testsuite/gcc.dg/pr87600-3.c new file mode 100644 index 00000000000..4f43a5f71ad --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr87600-3.c @@ -0,0 +1,26 @@ +/* PR rtl-optimization/87600 */ +/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* s390*-*-* x86_64-*-* } } */ +/* { dg-options "-O2" } */ + +#include "pr87600.h" + +/* The following are all invalid uses of local register variables. */ + +long +test2 (void) +{ + register long var1 asm (REG1); + register long var2 asm (REG1); + asm ("blah %0 %1" : "=&r" (var1) : "r" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + return var1; +} + +long +test3 (void) +{ + register long var1 asm (REG1); + register long var2 asm (REG1); + long var3; + asm ("blah %0 %1" : "=&r" (var1), "=r" (var3) : "1" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */ + return var1 + var3; +} diff --git a/gcc/testsuite/gcc.target/aarch64/asm-hard-reg-2.c b/gcc/testsuite/gcc.target/aarch64/asm-hard-reg-2.c new file mode 100644 index 00000000000..7434063646c --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/asm-hard-reg-2.c @@ -0,0 +1,17 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -march=armv8-a+sve" } */ + +/* Test register pairs. */ + +#include <arm_sve.h> + +void +test (void) +{ + svuint32x2_t x, y; + svuint32x4_t z; + + __asm__ __volatile__ ("" : "={z4}" (x), "={z6}" (y)); + __asm__ __volatile__ ("" : "={z5}" (x), "={z6}" (y)); /* { dg-error "multiple outputs to hard register: v6" } */ + __asm__ __volatile__ ("" : "={z4}" (z), "={z6}" (y)); /* { dg-error "multiple outputs to hard register: v6" } */ +} diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-7.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-7.c new file mode 100644 index 00000000000..923c9d2aaa3 --- /dev/null +++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-7.c @@ -0,0 +1,34 @@ +/* { dg-do compile } */ +/* { dg-options "-march=z13" } */ + +/* Test register pairs. */ + +void +test (void) +{ + register double f0 __asm__ ("f0"); + register double f2 __asm__ ("f2"); + register long double f0f2 __asm__ ("f0"); + double x; + long double y; + + /* Outputs */ + __asm__ __volatile__ ("" : "=r" (f0), "=r" (f0f2)); + __asm__ __volatile__ ("" : "=r" (f0f2), "={f0}" (y)); /* { dg-error "multiple outputs to hard register: %f0" } */ + __asm__ __volatile__ ("" : "={f0}" (x), "=r" (f0f2)); /* { dg-error "multiple outputs to hard register: %f0" } */ + + __asm__ __volatile__ ("" : "=r" (f2), "=r" (f0f2)); + __asm__ __volatile__ ("" : "={f2}" (x), "={f0}" (y)); /* { dg-error "multiple outputs to hard register: %f2" } */ + __asm__ __volatile__ ("" : "=r" (f2), "={f0}" (y)); /* { dg-error "multiple outputs to hard register: %f2" } */ + __asm__ __volatile__ ("" : "={f2}" (x), "=r" (f0f2)); /* { dg-error "multiple outputs to hard register: %f2" } */ + + /* Inputs */ + __asm__ __volatile__ ("" :: "r" (f0), "r" (f0f2)); + __asm__ __volatile__ ("" :: "r" (f0f2), "{f0}" (y)); /* { dg-error "multiple inputs to hard register: %f0" } */ + __asm__ __volatile__ ("" :: "{f0}" (x), "r" (f0f2)); /* { dg-error "multiple inputs to hard register: %f0" } */ + + __asm__ __volatile__ ("" :: "r" (f2), "r" (f0f2)); + __asm__ __volatile__ ("" :: "{f2}" (x), "{f0}" (y)); /* { dg-error "multiple inputs to hard register: %f2" } */ + __asm__ __volatile__ ("" :: "r" (f2), "{f0}" (y)); /* { dg-error "multiple inputs to hard register: %f2" } */ + __asm__ __volatile__ ("" :: "{f2}" (x), "r" (f0f2)); /* { dg-error "multiple inputs to hard register: %f2" } */ +} diff --git a/gcc/testsuite/lib/scanasm.exp b/gcc/testsuite/lib/scanasm.exp index 97935cb23c3..9bf426368b5 100644 --- a/gcc/testsuite/lib/scanasm.exp +++ b/gcc/testsuite/lib/scanasm.exp @@ -896,6 +896,10 @@ proc configure_check-function-bodies { config } { set up_config(fluff) {^\s*(?://)} } elseif { [istarget *-*-darwin*] } { set up_config(fluff) {^\s*(?:\.|//|@)|^L[0-9ABCESV]} + } elseif { [istarget s390*-*-*] } { + # Additionally to the defaults skip lines beginning with a # resulting + # from inline asm. + set up_config(fluff) {^\s*(?:\.|//|@|$|#)} } else { # Skip lines beginning with labels ('.L[...]:') or other directives # ('.align', '.cfi_startproc', '.quad [...]', '.text', etc.), '//' or diff --git a/gcc/tree-ssa-operands.cc b/gcc/tree-ssa-operands.cc index 4eb9673a085..a5970ac7b71 100644 --- a/gcc/tree-ssa-operands.cc +++ b/gcc/tree-ssa-operands.cc @@ -721,7 +721,7 @@ operands_scanner::get_asm_stmt_operands (gasm *stmt) constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); oconstraints[i] = constraint; parse_output_constraint (&constraint, i, 0, 0, &allows_mem, - &allows_reg, &is_inout); + &allows_reg, &is_inout, nullptr); /* This should have been split in gimplify_asm_expr. */ gcc_assert (!allows_reg || !is_inout); @@ -740,7 +740,7 @@ operands_scanner::get_asm_stmt_operands (gasm *stmt) tree link = gimple_asm_input_op (stmt, i); constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); parse_input_constraint (&constraint, 0, 0, noutputs, 0, oconstraints, - &allows_mem, &allows_reg); + &allows_mem, &allows_reg, nullptr); /* Memory operands are addressable. Note that STMT needs the address of this operand. */ diff --git a/gcc/tree-ssa-structalias.cc b/gcc/tree-ssa-structalias.cc index deca44ae0bf..afb5197c88a 100644 --- a/gcc/tree-ssa-structalias.cc +++ b/gcc/tree-ssa-structalias.cc @@ -5334,7 +5334,7 @@ find_func_aliases (struct function *fn, gimple *origt) constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); oconstraints[i] = constraint; parse_output_constraint (&constraint, i, 0, 0, &allows_mem, - &allows_reg, &is_inout); + &allows_reg, &is_inout, nullptr); /* A memory constraint makes the address of the operand escape. */ if (!allows_reg && allows_mem) @@ -5367,7 +5367,7 @@ find_func_aliases (struct function *fn, gimple *origt) constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link))); parse_input_constraint (&constraint, 0, 0, noutputs, 0, oconstraints, - &allows_mem, &allows_reg); + &allows_mem, &allows_reg, nullptr); /* A memory constraint makes the address of the operand escape. */ if (!allows_reg && allows_mem) diff --git a/gcc/varasm.cc b/gcc/varasm.cc index 10c1d2e3137..8384e8b635c 100644 --- a/gcc/varasm.cc +++ b/gcc/varasm.cc @@ -1734,7 +1734,8 @@ assemble_asm (tree asm_str) constraints[i] = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail))); if (!parse_output_constraint (&constraints[i], i, ninputs, noutputs, - &allows_mem, &allows_reg, &is_inout)) + &allows_mem, &allows_reg, &is_inout, + nullptr)) goto done; if (is_inout) { @@ -1776,7 +1777,7 @@ assemble_asm (tree asm_str) = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail))); if (!parse_input_constraint (&constraints[i + noutputs], i, ninputs, noutputs, 0, constraints, - &allows_mem, &allows_reg)) + &allows_mem, &allows_reg, nullptr)) goto done; if (strchr (constraints[i], '%')) { -- 2.49.0