diff --git a/gcc/config/riscv/arcv-apex.md b/gcc/config/riscv/arcv-apex.md new file mode 100644 index 000000000000..c6e9afdafb35 --- /dev/null +++ b/gcc/config/riscv/arcv-apex.md @@ -0,0 +1,437 @@ +;; Machine description for the APEX instructions +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; This file is part of GCC. + +;; GCC is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GCC is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GCC; see the file COPYING3. If not see +;; . + +(define_c_enum "unspec" [ + +;; ARC-V APEX + UNSPEC_ARCV_APEX_VOID_V + UNSPEC_ARCV_APEX_VOID_SRC0_V + UNSPEC_ARCV_APEX_VOID_SRC0_SRC1_V + UNSPEC_ARCV_APEX_DEST + UNSPEC_ARCV_APEX_DEST_V + UNSPEC_ARCV_APEX_DEST_SRC0 + UNSPEC_ARCV_APEX_DEST_SRC0_V + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1 + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1_V +]) + +;; Used by "XD" insn. format: `insn` +(define_insn "riscv_arcv_apex_void_ftype_v" + [(unspec_volatile:SI [(match_operand:SI 0 "const_int_operand" "xAVpXD")] + UNSPEC_ARCV_APEX_VOID_V)] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[0]); + return xasprintf ("%s # 'XD' `insn`", str); +} + [(set_attr "type" "arith")] +) + +;; Used by "XI","XD" insn. format: `insn src0` +(define_insn "riscv_arcv_apex_void_ftype_" + [(unspec_volatile [(match_operand:SI 0 "const_int_operand" "xAVpXI,xAVpXD") + (match_operand:S0M 1 "nonmemory_operand" "I,r")] + UNSPEC_ARCV_APEX_VOID_SRC0_V)] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[0]); + const char *suffix = arcv_apex_get_insn_suffix (operands[0]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%d # 'XI' `insn src0`", + str, + suffix, + (int) INTVAL (operands[1])); + case 1: + return xasprintf ("%s\t%s # 'XD' `insn src0`", + str, + reg_names[REGNO (operands[1])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith")] +) + +(define_expand "riscv_arcv_apex_void_ftype_src0_v" + [(unspec_volatile [(match_operand:SI 0 "const_int_operand") + (match_operand 1 "nonmemory_operand")] + UNSPEC_ARCV_APEX_VOID_SRC0_V)] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_UNSPEC_VOLATILE (VOIDmode, + gen_rtvec (2, operands[0], + operands[1]), + UNSPEC_ARCV_APEX_VOID_SRC0_V)); + DONE; +}) + +;; Used by "XS","XD" insn. format: `insn src0, src1` +(define_insn "riscv_arcv_apex_void_ftype___v" + [(unspec_volatile [(match_operand:SI 0 "const_int_operand" "xAVpXS,xAVpXD") + (match_operand:S0M 1 "register_operand" "r,r") + (match_operand:S1M 2 "nonmemory_operand" "B8,r")] + UNSPEC_ARCV_APEX_VOID_SRC0_SRC1_V)] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[0]); + const char *suffix = arcv_apex_get_insn_suffix (operands[0]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%s,%d # 'XS' `insn src0, src1`", + str, + suffix, + reg_names[REGNO (operands[1])], + (int) INTVAL (operands[2])); + case 1: + return xasprintf ("%s\t%s,%s # 'XD' `insn src0, src1` ", + str, + reg_names[REGNO (operands[1])], + reg_names[REGNO (operands[2])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith")] +) + +(define_expand "riscv_arcv_apex_void_ftype_src0_src1_v" + [(unspec_volatile [(match_operand:SI 0 "const_int_operand") + (match_operand 1 "register_operand") + (match_operand 2 "nonmemory_operand")] + UNSPEC_ARCV_APEX_VOID_SRC0_SRC1_V)] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_UNSPEC_VOLATILE (VOIDmode, + gen_rtvec (3, operands[0], + operands[1], + operands[2]), + UNSPEC_ARCV_APEX_VOID_SRC0_SRC1_V)); + DONE; +}) + +;; Used by "XD" insn. format: `insn dest` volatile +(define_insn "riscv_arcv_apex__ftype_v" + [(set (match_operand:DM 0 "register_operand" "=r") + (unspec_volatile:DM [(match_operand:SI 1 "const_int_operand" "xAVpXD")] + UNSPEC_ARCV_APEX_DEST_V))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + return xasprintf ("%s\t%s # 'XD' `insn dest` volatile", + str, + reg_names[REGNO (operands[0])]); +} + [(set_attr "type" "arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype_v" + [(set (match_operand 0 "register_operand") + (unspec [(match_operand:SI 1 "const_int_operand")] + UNSPEC_ARCV_APEX_DEST_V))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC_VOLATILE (GET_MODE (operands[0]), + gen_rtvec (1, operands[1]), + UNSPEC_ARCV_APEX_DEST_V))); + DONE; +}) + +;; Used by "XD" insn. format: `insn dest` +(define_insn "riscv_arcv_apex__ftype" + [(set (match_operand:DM 0 "register_operand" "=r") + (unspec:DM [(match_operand:SI 1 "const_int_operand" "xAVpXD")] + UNSPEC_ARCV_APEX_DEST))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + return xasprintf ("%s\t%s # 'XD' `insn dest`", + str, + reg_names[REGNO (operands[0])]); +} + [(set_attr "type" "arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype" + [(set (match_operand 0 "register_operand") + (unspec [(match_operand:SI 1 "const_int_operand")] + UNSPEC_ARCV_APEX_DEST))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC (GET_MODE (operands[0]), + gen_rtvec (1, operands[1]), + UNSPEC_ARCV_APEX_DEST))); + DONE; +}) + +;; Used by "XI","XD" insn. format: `insn dest, src0` volatile +(define_insn "riscv_arcv_apex__ftype__v" + [(set (match_operand:DM 0 "register_operand" "=r,r") + (unspec_volatile:DM [(match_operand:SI 1 "const_int_operand" "xAVpXI,xAVpXD") + (match_operand:S0M 2 "nonmemory_operand" "I,r")] + UNSPEC_ARCV_APEX_DEST_SRC0_V))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + const char *suffix = arcv_apex_get_insn_suffix (operands[1]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%s,%d # 'XI' `insn des, src0` volatile", + str, + suffix, + reg_names[REGNO (operands[0])], + (int) INTVAL (operands[2])); + case 1: + return xasprintf ("%s\t%s,%s # 'XD' `insn des, src0` volatile", + str, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype_src0_v" + [(set (match_operand 0 "register_operand") + (unspec_volatile [(match_operand:SI 1 "const_int_operand") + (match_operand 2 "nonmemory_operand")] + UNSPEC_ARCV_APEX_DEST_SRC0_V))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC_VOLATILE (GET_MODE (operands[0]), + gen_rtvec (2, operands[1], + operands[2]), + UNSPEC_ARCV_APEX_DEST_SRC0_V))); + DONE; +}) + +;; Used by "XI","XD" insn. format: `insn dest, src0` +(define_insn "riscv_arcv_apex__ftype_" + [(set (match_operand:DM 0 "register_operand" "=r,r") + (unspec:DM [(match_operand:SI 1 "const_int_operand" "xAVpXI,xAVpXD") + (match_operand:S0M 2 "nonmemory_operand" "I,r")] + UNSPEC_ARCV_APEX_DEST_SRC0))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + const char *suffix = arcv_apex_get_insn_suffix (operands[1]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%s,%d # 'XI' `insn des, src0`", + str, + suffix, + reg_names[REGNO (operands[0])], + (int) INTVAL (operands[2])); + case 1: + return xasprintf ("%s\t%s,%s # 'XD' `insn des, src0`", + str, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype_src0" + [(set (match_operand 0 "register_operand") + (unspec [(match_operand:SI 1 "const_int_operand") + (match_operand 2 "nonmemory_operand")] + UNSPEC_ARCV_APEX_DEST_SRC0))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC (GET_MODE (operands[0]), + gen_rtvec (2, operands[1], + operands[2]), + UNSPEC_ARCV_APEX_DEST_SRC0))); + DONE; +}) + +;; Used by "XS","XC","XD" insn. format: `insn dest, src0, imm/src1` volatile +(define_insn "riscv_arcv_apex__ftype___v" + [(set (match_operand:DM 0 "register_operand" "=r,r,r") + (unspec_volatile:DM [(match_operand:SI 1 "const_int_operand" "xAVpXS,xAVpXC,xAVpXD") + (match_operand:S0M 2 "register_operand" "r,0,r") + (match_operand:S1M 3 "nonmemory_operand" "B8,I,r")] + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1_V))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + const char *suffix = arcv_apex_get_insn_suffix (operands[1]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%s,%s,%d # 'XS' `insn dest, src0, imm/src1` volatile", + str, + suffix, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + (int) INTVAL (operands[3])); + case 1: + return xasprintf ("%s%s\t%s,%s,%d # 'XC' `insn dest/src0, imm` volatile", + str, + suffix, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + (int) INTVAL (operands[3])); + case 2: + return xasprintf ("%s\t%s,%s,%s # 'XD' `insn dest, src0, imm/src1` volatile", + str, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + reg_names[REGNO (operands[3])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith,arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype_src0_src1_v" + [(set (match_operand 0 "register_operand") + (unspec [(match_operand:SI 1 "const_int_operand") + (match_operand 2 "register_operand") + (match_operand 3 "nonmemory_operand")] + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1_V))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC_VOLATILE (GET_MODE (operands[0]), + gen_rtvec (3, operands[1], + operands[2], + operands[3]), + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1_V))); + DONE; +}) + +;; Used by "XS","XC","XD" insn. format: `insn dest, src0, imm/src1` +(define_insn "riscv_arcv_apex__ftype__" + [(set (match_operand:DM 0 "register_operand" "=r,r,r") + (unspec:DM [(match_operand:SI 1 "const_int_operand" "xAVpXS,xAVpXC,xAVpXD") + (match_operand:S0M 2 "register_operand" "r,0,r") + (match_operand:S1M 3 "nonmemory_operand" "B8,I,r")] + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1))] + "" +{ + const char *str = arcv_apex_get_insn_name (operands[1]); + const char *suffix = arcv_apex_get_insn_suffix (operands[1]); + switch (which_alternative) + { + case 0: + return xasprintf ("%s%s\t%s,%s,%d # 'XS' `insn dest, src0, imm/src1`", + str, + suffix, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + (int) INTVAL (operands[3])); + case 1: + return xasprintf ("%s%s\t%s,%s,%d # 'XC' `insn dest/src0, imm`", + str, + suffix, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + (int) INTVAL (operands[3])); + case 2: + return xasprintf ("%s\t%s,%s,%s # 'XD' `insn dest, src0, imm/src1`", + str, + reg_names[REGNO (operands[0])], + reg_names[REGNO (operands[2])], + reg_names[REGNO (operands[3])]); + default: + gcc_unreachable (); + } +} + [(set_attr "type" "arith,arith,arith")] +) + +(define_expand "riscv_arcv_apex_dest_ftype_src0_src1" + [(set (match_operand 0 "register_operand") + (unspec [(match_operand:SI 1 "const_int_operand") + (match_operand 2 "register_operand") + (match_operand 3 "nonmemory_operand")] + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1))] + "" +{ + /* Build the SET exactly as it appears above, but with the + real RTX objects. Every operand already carries its mode, + so nothing needs to be guessed. The UNSPEC code is + essential as it tags this RTL with a unique ID, so the + recognizer can match it to the correct mode-specific + define_insn. */ + emit_insn (gen_rtx_SET (operands[0], + gen_rtx_UNSPEC (GET_MODE (operands[0]), + gen_rtvec (3, operands[1], + operands[2], + operands[3]), + UNSPEC_ARCV_APEX_DEST_SRC0_SRC1))); + DONE; +}) diff --git a/gcc/config/riscv/constraints.md b/gcc/config/riscv/constraints.md index c0a10c270eb2..0277d1141034 100644 --- a/gcc/config/riscv/constraints.md +++ b/gcc/config/riscv/constraints.md @@ -355,3 +355,38 @@ (define_register_constraint "xAVn30" "GENERAL_REGS" "Even-odd register pair suitable as a 64-bit operand of some uDSP instructions." "regno != 30") + +;; ARC-V APEX Constraints +(define_constraint "B8" + "An 8-bit signed immediate (-128 to 127)." + (and (match_code "const_int") + (match_test "IN_RANGE (ival, -128, 127)"))) + +;; These constraints check whether the APEX builtin instruction identified by +;; the given subcode (an integer indexing into `riscv_apex_builtins`) has the +;; specified instruction format enabled (APEX_XD, APEX_XS, APEX_XI, or APEX_XC). +;; +;; The function `arcv_apex_format_supports_p` returns true if the instruction's +;; supported formats include the queried format. +;; +;; This validation is used in instruction selection to ensure the chosen pattern +;; matches only when the instruction supports the required format. +(define_constraint "xAVpXD" + "Validate support of APEX_XD instruction format." + (and (match_code "const_int") + (match_test "arcv_apex_format_supports_p (INTVAL (op), APEX_XD)"))) + +(define_constraint "xAVpXS" + "Validate support of APEX_XS instruction format." + (and (match_code "const_int") + (match_test "arcv_apex_format_supports_p (INTVAL (op), APEX_XS)"))) + +(define_constraint "xAVpXI" + "Validate support of APEX_XI instruction format." + (and (match_code "const_int") + (match_test "arcv_apex_format_supports_p (INTVAL (op), APEX_XI)"))) + +(define_constraint "xAVpXC" + "Validate support of APEX_XC instruction format." + (and (match_code "const_int") + (match_test "arcv_apex_format_supports_p (INTVAL (op), APEX_XC)"))) diff --git a/gcc/config/riscv/iterators.md b/gcc/config/riscv/iterators.md index a7694137685a..7574ced90fa4 100644 --- a/gcc/config/riscv/iterators.md +++ b/gcc/config/riscv/iterators.md @@ -22,6 +22,12 @@ ;; Mode Iterators ;; ------------------------------------------------------------------- +;; These mode iterators are used by ARCV APEX to generate +;; all valid combinations of operand types. +(define_mode_iterator DM [SI DI SF DF]) ;; dest +(define_mode_iterator S0M [SI DI SF DF]) ;; src0 +(define_mode_iterator S1M [SI DI SF DF]) ;; src1 + ;; This mode iterator allows 32-bit and 64-bit GPR patterns to be generated ;; from the same template. (define_mode_iterator GPR [SI (DI "TARGET_64BIT")]) diff --git a/gcc/config/riscv/riscv-builtins.cc b/gcc/config/riscv/riscv-builtins.cc index 917e5a825c89..b35198394b4d 100644 --- a/gcc/config/riscv/riscv-builtins.cc +++ b/gcc/config/riscv/riscv-builtins.cc @@ -236,6 +236,42 @@ static GTY(()) int riscv_builtin_decl_index[NUM_INSN_CODES]; tree riscv_float16_type_node = NULL_TREE; +struct arcv_apex_builtin_description { + /* The code of the main .md file instruction. See riscv_builtin_type + for more information. */ + enum insn_code icode; + + /* The name of the built-in function. */ + const char *name; + + /* The name of the built-in instruction. */ + const char *insn_name; + + /* The opcode of the built-in instruction. */ + unsigned int opcode; + + /* Specifies how the function should be expanded. */ + enum riscv_builtin_type builtin_type; + + /* Specifies the instruction format. See "apex_insn_format" enum + for more details. */ + unsigned int insn_formats; + + /* Suffix added to the instruction name when the format is resolved + (e.g., XS, XI, or XC); set to "i" if resolved, otherwise "". */ + const char *insn_suffix; +}; + +/* The XD-type has 8 function bits encoding up to 256 instructions. + The XS-type has 6 function bits encoding up to 64 instructions. + Both the XI-type and the XC-type have 5 function bits each encoding up + to 32 instructions respectively. Thus giving a total of 384 possible + different instructions. */ +static const int arcv_apex_builtins_limit = 384; +static struct arcv_apex_builtin_description +arcv_apex_builtins[arcv_apex_builtins_limit]; +static int arcv_apex_builtin_index = 0; + /* Return the function type associated with function prototype TYPE. */ static tree @@ -353,6 +389,73 @@ riscv_expand_builtin_insn (enum insn_code icode, unsigned int n_ops, return has_target_p ? ops[0].value : const0_rtx; } +/* Validate the immediate argument passed to an APEX intrinsic. + + This function checks if the argument to the intrinsic call is a constant + integer and fits within the required immediate range depending on the + format supported by the given SUBCODE. Only instructions that do not + support APEX_XD are validated here. + + - For APEX_XI and APEX_XC formats: the argument must be a + signed 12-bit integer. + - For APEX_XS format: the argument must be a signed 8-bit integer. + + Returns false and reports an error if the argument is invalid; true + otherwise. */ + +static bool +arcv_apex_immediate_argument_valid_p (unsigned int subcode, tree exp) +{ + if (!arcv_apex_format_supports_p (subcode, APEX_XD)) + { + tree arg; + /* Get the first (and only) argument passed to the intrinsic call. */ + if (arcv_apex_format_supports_p (subcode, APEX_XI)) + arg = CALL_EXPR_ARG (exp, 0); + else if (arcv_apex_format_supports_p (subcode, APEX_XC) + || arcv_apex_format_supports_p (subcode, APEX_XS)) + arg = CALL_EXPR_ARG (exp, 1); + + /* If the argument is NOT a constant integer. */ + if (!TREE_CONSTANT (arg) || TREE_CODE (arg) != INTEGER_CST) + { + error ("argument to %qs must be a constant integer", + arcv_apex_builtins[subcode].name); + return false; + } + + /* If the current subcode supports the APEX_XI or APEX_XC format, then + the operand must fit within a signed 12-bit immediate. */ + if (arcv_apex_format_supports_p (subcode, APEX_XI) + || arcv_apex_format_supports_p (subcode, APEX_XC)) + { + HOST_WIDE_INT val = tree_to_shwi (arg); + /* Check if the value fits within a signed 12-bit immediate. */ + if ((val < -2048 || val > 2047)) + { + error ("argument value %d is outside the valid range [-2048, 2047]", + val); + return false; + } + } + + /* If the current subcode supports the APEX_XS format, then + the operand must fit within a signed 8-bit immediate. */ + if (arcv_apex_format_supports_p (subcode, APEX_XS)) + { + HOST_WIDE_INT val = tree_to_shwi (arg); + /* Check if the value fits within a signed 8-bit immediate. */ + if ((val < -128 || val > 127)) + { + error ("argument value %d is outside the valid range [-128, 127]", + val); + return false; + } + } + } + return true; +} + /* Expand a RISCV_BUILTIN_DIRECT or RISCV_BUILTIN_DIRECT_NO_TARGET function; HAS_TARGET_P says which. EXP is the CALL_EXPR that calls the function and ICODE is the code of the associated .md pattern. TARGET, if nonnull, @@ -360,7 +463,8 @@ riscv_expand_builtin_insn (enum insn_code icode, unsigned int n_ops, static rtx riscv_expand_builtin_direct (enum insn_code icode, rtx target, tree exp, - bool has_target_p) + bool has_target_p, unsigned int subcode, + bool has_subcode_p) { struct expand_operand ops[MAX_RECOG_OPERANDS]; @@ -369,6 +473,17 @@ riscv_expand_builtin_direct (enum insn_code icode, rtx target, tree exp, if (has_target_p) create_output_operand (&ops[opno++], target, TYPE_MODE (TREE_TYPE (exp))); + if (has_subcode_p) + { + /* Create an RTL constant for the APEX subcode. */ + rtx const_rtx = GEN_INT (subcode); + /* Add the subcode as an additional input operand to the RTL expression. */ + create_input_operand (&ops[opno++], const_rtx, SImode); + /* Validate the immediate argument passed to the APEX intrinsic. */ + if (!arcv_apex_immediate_argument_valid_p (subcode, exp)) + return const0_rtx; + } + /* Map the arguments to the other operands. */ gcc_assert (opno + call_expr_nargs (exp) == insn_data[icode].n_generator_args); @@ -420,16 +535,34 @@ riscv_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED, { case RISCV_BUILTIN_VECTOR: return riscv_vector::expand_builtin (subcode, exp, target); + case RISCV_BUILTIN_APEX: { + const struct arcv_apex_builtin_description *d + = &arcv_apex_builtins[subcode]; + + switch (d->builtin_type) + { + case RISCV_BUILTIN_DIRECT: + return riscv_expand_builtin_direct (d->icode, target, exp, true, + subcode, true); + + case RISCV_BUILTIN_DIRECT_NO_TARGET: + return riscv_expand_builtin_direct (d->icode, target, exp, false, + subcode, true); + } + break; + } case RISCV_BUILTIN_GENERAL: { const struct riscv_builtin_description *d = &riscv_builtins[subcode]; switch (d->builtin_type) { case RISCV_BUILTIN_DIRECT: - return riscv_expand_builtin_direct (d->icode, target, exp, true); + return riscv_expand_builtin_direct (d->icode, target, exp, true, + subcode, false); case RISCV_BUILTIN_DIRECT_NO_TARGET: - return riscv_expand_builtin_direct (d->icode, target, exp, false); + return riscv_expand_builtin_direct (d->icode, target, exp, false, + subcode, false); } } } @@ -454,3 +587,421 @@ riscv_atomic_assign_expand_fenv (tree *hold, tree *clear, tree *update) *clear = build_call_expr (fsflags, 1, old_flags); *update = NULL_TREE; } + +/* Return the APEX instruction name associated with a given subcode operand. + + The subcode is an unsigned integer extracted from `op` that indexes into + the `arcv_apex_builtins` array, which holds metadata about APEX builtin + instructions. + + This function retrieves the instruction name string corresponding to the + specified subcode, allowing the backend code to reference the mnemonic + of the builtin instruction during assembly emission. */ + +const char* +arcv_apex_get_insn_name (rtx op) +{ + unsigned int subcode = UINTVAL (op); + return arcv_apex_builtins[subcode].insn_name; +} + +/* Return the instruction suffix for an APEX subcode operand. + + This suffix is used to mark instructions whose format was resolved + (e.g., XS, XI, or XC) rather than explicitly specified via pragma. */ + +const char* +arcv_apex_get_insn_suffix (rtx op) +{ + unsigned int subcode = UINTVAL (op); + return arcv_apex_builtins[subcode].insn_suffix; +} + +/* Checks if the APEX builtin instruction identified by the subcode + supports the given instruction format. + + Returns true if the instruction format is included in the builtin's + supported formats; otherwise, returns false. */ + +bool +arcv_apex_format_supports_p (unsigned int subcode, unsigned int insn_format) +{ + const struct arcv_apex_builtin_description *d = &arcv_apex_builtins[subcode]; + return (d->insn_formats & insn_format); +} + +/* Set APEX operand flags for a built-in function. + This function inspects the function prototype in FNDECL and sets the + appropriate operand flags in INSN_FORMAT: + - APEX_DEST if the return type is not void. + - APEX_SRC0 and/or APEX_SRC1 depending on the number of arguments. + Emits an error if more than 2 arguments are present. */ + +static unsigned int +arcv_apex_set_insn_operand_flags (unsigned int insn_format, tree fndecl) +{ + /* Set DEST flag if the function does not return void. */ + tree return_type = TREE_TYPE (TREE_TYPE (fndecl)); + if (return_type != void_type_node) + insn_format |= APEX_DEST; + + /* Count non‑void parameters, aborting if there are more than two. */ + unsigned int nargs = 0; + for (tree arg = TYPE_ARG_TYPES (TREE_TYPE (fndecl)); + arg && TREE_CODE (TREE_VALUE (arg)) != VOID_TYPE; + arg = TREE_CHAIN (arg)) + { + if (++nargs > 2) + { + warning (0, "pragma intrinsic: Associated function can have " + "no more than 2 parameters"); + return 0xFFFFFFFF; + } + + /* Only perform size checks on 32-bit architectures. */ + if (POINTER_SIZE != 32) + continue; + + /* We want to check the size of the value represented by the argument. + - If it's a pointer, we check the size of the pointed-to type. + - If it's a scalar or aggregate type, we check its own size. */ + tree argtype = TREE_VALUE (arg); + tree type_to_check = argtype; + if (TREE_CODE (argtype) == POINTER_TYPE) + { + /* Skip size check if return type is void. */ + if (return_type == void_type_node) + continue; + + type_to_check = TREE_TYPE (argtype); + } + else + type_to_check = argtype; + + /* If TYPE_SIZE_UNIT exists and represents a constant integer value, + retrieve its size in bytes as a HOST_WIDE_INT. */ + if (! (TYPE_SIZE_UNIT (type_to_check) + && tree_fits_uhwi_p (TYPE_SIZE_UNIT (type_to_check)))) + continue; + + HOST_WIDE_INT bytes = tree_to_uhwi (TYPE_SIZE_UNIT (type_to_check)); + + /* If the type’s size is greater than 4 bytes, emit an error. + This applies to both pointed-to types and scalar types + larger than 4 bytes. */ + if (bytes <= 4) + continue; + + const char *fn_name = IDENTIFIER_POINTER (DECL_NAME (fndecl)); + if (TREE_CODE (argtype) == POINTER_TYPE) + { + /* Specific error for pointer parameters when return type is not void. */ + error ("pragma intrinsic: APEX function %qs must return " + "void or a scalar type that does not exceed 4 bytes", + fn_name); + } + else + { + /* General error for large or non-scalar parameter types. */ + error ("pragma intrinsic: APEX function %qs contains a parameter " + "of a non-scalar type, or one that exceeds 4 bytes", + fn_name); + } + return 0xFFFFFFFF; + } + + /* Source‑operand flags. */ + if (nargs >= 1) + insn_format |= APEX_SRC0; + if (nargs == 2) + insn_format |= APEX_SRC1; + + return insn_format; +} + +/* Infer APEX instruction format if none was explicitly specified. + + This function is only used when the user has not specified a concrete + instruction format (i.e., INSN_FORMAT is APEX_NONE). It determines the + actual format tag (APEX_XD, APEX_XS, APEX_XI, APEX_XC) based on + the opcode and operand layout. + + The operand configuration is extracted by right-shifting out the + APEX_DEST and APEX_SRC flags (bits 4–6). The opcode is then used to + select the most specific format that matches. + + Returns the updated instruction format with a resolved concrete type. */ + +static unsigned int +arcv_apex_resolve_insn_format (unsigned int insn_format, unsigned int opcode) +{ + /* Extract the operand flags (DEST, SRC0, SRC1) from bits 5–7. + These bits encode the operand signature used for format selection. */ + unsigned int insn_operands = insn_format >> 5; + + /* Assign the most general format APEX_XD. If opcode does not permit, + it will report an error at "arcv_apex_validate_insn_format". */ + insn_format |= APEX_XD; /* Any operands allowed. */ + + /* Assign APEX_XS format for two source operands patterns. */ + if (opcode <= APEX_OP_MAX_XS + && (insn_operands == APEX_VOID_FTYPE_SRC0_SRC1 + || insn_operands == APEX_DEST_FTYPE_SRC0_SRC1)) + insn_format |= APEX_XS; + + /* Assign APEX_XI format for one source operand patterns. */ + if (opcode <= APEX_OP_MAX_XI + && (insn_operands == APEX_VOID_FTYPE_SRC0 + || insn_operands == APEX_DEST_FTYPE_SRC0)) + insn_format |= APEX_XI; + + /* Assign APEX_XC format for one destination and two source operands. */ + if (opcode <= APEX_OP_MAX_XC + && insn_operands == APEX_DEST_FTYPE_SRC0_SRC1) + insn_format |= APEX_XC; + + return insn_format; +} + +/* Represents a validation rule for an APEX instruction format. */ +struct format_rule { + /* The instruction format bitmask. */ + unsigned insn_format; + + /* The string name for diagnostics. */ + const char *insn_format_str; + + /* The maximum allowed opcode value. */ + unsigned max_opcode; + + /* Number of required scalar arguments. */ + unsigned int required_args; + + /* Whether a destination is required. */ + bool required_dest; +}; + +/* insn_format, string, max_opcode, has_return, max_args. */ +const struct format_rule rules[] = { + { APEX_XD, "XD", 0xFF, /* Not taking into account. */ 0, false }, + { APEX_XS, "XS", 0x3F, 2, false }, + { APEX_XI, "XI", 0x1F, 1, false }, + { APEX_XC, "XC", 0x1F, 2, true }, +}; + +/* Validate that the given instruction format, opcode and operand count + comply with the predefined APEX instruction format rules. + + This function checks the instruction format against a set of rules that + define valid combinations of instruction formats, maximum allowed opcode + values, and required number of operands. It reports errors if: + - The instruction format is invalid or not supported. + - The opcode exceeds the maximum allowed for the given format. + - The operand count does not match the expected count for the format. + - Certain format-specific constraints are violated (e.g., return type + requirements or invalid argument usage). */ + +static void +arcv_apex_validate_insn_format (const char* fn_name, unsigned int insn_format, + unsigned opcode) +{ + /* If the instruction format is APEX_NONE, report an error. */ + gcc_assert (insn_format != APEX_NONE); + + /* Check for opcode duplication: + Emit an error if a previously registered APEX instruction has both: + - At least one matching insn format bit (among XD, XS, XI, XC). + - The same opcode value. + + This prevents defining two intrinsics with overlapping formats + that would conflict in opcode decoding. */ + for (int i = 0; i < arcv_apex_builtin_index; i++) + { + if ((arcv_apex_builtins[i].insn_formats & 0xF) & insn_format + && arcv_apex_builtins[i].opcode == opcode) + { + error ("pragma intrinsic: this specification defines an " + "opcode that duplicates a previous one", fn_name, opcode); + return; + } + } + + bool has_dest = (insn_format & APEX_DEST) >> 5; + unsigned int num_arguments = ((insn_format & APEX_SRC0) >> 6) + + ((insn_format & APEX_SRC1) >> 7); + + /* Iterate over each rule in the rules array. */ + for (size_t i = 0; i < sizeof (rules)/sizeof (rules[0]); ++i) + { + /* If the instruction format does not matche the + rule's insn_format, skip to the next rule. */ + if (!(insn_format & rules[i].insn_format)) + continue; + + const struct format_rule *rule = &rules[i]; + + /* Check if the opcode exceeds the rule's maximum + allowed opcode. */ + if (opcode > rule->max_opcode) + { + error ("pragma intrinsic: APEX opcode value %qd must be an integer " + "constant in the range 0 to 0x%x, inclusive", + opcode, rule->max_opcode); + return; + } + + /* Check if the number of operands matches the rule's required + operand count. */ + if (rule->insn_format != APEX_XD + && num_arguments != rule->required_args) + { + error ("pragma intrinsic: APEX function %qs must have %d scalar " + "parameter(s) for the %qs format class", + fn_name, rule->required_args, rule->insn_format_str); + return; + } + + if (rule->insn_format == APEX_XI && num_arguments == 0) + { + error ("argument 1 is not valid in \"constant\" designation"); + return; + } + + /* FIXME: Same behavior as CCAC, but shouldnt it we actually + validate both datatypes? */ + if (rule->insn_format == APEX_XC && has_dest != rule->required_dest) + { + error ("pragma intrinsic: APEX function %qs must return the same " + "type as the first parameter for the %qs format class", + fn_name, rule->insn_format_str); + return; + } + } +} + +/* Determine the appropriate GCC instruction code (insn_code) + based on the given APEX instruction format flags. + + This function decodes the instruction operand pattern encoded + in `insn_format` and returns the matching internal GCC insn_code + that corresponds to the instruction variant used during RTL generation. + + The operand layout is extracted by right-shifting out APEX_DEST and + APEX_SRC flags (bits 5–7). The function matches the operand pattern + against predefined instruction codes for different instruction formats + such as XI, XS, XC, and XD. + + Returns the corresponding insn_code enum for the given operand pattern. */ + +static enum insn_code +arcv_apex_get_icode (unsigned insn_format) +{ + unsigned int insn_operands = insn_format >> 5; + bool is_volatile = insn_format & APEX_VOLATILE; + + switch (insn_operands) + { + /* Used by "XD" insn. format: `insn` */ + case APEX_VOID_FTYPE: + return CODE_FOR_riscv_arcv_apex_void_ftype_v; + + /* Used by "XI","XD" insn. format: `insn src0` */ + case APEX_VOID_FTYPE_SRC0: + return CODE_FOR_riscv_arcv_apex_void_ftype_src0_v; + + /* Used by "XS","XD" insn. format: `insn src0, src1` */ + case APEX_VOID_FTYPE_SRC0_SRC1: + return CODE_FOR_riscv_arcv_apex_void_ftype_src0_src1_v; + + /* Used by "XD" insn. format: `insn dest` */ + case APEX_DEST_FTYPE: + return is_volatile + ? CODE_FOR_riscv_arcv_apex_dest_ftype_v + : CODE_FOR_riscv_arcv_apex_dest_ftype; + + /* Used by "XI","XD" insn. format: `insn dest, src0` */ + case APEX_DEST_FTYPE_SRC0: + return is_volatile + ? CODE_FOR_riscv_arcv_apex_dest_ftype_src0_v + : CODE_FOR_riscv_arcv_apex_dest_ftype_src0; + + /* Used by "XS","XC","XD" insn. format: `insn dest, src0, imm/src1` */ + case APEX_DEST_FTYPE_SRC0_SRC1: + return is_volatile + ? CODE_FOR_riscv_arcv_apex_dest_ftype_src0_src1_v + : CODE_FOR_riscv_arcv_apex_dest_ftype_src0_src1; + + default: + /* If none is selected, the default is "CODE_FOR_nothing". */ + return CODE_FOR_nothing; + } +} + +/* Initialize a RISC-V APEX built-in function. + + This function is invoked for each user-defined APEX intrinsic declared via + a pragma. It processes the parased, resolves the appropriate instruction + format, validates it and prints the corresponding .extInstruction section + for the assembler. + + It then determines the internal instruction code (icode) and categorizes the + built-in as either with or without a destination operand. The function + stores the resulting instruction metadata into the "arcv_apex_builtins" + array, modifies the function declaration "fndecl" to be recognized as a + built-in (BUILT_IN_MD), and encodes a custom function code for use + during later compiler stages. + + Each call increments the global built-in index to allow defining multiple + intrinsics in sequence. */ + +void +arcv_apex_init_builtin (tree fndecl, const char *fn_name, + const char *insn_name, unsigned int insn_formats, + int opcode) +{ + /* Update operand flags based on the function declaration. */ + insn_formats = arcv_apex_set_insn_operand_flags (insn_formats, fndecl); + if (insn_formats == 0xFFFFFFFF) + return; + + /* Resolve the instruction format: + If the user did not specify an instruction format at pragma level, + infer the concrete format based on opcode and operand flags. Mark + the insn. name with an "i" suffix for resolved XS/XI/XC formats; + otherwise, leave it as is. */ + const char *insn_suffix = ""; + if ((insn_formats & 0xF) == APEX_NONE) + { + insn_formats = arcv_apex_resolve_insn_format (insn_formats, opcode); + insn_suffix = "i"; + } + + /* Validate the format is allowed for this instruction. */ + arcv_apex_validate_insn_format (fn_name, insn_formats, opcode); + + /* Print .extInstruction section about APEX instruction. */ + arcv_apex_print_insn_section (insn_name, insn_suffix, opcode, insn_formats); + + /* Determine the internal instruction code (icode). */ + enum insn_code icode = arcv_apex_get_icode (insn_formats); + + /* Determine whether this builtin has a destination operand. */ + enum riscv_builtin_type builtin_type + = (insn_formats & APEX_DEST) ? RISCV_BUILTIN_DIRECT : + RISCV_BUILTIN_DIRECT_NO_TARGET; + + /* Store APEX insn information. */ + arcv_apex_builtins[arcv_apex_builtin_index] + = { icode, fn_name, insn_name, opcode, + builtin_type, insn_formats, insn_suffix }; + + /* Modify the prototype type as built-in. */ + fndecl->function_decl.built_in_class = BUILT_IN_MD; + + /* Modify the prototype function code to match the index + in "riscv_apex_builtins" with a mask for APEX only insns. */ + fndecl->function_decl.function_code + = (arcv_apex_builtin_index << RISCV_BUILTIN_SHIFT) + RISCV_BUILTIN_APEX; + + arcv_apex_builtin_index++; +} diff --git a/gcc/config/riscv/riscv-c.cc b/gcc/config/riscv/riscv-c.cc index 38d78bece5c5..f1b159681ca9 100644 --- a/gcc/config/riscv/riscv-c.cc +++ b/gcc/config/riscv/riscv-c.cc @@ -31,6 +31,7 @@ along with GCC; see the file COPYING3. If not see #include "target.h" #include "tm_p.h" #include "riscv-subset.h" +#include "stringpool.h" /* Used for "get_identifier ()" */ #define builtin_define(TXT) cpp_define (pfile, TXT) @@ -100,6 +101,234 @@ riscv_pragma_intrinsic_flags_restore (struct pragma_intrinsic_flags *flags) riscv_zvk_subext = flags->intrinsic_riscv_zvk_subext; } +/* Look up the user-defined function declaration by name. + + Given a function name as a string, this function returns the corresponding + tree node for its declaration, if it exists. If the function is not declared + in the current scope, an error is reported. */ + +tree +arcv_apex_lookup_function (const char *fn_name) +{ + /* Convert the raw string to an interned IDENTIFIER_NODE. */ + tree id = get_identifier (fn_name); + + /* Try the current scope (and outer scopes) for a matching declaration. */ + tree fndecl = lookup_name (id); + + /* Verify that we really found a function. */ + if (fndecl == NULL_TREE || TREE_CODE (fndecl) != FUNCTION_DECL) + { + error_at (input_location, "%qs is not declared as a function", fn_name); + } + + return fndecl; +} + +/* Return true if S is a valid APEX-intrinsic identifier. + Rules: + - The identifier must begin with an ASCII letter (A–Z or a–z) or + an underscore ('_'). + - All remaining characters must be ASCII letters, digits (0–9) or + underscores ('_'). + - Other symbols are not allowed. + Examples of valid identifiers: "add", "_bar", "Mul3", "op123". + Examples of invalid identifiers: "1foo", "baz.qux", "foo%". */ + +static bool +arcv_apex_valid_identifier_p (const char *s) +{ + if (!s || !s[0]) + return false; + + if (!(ISALPHA (s[0]) || s[0] == '_')) + return false; + + for (const char *p = s + 1; *p; ++p) + if (!ISALNUM (*p) && *p != '_') + return false; + + return true; +} + +/* Parses and handles `#pragma intrinsic` for APEX instructions. + + This pragma takes the form: + #pragma intrinsic(fn_name, "insn_name", opcode, "attributes"...) + + - `fn_name` is the name of the C function to be marked as intrinsic. + - `insn_name` is the string representation of the assembly instruction. + - `opcode` is the instruction's unique opcode value. + - `attributes` strings specify the attributes to the instruction: + - "XD", "XS", "XI", "XC": instruction format. + - "side_effect": Define instruction as volatile + (ignore optimizations). */ + +static void +arcv_apex_pragma_intrinsic (cpp_reader *) +{ + + enum cpp_ttype token; + tree x; + + /* Parse open Parenthesis '(' */ + if (pragma_lex (&x) != CPP_OPEN_PAREN) + { + error ("missing %<(%< after %<#pragma intrinsic%<"); + return; + } + + /* Parse the function identifier to be marked as intrinsic. */ + if (pragma_lex (&x) != CPP_NAME) + { + warning (0, "expected identifier in '#pragma intrinsic' - ignoring"); + return; + } + const char *fn_name = IDENTIFIER_POINTER (x); + + /* Note: We intentionally do not validate the presence of commas strictly. + If a comma is missing after the function identifier or after the + instruction name, parsing will continue, but the following token + will not match the expected type (e.g., string or number), and we’ll + always end up reporting a missing or invalid attribute. + + Because of this, explicitly diagnosing missing commas would be redundant + and would only clutter the error output. This behavior is intentional. */ + pragma_lex (&x); + + /* Parse the instruction name string, e.g., "add", "mul". */ + token = pragma_lex (&x); + const char *insn_name_raw = ""; + if (token == CPP_STRING) + insn_name_raw = TREE_STRING_POINTER (x); + else if (token == CPP_NAME) + insn_name_raw = IDENTIFIER_POINTER (x); + + /* If the instruction name is empty or absent, report an error. */ + if (insn_name_raw[0] == '\0') + error ("pragma intrinsic: APEX attribute 'name' is missing"); + /* Reject instruction names that do not follow APEX identifier rules. */ + else if (!arcv_apex_valid_identifier_p (insn_name_raw)) + error ("pragma intrinsic: APEX name %qs is not lexically valid", + insn_name_raw); + + /* Convert instruction name to lowercase to normalize it + for the assembler. */ + char *insn_name = xstrdup (insn_name_raw); + for (char *p = insn_name; *p; p++) + *p = TOLOWER (*p); + + /* See note above regarding comma handling. */ + pragma_lex (&x); + + /* Parse the opcode value (must be an integer). */ + if (pragma_lex (&x) != CPP_NUMBER) + { + error ("pragma intrinsic: APEX attribute 'opcode' is missing"); + return; + } + unsigned HOST_WIDE_INT opcode = TREE_INT_CST_LOW (x); + + /* Start with no formats selected. If none are explicitly provided, + formats will be determined later at "arcv_resolve_insn_format ()". */ + unsigned int insn_formats = APEX_NONE; + + /* Parse zero or more instruction format specifiers. */ + while (1) + { + token = pragma_lex (&x); + + /* Break if end of argument list reached. */ + if (token == CPP_CLOSE_PAREN) + break; + + /* Expect comma before each format string. */ + if (token != CPP_COMMA) + { + error ("pragma intrinsic: expected %<,%> or %<)%>"); + return; + } + + token = pragma_lex (&x); + + const char *attribute; + switch (token) + { + case CPP_STRING: + attribute = TREE_STRING_POINTER (x); + break; + case CPP_NAME: + attribute = IDENTIFIER_POINTER (x); + break; + default: + error ("pragma intrinsic: expected attribute"); + return; + } + + /* On first valid format specifier, override the default (NONE). */ + if (strcmp (attribute, "XD") == 0) + insn_formats |= APEX_XD; + else if (strcmp (attribute, "XS") == 0) + insn_formats |= APEX_XS; + else if (strcmp (attribute, "XI") == 0) + insn_formats |= APEX_XI; + else if (strcmp (attribute, "XC") == 0) + insn_formats |= APEX_XC; + else if (strcmp (attribute, "side_effect") == 0) + insn_formats |= APEX_VOLATILE; + else if (strcmp (attribute, "opcode") == 0) + { + if (pragma_lex (&x) != CPP_EQ || pragma_lex (&x) != CPP_GREATER) + { + warning (0, "expected %<=>%> in '#pragma intrinsic' - ignoring"); + return; + } + error ("pragma intrinsic: APEX attribute 'opcode' is redundant"); + return; + } + else + { + error ("pragma intrinsic: APEX attribute %qs is " + "not recognized", attribute); + return; + } + + } + + /* Check for incompatible combinations of APEX instruction formats + when user-defined. + The format 'XI' cannot be combined with 'XD', 'XS', or 'XC'. + If such a combination is detected, report an error and return + immediately. */ + if (insn_formats & APEX_XI) + { + if (insn_formats & APEX_XD) + { + error ("pragma intrinsic: APEX formats 'XI' and 'XD' " + "are not compatible"); + return; + } + else if (insn_formats & APEX_XS) + { + error ("pragma intrinsic: APEX formats 'XI' and 'XS' " + "are not compatible"); + return; + } + else if (insn_formats & APEX_XC) + { + error ("pragma intrinsic: APEX formats 'XI' and 'XC' " + "are not compatible"); + return; + } + } + + /* Lookup the user-defined function declaration of the APEX intrinsic. */ + tree fndecl = arcv_apex_lookup_function (fn_name); + + /* Register the specified function as an APEX intrinsic. */ + arcv_apex_init_builtin (fndecl, fn_name, insn_name, insn_formats, opcode); +} + static int riscv_ext_version_value (unsigned major, unsigned minor) { @@ -304,6 +533,9 @@ riscv_check_builtin_call (location_t loc, vec arg_loc, tree fndecl, case RISCV_BUILTIN_VECTOR: return riscv_vector::check_builtin_call (loc, arg_loc, subcode, fndecl, nargs, args); + + case RISCV_BUILTIN_APEX: + return true; } gcc_unreachable (); } @@ -331,6 +563,8 @@ riscv_resolve_overloaded_builtin (unsigned int uncast_location, tree fndecl, new_fndecl = riscv_vector::resolve_overloaded_builtin (loc, subcode, fndecl, arglist); break; + case RISCV_BUILTIN_APEX: + break; default: gcc_unreachable (); } @@ -350,4 +584,5 @@ riscv_register_pragmas (void) targetm.resolve_overloaded_builtin = riscv_resolve_overloaded_builtin; targetm.check_builtin_call = riscv_check_builtin_call; c_register_pragma ("riscv", "intrinsic", riscv_pragma_intrinsic); + c_register_pragma_with_expansion (0, "intrinsic", arcv_apex_pragma_intrinsic); } diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h index c90a77ad35cb..79d667583fa8 100644 --- a/gcc/config/riscv/riscv-protos.h +++ b/gcc/config/riscv/riscv-protos.h @@ -732,14 +732,17 @@ bool splat_to_scalar_move_p (rtx *); /* We classify builtin types into two classes: 1. General builtin class which is defined in riscv_builtins. 2. Vector builtin class which is a special builtin architecture - that implement intrinsic short into "pragma". */ + that implement intrinsic short into "pragma". + 3. Apex builtin class which is user-defined custom instruction + via "pragma intrinsic()". */ enum riscv_builtin_class { RISCV_BUILTIN_GENERAL, - RISCV_BUILTIN_VECTOR + RISCV_BUILTIN_VECTOR, + RISCV_BUILTIN_APEX }; -const unsigned int RISCV_BUILTIN_SHIFT = 1; +const unsigned int RISCV_BUILTIN_SHIFT = 2; /* Mask that selects the riscv_builtin_class part of a function code. */ const unsigned int RISCV_BUILTIN_CLASS = (1 << RISCV_BUILTIN_SHIFT) - 1; @@ -767,6 +770,14 @@ extern bool riscv_is_micro_arch (enum riscv_microarchitecture_type); extern bool arcv_micro_arch_supports_fusion_p (void); +extern void arcv_apex_print_insn_section (const char *, const char *, + int, unsigned int); +extern const char* arcv_apex_get_insn_name (rtx); +extern const char* arcv_apex_get_insn_suffix (rtx); +extern bool arcv_apex_format_supports_p (unsigned int, unsigned int); +extern void arcv_apex_init_builtin (tree, const char *, const char *, + unsigned int, int); + #ifdef RTX_CODE extern const char* th_mempair_output_move (rtx[4], bool, machine_mode, RTX_CODE); diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc index f40eaf2a6ed1..bdc16ba172ff 100644 --- a/gcc/config/riscv/riscv.cc +++ b/gcc/config/riscv/riscv.cc @@ -9681,6 +9681,71 @@ riscv_asm_output_variant_cc (FILE *stream, const tree decl, const char *name) } } +/* Print the ".extInstruction" section for the given APEX instruction formats. + + This function takes the instruction name, opcode, and a bitmask representing + the active APEX instruction formats and operand signatures. It prints one or + more `.extInstruction` lines into the assembly output file, allowing the + assembler and disassembler to recognize the instruction variants. + + The operand signature is encoded in bits 5–7 of `insn_format` and indicates + the presence and types of operands such as destination, source0 + and source1. */ + +void +arcv_apex_print_insn_section (const char *insn_name, const char *insn_suffix, + int opcode, unsigned int insn_format) +{ + /* Extract the operand flags (DEST, SRC0, SRC1) from bits 5–7. + These bits encode the operand signature used for format selection. */ + unsigned int insn_operands = insn_format >> 5; + + /* Print XD format line, adding operand info flags if absent. */ + if (insn_format & APEX_XD) + { + fprintf (asm_out_file, "\t.extInstruction %s,%d,XD", insn_name, opcode); + if ((insn_format & APEX_DEST) == 0) + fputs (",void", asm_out_file); + if ((insn_format & APEX_SRC0) == 0) + fputs (",no_src0", asm_out_file); + if ((insn_format & APEX_SRC1) == 0) + fputs (",no_src1", asm_out_file); + fputc ('\n', asm_out_file); + } + + /* Print XS and XC formats combined in a single line + with dest, src0, src1. */ + if ((insn_format & (APEX_XS | APEX_XC)) + && (insn_operands == APEX_DEST_FTYPE_SRC0_SRC1)) + { + fprintf (asm_out_file, "\t.extInstruction %s%s,%d", + insn_name, insn_suffix, opcode); + if (insn_format & APEX_XS) + fputs (",XS", asm_out_file); + if (insn_format & APEX_XC) + fputs (",XC", asm_out_file); + fputc ('\n', asm_out_file); + } + + /* Print XI format with dest, src0 operands; append ",void" if no dest. */ + if (insn_format & APEX_XI) + { + fprintf (asm_out_file, "\t.extInstruction %s%s,%d,XI", + insn_name, insn_suffix, opcode); + if ((insn_format & APEX_DEST) == 0) + fputs (",void", asm_out_file); + fputc ('\n', asm_out_file); + } + + /* Print XS format with void return and src0, src1 operands. */ + if ((insn_format & APEX_XS) + && (insn_operands == APEX_VOID_FTYPE_SRC0_SRC1)) + { + fprintf (asm_out_file, "\t.extInstruction %s%s,%d,XS,void\n", + insn_name, insn_suffix, opcode); + } +} + /* Implement ASM_DECLARE_FUNCTION_NAME. */ void diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h index c7ae6e4da791..53edf3d3e7ca 100644 --- a/gcc/config/riscv/riscv.h +++ b/gcc/config/riscv/riscv.h @@ -46,6 +46,40 @@ along with GCC; see the file COPYING3. If not see #define RISCV_TUNE_STRING_DEFAULT "rocket" #endif +#ifndef RISCV_APEX +#define RISCV_APEX + +enum APEX_OPCODE_FIELD_MAX +{ + APEX_OP_MAX_XD = 0xFF, + APEX_OP_MAX_XS = 0x3F, + APEX_OP_MAX_XI = 0x1F, + APEX_OP_MAX_XC = 0x1F, +}; + +enum apex_signature_mask { + APEX_VOID_FTYPE = 0b000, + APEX_VOID_FTYPE_SRC0 = 0b010, + APEX_VOID_FTYPE_SRC0_SRC1 = 0b110, + APEX_DEST_FTYPE = 0b001, + APEX_DEST_FTYPE_SRC0 = 0b011, + APEX_DEST_FTYPE_SRC0_SRC1 = 0b111, +}; + +enum apex_insn_format { + APEX_NONE = 0, + APEX_XD = 1 << 0, + APEX_XS = 1 << 1, + APEX_XI = 1 << 2, + APEX_XC = 1 << 3, + APEX_VOLATILE = 1 << 4, + APEX_DEST = 1 << 5, + APEX_SRC0 = 1 << 6, + APEX_SRC1 = 1 << 7, +}; + +#endif /* ! RISCV_APEX */ + extern const char *riscv_expand_arch (int argc, const char **argv); extern const char *riscv_expand_arch_from_cpu (int argc, const char **argv); extern const char *riscv_default_mtune (int argc, const char **argv); diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md index 4c0dcec2211f..5756d8bf474a 100644 --- a/gcc/config/riscv/riscv.md +++ b/gcc/config/riscv/riscv.md @@ -3957,3 +3957,4 @@ (include "arcv-rhx100.md") (include "arcv-rpx100.md") (include "arcv-udsp.md") +(include "arcv-apex.md")