This patch adds Link-Time Optimization support for APEX custom instructions
by serializing pragma-registered builtin metadata across compilation units.
Since APEX intrinsics are registered dynamically via #pragma intrinsic
rather than predefined in the compiler, their metadata (name, mnemonic,
opcode, format) must be explicitly streamed to survive LTO. The
implementation adds a custom LTO section (LTO_section_riscv_apex) and
hooks into the LTO write/read phases.
During output (arcv_apex_lto_write_section), referenced APEX builtins are
serialized to the LTO stream. During input (arcv_apex_lto_read_section),
builtin metadata from all compilation units is deserialized and re-registered.
The arcv_apex_lto_lookup_builtin function detects conflicting definitions
across translation units and reports errors.
However, LTO exposes a latent hash-equality inconsistency in fold-const.cc
for BUILT_IN_MD and BUILT_IN_FRONTEND builtins: operand_equal_p considers
two builtin FUNCTION_DECLs equal when they share DECL_BUILT_IN_CLASS and
DECL_UNCHECKED_FUNCTION_CODE, but hash_operand only canonicalizes
BUILT_IN_NORMAL builtins and falls back to hashing DECL_UID for other
classes. When APEX builtins are re-registered across TUs with distinct
FUNCTION_DECL nodes but identical builtin metadata, verify_hash_value
asserts due to the hash/equality mismatch.
This patch also fixes the issue by hashing non-BUILT_IN_NORMAL builtins using
(DECL_BUILT_IN_CLASS, DECL_UNCHECKED_FUNCTION_CODE), matching what
operand_equal_p compares.
gcc/ChangeLog:
* config/riscv/arcv-builtins.cc (arcv_apex_lto_lookup_builtin): New
function to find existing builtin and detect conflicts.
(arcv_apex_lto_register_builtin): New function to register builtin
during LTO read, skipping duplicates and reporting conflicts.
(arcv_apex_lto_write_section): New function to serialize referenced
APEX builtins to LTO stream.
(arcv_apex_lto_read_section): New function to deserialize and
re-register APEX builtins from all LTO input files.
* config/riscv/riscv-builtins.cc (riscv_builtin_decl): Remove
RISCV_BUILTIN_APEX sorry diagnostic for LTO.
* config/riscv/riscv.h (TARGET_RISCV_APEX): Define.
* lto-section-in.cc (lto_section_name): Add "riscv.apex" section name.
* lto-streamer-out.cc (produce_asm_for_decls): Call
arcv_apex_lto_write_section.
* lto-streamer.h (enum lto_section_type): Add LTO_section_riscv_apex.
(arcv_apex_lto_write_section): Declare.
(arcv_apex_lto_read_section): Declare.
gcc/lto/ChangeLog:
* lto-common.cc (read_cgraph_and_symbols): Call
arcv_apex_lto_read_section after reading all input files.
gcc/testsuite/ChangeLog:
* gcc.target/riscv/apex/apex.exp: Add LTO test support.
* gcc.target/riscv/apex/arcv-apex-lto-err1_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err1_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err2_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err2_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err3_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err3_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err4_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-err4_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test1_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test1_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test2_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test2_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test3_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test3_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test4_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test4_1.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test5_0.c: New test.
* gcc.target/riscv/apex/arcv-apex-lto-test5_1.c: New test.
Signed-off-by: Luis Silva <[email protected]>
---
gcc/config/riscv/arcv-builtins.cc | 233 ++++++++++++++++++
gcc/config/riscv/riscv-builtins.cc | 8 +-
gcc/config/riscv/riscv.h | 2 +
gcc/fold-const.cc | 12 +
gcc/lto-section-in.cc | 1 +
gcc/lto-streamer-out.cc | 6 +
gcc/lto-streamer.h | 6 +
gcc/lto/lto-common.cc | 6 +
gcc/testsuite/gcc.target/riscv/apex/apex.exp | 27 +-
.../riscv/apex/arcv-apex-lto-err1_0.c | 16 ++
.../riscv/apex/arcv-apex-lto-err1_1.c | 9 +
.../riscv/apex/arcv-apex-lto-err2_0.c | 17 ++
.../riscv/apex/arcv-apex-lto-err2_1.c | 9 +
.../riscv/apex/arcv-apex-lto-err3_0.c | 17 ++
.../riscv/apex/arcv-apex-lto-err3_1.c | 9 +
.../riscv/apex/arcv-apex-lto-err4_0.c | 19 ++
.../riscv/apex/arcv-apex-lto-err4_1.c | 9 +
.../riscv/apex/arcv-apex-lto-test1_0.c | 49 ++++
.../riscv/apex/arcv-apex-lto-test1_1.c | 25 ++
.../riscv/apex/arcv-apex-lto-test2_0.c | 36 +++
.../riscv/apex/arcv-apex-lto-test2_1.c | 15 ++
.../riscv/apex/arcv-apex-lto-test3_0.c | 49 ++++
.../riscv/apex/arcv-apex-lto-test3_1.c | 25 ++
.../riscv/apex/arcv-apex-lto-test4_0.c | 25 ++
.../riscv/apex/arcv-apex-lto-test4_1.c | 12 +
.../riscv/apex/arcv-apex-lto-test5_0.c | 52 ++++
.../riscv/apex/arcv-apex-lto-test5_1.c | 20 ++
27 files changed, 708 insertions(+), 6 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_1.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_0.c
create mode 100644 gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_1.c
diff --git a/gcc/config/riscv/arcv-builtins.cc
b/gcc/config/riscv/arcv-builtins.cc
index d8a633fe56c..fee8146ec6f 100644
--- a/gcc/config/riscv/arcv-builtins.cc
+++ b/gcc/config/riscv/arcv-builtins.cc
@@ -40,6 +40,10 @@ along with GCC; see the file COPYING3. If not see
#include "backend.h"
#include "gimple.h"
#include "gimple-iterator.h"
+#include "cgraph.h"
+#include "lto-streamer.h"
+#include "ipa-utils.h"
+#include "data-streamer.h"
/* Specifies how a built-in function should be converted into rtl. */
enum riscv_builtin_type {
@@ -394,6 +398,235 @@ arcv_apex_register_builtin (tree fndecl, const char
*fn_name,
arcv_apex_emit_ext_directive (mnemonic, opcode, format_flags);
}
+/* Check if an APEX builtin with the given characteristics already exists.
+ Returns the index if found with matching parameters, -1 if not found,
+ or -2 if found but with conflicting parameters. */
+
+static int
+arcv_apex_lto_lookup_builtin (const char *fn_name, const char *mnemonic,
+ unsigned int opcode, unsigned int format_flags,
+ location_t loc)
+{
+ for (int i = 0; i < arcv_apex_builtin_index; i++)
+ {
+ const struct arcv_apex_builtin_description *d = &arcv_apex_builtins[i];
+
+ /* Skip if function name doesn't match. */
+ if (strcmp (d->name, fn_name) != 0)
+ continue;
+
+ /* Function name matches - validate all parameters. */
+ bool mismatch = false;
+
+ if (strcmp (d->mnemonic, mnemonic) != 0)
+ {
+ warning_at (loc, 0, "APEX builtin %qs already registered with "
+ "different mnemonic: %qs vs %qs",
+ fn_name, d->mnemonic, mnemonic);
+ mismatch = true;
+ }
+
+ if (d->opcode != opcode)
+ {
+ warning_at (loc, 0, "APEX builtin %qs already registered with "
+ "different opcode: 0x%x vs 0x%x",
+ fn_name, d->opcode, opcode);
+ mismatch = true;
+ }
+
+ if (d->format_flags != format_flags)
+ {
+ warning_at (loc, 0, "APEX builtin %qs already registered with "
+ "different instruction formats: 0x%x vs 0x%x",
+ fn_name, d->format_flags, format_flags);
+ mismatch = true;
+ }
+
+ /* Return -2 for conflicting registration, or index if match. */
+ return mismatch ? -2 : i;
+ }
+
+ return -1;
+}
+
+static void
+arcv_apex_lto_register_builtin (const char *fn_name, const char *mnemonic,
+ unsigned int opcode, unsigned int format_flags,
+ bool emit_directive_p, tree fndecl)
+{
+ location_t loc = DECL_SOURCE_LOCATION (fndecl);
+
+ int existing_idx = arcv_apex_lto_lookup_builtin (fn_name, mnemonic, opcode,
+ format_flags, loc);
+
+ if (existing_idx == -2)
+ {
+ error_at (loc, "APEX builtin %qs has conflicting definitions across "
+ "compilation units", fn_name);
+ return;
+ }
+
+ if (existing_idx >= 0)
+ {
+ fndecl->function_decl.function_code
+ = (existing_idx << RISCV_BUILTIN_SHIFT) + RISCV_BUILTIN_APEX;
+ return;
+ }
+
+ gcc_assert (arcv_apex_builtin_index < arcv_apex_builtins_limit);
+
+ enum riscv_builtin_type builtin_type
+ = (format_flags & APEX_VOID) ? RISCV_BUILTIN_DIRECT_NO_TARGET
+ : RISCV_BUILTIN_DIRECT;
+
+ enum insn_code icode = arcv_apex_icode (format_flags);
+
+ /* Store APEX insn information. */
+ arcv_apex_builtins[arcv_apex_builtin_index]
+ = { icode, xstrdup (fn_name), xstrdup (mnemonic), opcode,
+ builtin_type, format_flags };
+
+ fndecl->function_decl.function_code
+ = (arcv_apex_builtin_index << RISCV_BUILTIN_SHIFT) + RISCV_BUILTIN_APEX;
+
+ arcv_apex_builtin_index++;
+
+ if (emit_directive_p)
+ arcv_apex_emit_ext_directive (mnemonic, opcode, format_flags);
+}
+
+/* LTO serialization for APEX intrinsics.
+
+ APEX intrinsics are registered dynamically via #pragma intrinsic,
+ so their metadata must be explicitly serialized to survive LTO.
+ See lto-streamer-out.cc (produce_asm_for_decls) and
+ lto/lto-common.cc (read_cgraph_and_symbols) for the call sites. */
+
+/* Serialize all referenced APEX intrinsics to the LTO stream. */
+
+void
+arcv_apex_lto_write_section (void)
+{
+ if (arcv_apex_builtin_index == 0)
+ return;
+
+ auto_vec<int> used_indices;
+ for (int i = 0; i < arcv_apex_builtin_index; i++)
+ {
+ const struct arcv_apex_builtin_description *d = &arcv_apex_builtins[i];
+ gcc_assert (d->name);
+
+ symtab_node *snode
+ = symtab_node::get_for_asmname (get_identifier (d->name));
+
+ if (snode && snode->referred_to_p ())
+ used_indices.safe_push (i);
+ }
+
+ if (used_indices.is_empty ())
+ return;
+
+ struct lto_simple_output_block *ob
+ = lto_create_simple_output_block (LTO_section_riscv_apex);
+
+ if (!ob)
+ return;
+
+ streamer_write_uhwi_stream (ob->main_stream, used_indices.length ());
+
+ for (unsigned int idx = 0; idx < used_indices.length (); idx++)
+ {
+ const struct arcv_apex_builtin_description *d
+ = &arcv_apex_builtins[used_indices[idx]];
+
+ gcc_assert (d->name && d->mnemonic);
+
+ size_t name_len = strlen (d->name);
+ streamer_write_uhwi_stream (ob->main_stream, name_len);
+ for (size_t j = 0; j < name_len; j++)
+ streamer_write_char_stream (ob->main_stream, d->name[j]);
+
+ size_t mnemonic_len = strlen (d->mnemonic);
+ streamer_write_uhwi_stream (ob->main_stream, mnemonic_len);
+ for (size_t j = 0; j < mnemonic_len; j++)
+ streamer_write_char_stream (ob->main_stream, d->mnemonic[j]);
+
+ streamer_write_uhwi_stream (ob->main_stream, d->opcode);
+ streamer_write_uhwi_stream (ob->main_stream, d->format_flags);
+ }
+
+ lto_destroy_simple_output_block (ob);
+}
+
+/* Deserialize and re-register APEX intrinsics from all LTO input files. */
+
+void
+arcv_apex_lto_read_section (void)
+{
+ struct lto_file_decl_data **file_data_vec = lto_get_file_decl_data ();
+ struct lto_file_decl_data *file_data;
+ unsigned int j = 0;
+
+ while ((file_data = file_data_vec[j++]))
+ {
+ const char *data;
+ size_t len;
+ class lto_input_block *ib
+ = lto_create_simple_input_block (file_data, LTO_section_riscv_apex,
+ &data, &len);
+
+ if (!ib)
+ continue;
+
+ unsigned int apex_count = streamer_read_uhwi (ib);
+ unsigned int registered_count = 0;
+
+ for (unsigned int i = 0; i < apex_count; i++)
+ {
+ unsigned int fn_name_len = streamer_read_uhwi (ib);
+ char *fn_name = XNEWVEC (char, fn_name_len + 1);
+ for (unsigned int k = 0; k < fn_name_len; k++)
+ fn_name[k] = streamer_read_uchar (ib);
+ fn_name[fn_name_len] = '\0';
+
+ unsigned int mnemonic_len = streamer_read_uhwi (ib);
+ char *mnemonic = XNEWVEC (char, mnemonic_len + 1);
+ for (unsigned int k = 0; k < mnemonic_len; k++)
+ mnemonic[k] = streamer_read_uchar (ib);
+ mnemonic[mnemonic_len] = '\0';
+
+ unsigned int opcode = streamer_read_uhwi (ib);
+ unsigned int format_flags = streamer_read_uhwi (ib);
+
+ symtab_node *snode
+ = symtab_node::get_for_asmname (get_identifier (fn_name));
+
+ cgraph_node *node = dyn_cast<cgraph_node *> (snode);
+ if (node)
+ {
+ tree fndecl = node->decl;
+ if (fndecl && TREE_CODE (fndecl) == FUNCTION_DECL)
+ {
+ arcv_apex_lto_register_builtin (fn_name, mnemonic, opcode,
+ format_flags, !flag_wpa,
+ fndecl);
+ registered_count++;
+ }
+ }
+
+ XDELETEVEC (fn_name);
+ XDELETEVEC (mnemonic);
+ }
+
+ if (registered_count != apex_count)
+ warning (0, "APEX LTO: expected %u intrinsics but registered %u",
+ apex_count, registered_count);
+
+ lto_destroy_simple_input_block (file_data, LTO_section_riscv_apex,
+ ib, data, len);
+ }
+}
+
/* Validate the immediate argument passed to an APEX intrinsic.
Used during builtin expansion. */
diff --git a/gcc/config/riscv/riscv-builtins.cc
b/gcc/config/riscv/riscv-builtins.cc
index d58915799f1..5a189fc6014 100644
--- a/gcc/config/riscv/riscv-builtins.cc
+++ b/gcc/config/riscv/riscv-builtins.cc
@@ -317,10 +317,10 @@ riscv_builtin_decl (unsigned int code, bool initialize_p
ATTRIBUTE_UNUSED)
return riscv_vector::builtin_decl (subcode, initialize_p);
case RISCV_BUILTIN_APEX:
- /* APEX intrinsics are pragma-registered and don't have
- persistent decls. */
- sorry ("APEX intrinsics are not supported with LTO yet");
- return error_mark_node;
+ /* Trick GCC to think that the function is defined.
+ The actual fndecl used is created after this
+ validation from the GIMPLE representation in LTO. */
+ return integer_zero_node;
}
return error_mark_node;
}
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index f9a5bb02337..ca8f9090660 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -1349,6 +1349,8 @@ typedef enum {
#ifndef RISCV_APEX_H
#define RISCV_APEX_H
+#define TARGET_RISCV_APEX 1
+
/* APEX instruction format flags. */
enum apex_insn_format {
APEX_NONE = 0,
diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
index 352083e146b..f7e30c11f3d 100644
--- a/gcc/fold-const.cc
+++ b/gcc/fold-const.cc
@@ -4118,6 +4118,18 @@ operand_compare::hash_operand (const_tree t,
inchash::hash &hstate,
t = builtin_decl_explicit (DECL_FUNCTION_CODE (t));
code = TREE_CODE (t);
}
+ /* For non-BUILT_IN_NORMAL builtins (e.g. BUILT_IN_MD), we cannot
+ canonicalize to a single representative decl, but we must still
+ hash consistently with operand_equal_p which compares by
+ built_in_class + function_code. Hash those fields instead of
+ DECL_UID so that distinct FUNCTION_DECLs (e.g. from different
+ LTO translation units) that compare equal also hash equal. */
+ else if (fndecl_built_in_p (t))
+ {
+ hstate.add_int (DECL_BUILT_IN_CLASS (t));
+ hstate.add_hwi (DECL_UNCHECKED_FUNCTION_CODE (t));
+ return;
+ }
/* FALL THROUGH */
default:
if (POLY_INT_CST_P (t))
diff --git a/gcc/lto-section-in.cc b/gcc/lto-section-in.cc
index c57f84f02bd..892ce06bafd 100644
--- a/gcc/lto-section-in.cc
+++ b/gcc/lto-section-in.cc
@@ -57,6 +57,7 @@ const char *lto_section_name[LTO_N_SECTION_TYPES] =
"ipa_sra",
"odr_types",
"ipa_modref",
+ "riscv_apex",
};
/* Hooks so that the ipa passes can call into the lto front end to get
diff --git a/gcc/lto-streamer-out.cc b/gcc/lto-streamer-out.cc
index 5338a952cb0..cc746e1c5aa 100644
--- a/gcc/lto-streamer-out.cc
+++ b/gcc/lto-streamer-out.cc
@@ -3474,6 +3474,12 @@ produce_asm_for_decls (void)
/* Write command line opts. */
lto_write_options ();
+#ifdef TARGET_RISCV_APEX
+ /* RISC-V APEX intrinsics are registered dynamically via pragmas and
+ need explicit serialization to survive LTO. See arcv-builtins.cc. */
+ arcv_apex_lto_write_section ();
+#endif
+
/* Deallocate memory and clean up. */
for (idx = 0; idx < num_fns; idx++)
{
diff --git a/gcc/lto-streamer.h b/gcc/lto-streamer.h
index f8aa8465b7c..8e86816ec0a 100644
--- a/gcc/lto-streamer.h
+++ b/gcc/lto-streamer.h
@@ -225,6 +225,7 @@ enum lto_section_type
LTO_section_ipa_sra,
LTO_section_odr_types,
LTO_section_ipa_modref,
+ LTO_section_riscv_apex,
LTO_N_SECTION_TYPES /* Must be last. */
};
@@ -966,6 +967,11 @@ void cl_optimization_stream_in (class data_in *,
/* In lto-opts.cc. */
extern void lto_write_options (void);
+#ifdef TARGET_RISCV_APEX
+/* In config/riscv/arcv-builtins.cc. */
+extern void arcv_apex_lto_write_section (void);
+extern void arcv_apex_lto_read_section (void);
+#endif
/* Statistics gathered during LTO, WPA and LTRANS. */
extern struct lto_stats_d lto_stats;
diff --git a/gcc/lto/lto-common.cc b/gcc/lto/lto-common.cc
index cc513b84ef1..50047bec95b 100644
--- a/gcc/lto/lto-common.cc
+++ b/gcc/lto/lto-common.cc
@@ -2970,6 +2970,12 @@ read_cgraph_and_symbols (unsigned nfiles, const char
**fnames)
else
ipa_read_summaries ();
+#ifdef TARGET_RISCV_APEX
+ /* RISC-V APEX intrinsics are registered dynamically via pragmas and
+ need explicit deserialization after LTO input. See arcv-builtins.cc. */
+ arcv_apex_lto_read_section ();
+#endif
+
ggc_grow ();
for (i = 0; all_file_decl_data[i]; i++)
diff --git a/gcc/testsuite/gcc.target/riscv/apex/apex.exp
b/gcc/testsuite/gcc.target/riscv/apex/apex.exp
index 07a23718671..44e3acc2232 100644
--- a/gcc/testsuite/gcc.target/riscv/apex/apex.exp
+++ b/gcc/testsuite/gcc.target/riscv/apex/apex.exp
@@ -23,8 +23,31 @@ if ![istarget riscv*-*-*] then {
# Load support libraries.
load_lib gcc-dg.exp
+load_lib standard.exp
+load_lib lto.exp
+load_lib scanltrans.exp
-# Run tests.
+# Run regular single-file tests.
dg-init
-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]] "" ""
+set tests [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]]
+set non_lto_tests [list]
+foreach test $tests {
+ if {![string match "*arcv-apex-lto-*" $test]} {
+ lappend non_lto_tests $test
+ }
+}
+dg-runtest $non_lto_tests "" ""
dg-finish
+
+# Run multi-file LTO tests if supported.
+if [check_effective_target_lto] {
+ lto_init no-mathlib
+
+ foreach src [lsort [find $srcdir/$subdir arcv-apex-lto-*_0.c]] {
+ if [runtest_file_p $runtests $src] {
+ lto-execute $src "riscv_apex_lto"
+ }
+ }
+
+ lto_finish
+}
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_0.c
new file mode 100644
index 00000000000..263eec7f545
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_0.c
@@ -0,0 +1,16 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 } } } */
+
+int apex_conflict (int, int);
+#pragma intrinsic (apex_conflict, "conflict", 50, "XS")
+
+extern int use_conflict (int, int);
+
+int
+main (void)
+{
+ return apex_conflict (1, 2) + use_conflict (5, 10);
+}
+
+/* { dg-lto-warning "APEX builtin 'apex_conflict' already registered with
different opcode: 0x32 vs 0x33" "" { target *-*-* } 4 } */
+/* { dg-lto-error "APEX builtin 'apex_conflict' has conflicting definitions
across compilation units" "" { target *-*-* } 4 } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_1.c
new file mode 100644
index 00000000000..9ec7e518ccb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err1_1.c
@@ -0,0 +1,9 @@
+
+int apex_conflict (int, int);
+#pragma intrinsic (apex_conflict, "conflict", 51, "XS") /* Different opcode:
51 vs 50 */
+
+int
+use_conflict (int a, int b)
+{
+ return apex_conflict (a, b);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_0.c
new file mode 100644
index 00000000000..757419626dc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_0.c
@@ -0,0 +1,17 @@
+/* Test LTO error detection for instruction name mismatch */
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 } } } */
+
+int apex_insn_mismatch (int, int);
+#pragma intrinsic (apex_insn_mismatch, "insn_a", 100, "XD")
+
+extern int use_insn_mismatch (int, int);
+
+int
+main (void)
+{
+ return apex_insn_mismatch (1, 2) + use_insn_mismatch (5, 10);
+}
+
+/* { dg-lto-warning "APEX builtin 'apex_insn_mismatch' already registered with
different mnemonic: 'insn_a' vs 'insn_b'" "" { target *-*-* } 5 } */
+/* { dg-lto-error "APEX builtin 'apex_insn_mismatch' has conflicting
definitions across compilation units" "" { target *-*-* } 5 } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_1.c
new file mode 100644
index 00000000000..7df812a4cd8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err2_1.c
@@ -0,0 +1,9 @@
+
+int apex_insn_mismatch (int, int);
+#pragma intrinsic (apex_insn_mismatch, "insn_b", 100, "XD") /* Different
instruction name */
+
+int
+use_insn_mismatch (int a, int b)
+{
+ return apex_insn_mismatch (a, b);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_0.c
new file mode 100644
index 00000000000..b005b5525e2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_0.c
@@ -0,0 +1,17 @@
+/* Test LTO error detection for instruction format mismatch */
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 } } } */
+
+int apex_format_mismatch (int, int);
+#pragma intrinsic (apex_format_mismatch, "format_test", 20, "XS")
+
+extern int use_format_mismatch (int);
+
+int
+main (void)
+{
+ return apex_format_mismatch (1, 2) + use_format_mismatch (5);
+}
+
+/* { dg-lto-warning "APEX builtin 'apex_format_mismatch' already registered
with different instruction formats: 0x2 vs 0x8" "" { target *-*-* } 5 } */
+/* { dg-lto-error "APEX builtin 'apex_format_mismatch' has conflicting
definitions across compilation units" "" { target *-*-* } 5 } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_1.c
new file mode 100644
index 00000000000..4efb805f31f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err3_1.c
@@ -0,0 +1,9 @@
+
+int apex_format_mismatch (int, int);
+#pragma intrinsic (apex_format_mismatch, "format_test", 20, "XC") /*
Different format: XC vs XS */
+
+int
+use_format_mismatch (int a)
+{
+ return apex_format_mismatch (a, 10);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_0.c
new file mode 100644
index 00000000000..e9b3e8f742a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_0.c
@@ -0,0 +1,19 @@
+/* Test LTO error detection for multiple mismatches */
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 } } } */
+
+int apex_multi_mismatch (int, int);
+#pragma intrinsic (apex_multi_mismatch, "insn_original", 20, "XD")
+
+extern int use_multi_mismatch (int);
+
+int
+main (void)
+{
+ return apex_multi_mismatch (1, 2) + use_multi_mismatch (5);
+}
+
+/* { dg-lto-warning "APEX builtin 'apex_multi_mismatch' already registered
with different mnemonic: 'insn_original' vs 'insn_different'" "" { target *-*-*
} 5 } */
+/* { dg-lto-warning "APEX builtin 'apex_multi_mismatch' already registered
with different opcode: 0x14 vs 0x15" "" { target *-*-* } 5 } */
+/* { dg-lto-warning "APEX builtin 'apex_multi_mismatch' already registered
with different instruction formats: 0x1 vs 0x2" "" { target *-*-* } 5 } */
+/* { dg-lto-error "APEX builtin 'apex_multi_mismatch' has conflicting
definitions across compilation units" "" { target *-*-* } 5 } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_1.c
new file mode 100644
index 00000000000..1134e68e355
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-err4_1.c
@@ -0,0 +1,9 @@
+
+int apex_multi_mismatch (int, int);
+#pragma intrinsic (apex_multi_mismatch, "insn_different", 21, "XS") /* All
different */
+
+int
+use_multi_mismatch (int a)
+{
+ return apex_multi_mismatch (a, 10);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_0.c
new file mode 100644
index 00000000000..fb0ae276394
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_0.c
@@ -0,0 +1,49 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 -fdump-tree-optimized -save-temps } } } */
+
+int func_apex_xd (int, int);
+#pragma intrinsic (func_apex_xd, "insn_apex_xd", 50, "XD")
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 32, "XS")
+
+int func_apex_xi (int);
+#pragma intrinsic (func_apex_xi, "insn_apex_xi", 20, "XI")
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 21, "XC")
+
+extern int foo (int);
+
+int
+main (void)
+{
+ int x = func_apex_xd (1,2);
+ int y = func_apex_xs (x, 3);
+ int z = func_apex_xi (4);
+ int w = func_apex_xc (y, 5);
+
+ return foo (y) + w + z;
+}
+
+/* Verify that APEX intrinsic calls survive LTO optimization. */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xd" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xs" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xi" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xc" "optimized" } } */
+
+/* Verify that the correct custom instructions are emitted in assembly. */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xd,50,XD" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xs,32,XS" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xi,20,XI" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xc,21,XC" }
} */
+
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xd\\s+a\[0-9\]+,a\[0-9\]+,a\[0-9\]+" 2 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,3" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times "insn_apex_xi\\s+a\[0-9\]+,4" 1 }
} */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,a\[0-9\]+,5" 1 } } */
+
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,8" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times "insn_apex_xi\\s+a\[0-9\]+,9" 1 }
} */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,a\[0-9\]+,10" 1 } } */
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_1.c
new file mode 100644
index 00000000000..e02a0cd5b1e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test1_1.c
@@ -0,0 +1,25 @@
+
+int func_apex_xd (int, int);
+#pragma intrinsic (func_apex_xd, "insn_apex_xd", 50, "XD")
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 32, "XS")
+
+int func_apex_xi (int);
+#pragma intrinsic (func_apex_xi, "insn_apex_xi", 20, "XI")
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 21, "XC")
+
+extern int foo (int);
+
+int
+foo (int val)
+{
+ int a = func_apex_xd (6, 7);
+ int b = func_apex_xs (val, 8);
+ int c = func_apex_xi (9);
+ int d = func_apex_xc (val, 10);
+ return a + b + c + d;
+}
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_0.c
new file mode 100644
index 00000000000..c384a504425
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_0.c
@@ -0,0 +1,36 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 -fdump-tree-optimized -save-temps } } } */
+
+int func_apex_xd (int, int);
+#pragma intrinsic (func_apex_xd, "insn_apex_xd", 50, "XD")
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 32, "XS")
+
+extern int bar (int, int);
+
+int
+main (void)
+{
+ int x = func_apex_xd (1, 2);
+ int y = func_apex_xs (x, 3);
+
+ return bar (x, y);
+}
+
+/* Verify that APEX intrinsic calls survive LTO optimization. */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xd" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xs" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xi" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xc" "optimized" } } */
+
+/* Verify that the correct custom instructions are emitted in assembly. */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xd,50,XD" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xs,32,XS" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xc,21,XC" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xi,20,XI" }
} */
+
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xd\\s+a\[0-9\]+,a\[0-9\]+,a\[0-9\]+" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,3" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times "insn_apex_xi\\s+a\[0-9\]+,4" 1 }
} */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,a\[0-9\]+,5" 1 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_1.c
new file mode 100644
index 00000000000..493318bb53d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test2_1.c
@@ -0,0 +1,15 @@
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 21, "XC")
+
+int func_apex_xi (int);
+#pragma intrinsic (func_apex_xi, "insn_apex_xi", 20, "XI")
+
+int
+bar (int a, int b)
+{
+ int c = func_apex_xi (4);
+ int d = func_apex_xc (a, 5);
+ return b + c + d;
+}
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_0.c
new file mode 100644
index 00000000000..3d8de370c54
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_0.c
@@ -0,0 +1,49 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 -fdump-tree-optimized -save-temps } } } */
+
+/* Pragma order in _0.c: XD, XS, XI, XC */
+int func_apex_xd (int, int);
+#pragma intrinsic (func_apex_xd, "insn_apex_xd", 50, "XD")
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 32, "XS")
+
+int func_apex_xi (int);
+#pragma intrinsic (func_apex_xi, "insn_apex_xi", 20, "XI")
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 21, "XC")
+
+extern int foo (int);
+
+int
+main (void)
+{
+ int x = func_apex_xd (1, 2);
+ int y = func_apex_xs (x, 3);
+ int z = func_apex_xi (4);
+ int w = func_apex_xc (y, 5);
+
+ return foo (y) + w + z;
+}
+
+/* Verify that APEX intrinsic calls survive LTO optimization. */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xd" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xs" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xi" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xc" "optimized" } } */
+
+/* Verify that the correct custom instructions are emitted in assembly. */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xd,50,XD" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xs,32,XS" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xi,20,XI" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xc,21,XC" }
} */
+
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xd\\s+a\[0-9\]+,a\[0-9\]+,a\[0-9\]+" 2 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,3" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times "insn_apex_xi\\s+a\[0-9\]+,4" 1 }
} */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,a\[0-9\]+,5" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,8" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times "insn_apex_xi\\s+a\[0-9\]+,9" 1 }
} */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,a\[0-9\]+,10" 1 } } */
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_1.c
new file mode 100644
index 00000000000..9401957a0f9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test3_1.c
@@ -0,0 +1,25 @@
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 21, "XC")
+
+int func_apex_xi (int);
+#pragma intrinsic (func_apex_xi, "insn_apex_xi", 20, "XI")
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 32, "XS")
+
+int func_apex_xd (int, int);
+#pragma intrinsic (func_apex_xd, "insn_apex_xd", 50, "XD")
+
+extern int foo (int);
+
+int
+foo (int val)
+{
+ int a = func_apex_xd (6, 7);
+ int b = func_apex_xs (val, 8);
+ int c = func_apex_xi (9);
+ int d = func_apex_xc (val, 10);
+ return a + b + c + d;
+}
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_0.c
new file mode 100644
index 00000000000..bcd9f68765a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_0.c
@@ -0,0 +1,25 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O0 -fdump-tree-optimized -save-temps } } } */
+
+int func_apex_xc (int, int);
+#pragma intrinsic (func_apex_xc, "insn_apex_xc", 1, "XC")
+
+extern int foo (int);
+
+int
+main (void)
+{
+ int x = func_apex_xc (1, 2);
+ return foo (x);
+}
+
+/* Verify that APEX intrinsic calls survive LTO optimization. */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xc" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_xs" "optimized" } } */
+
+/* Verify that the correct custom instructions are emitted in assembly. */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xc,1,XC" }
} */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_xs,2,XS" }
} */
+
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xc\\s+a\[0-9\]+,+a\[0-9\]+,2" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_xs\\s+a\[0-9\]+,a\[0-9\]+,3" 1 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_1.c
new file mode 100644
index 00000000000..66757f6bbe5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test4_1.c
@@ -0,0 +1,12 @@
+
+int func_apex_xs (int, int);
+#pragma intrinsic (func_apex_xs, "insn_apex_xs", 2, "XS")
+
+extern int foo (int);
+
+int
+foo (int val)
+{
+ return func_apex_xs (val, 3);
+}
+
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_0.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_0.c
new file mode 100644
index 00000000000..ac522c3101c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_0.c
@@ -0,0 +1,52 @@
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -flto -O2 -fdump-tree-optimized -save-temps } } } */
+
+/* Test that intrinsics optimized away don't cause crashes during LTO.
+ This test registers multiple intrinsics but only uses some of them,
+ allowing the optimizer to remove the unused ones. */
+
+int func_apex_used1 (int, int);
+#pragma intrinsic (func_apex_used1, "insn_apex_used1", 1, "XC")
+
+int func_apex_unused (int, int);
+#pragma intrinsic (func_apex_unused, "insn_apex_unused", 2, "XS")
+
+int func_apex_used2 (int, int);
+#pragma intrinsic (func_apex_used2, "insn_apex_used2", 3, "XC")
+
+extern int bar (int);
+
+static int
+func_apex_unused_helper (int a, int b)
+{
+ return func_apex_unused (a, b);
+}
+
+int
+main (void)
+{
+ /* Only use func_apex_used1 and func_apex_used2.
+ func_apex_unused should be optimized away. */
+ int x = func_apex_used1 (5, 10);
+ int y = func_apex_used2 (x, 15);
+ return bar (y);
+}
+
+/* Verify that only the used intrinsics appear in the optimized output. */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_used1" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_used2" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump "func_apex_from_other" "optimized" } } */
+
+/* Verify that the unused intrinsic is NOT in the optimized output. */
+/* { dg-final { scan-ltrans-tree-dump-not "func_apex_unused" "optimized" } } */
+/* { dg-final { scan-ltrans-tree-dump-not "func_apex_also_unused" "optimized"
} } */
+
+/* Verify that only used intrinsics have .extInstruction directives. */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_used1,1,XC"
} } */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction insn_apex_used2,3,XC"
} } */
+/* { dg-final { scan-ltrans-assembler "\\.extInstruction
insn_apex_from_other,4,XS" } } */
+
+/* Verify the actual instruction usage in assembly. */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_used1\\s+a\[0-9\]+,a\[0-9\]+,10" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_used2\\s+a\[0-9\]+,a\[0-9\]+,15" 1 } } */
+/* { dg-final { scan-ltrans-assembler-times
"insn_apex_from_other\\s+a\[0-9\]+,a\[0-9\]+,20" 1 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_1.c
b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_1.c
new file mode 100644
index 00000000000..9915f2306bf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/apex/arcv-apex-lto-test5_1.c
@@ -0,0 +1,20 @@
+
+int func_apex_from_other (int, int);
+#pragma intrinsic (func_apex_from_other, "insn_apex_from_other", 4, "XS")
+
+int func_apex_also_unused (int, int);
+#pragma intrinsic (func_apex_also_unused, "insn_apex_also_unused", 5, "XC")
+
+static int
+func_apex_also_unused_helper (int a, int b)
+{
+ return func_apex_also_unused (a, b);
+}
+
+int
+bar (int val)
+{
+ /* Only use func_apex_from_other.
+ func_apex_also_unused should be optimized away. */
+ return func_apex_from_other (val, 20);
+}
--
2.34.0