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")