diff --git a/gcc/config.gcc b/gcc/config.gcc
index 6af7eadaaab4..744729503169 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -557,7 +557,7 @@ riscv*)
extra_objs="riscv-builtins.o riscv-c.o riscv-sr.o riscv-shorten-memrefs.o riscv-selftests.o riscv-string.o"
extra_objs="${extra_objs} riscv-v.o riscv-vsetvl.o riscv-vector-costs.o riscv-avlprop.o"
extra_objs="${extra_objs} riscv-vector-builtins.o riscv-vector-builtins-shapes.o riscv-vector-builtins-bases.o sifive-vector-builtins-bases.o"
- extra_objs="${extra_objs} thead.o riscv-target-attr.o riscv-zicfilp.o"
+ extra_objs="${extra_objs} thead.o riscv-target-attr.o riscv-zicfilp.o riscv-apex-lto.o"
d_target_objs="riscv-d.o"
extra_headers="riscv_vector.h riscv_crypto.h riscv_bitmanip.h riscv_th_vector.h sifive_vector.h"
target_gtfiles="$target_gtfiles \$(srcdir)/config/riscv/riscv-vector-builtins.cc"
diff --git a/gcc/config/riscv/riscv-apex-lto.cc b/gcc/config/riscv/riscv-apex-lto.cc
new file mode 100644
index 000000000000..2b2952daddc4
--- /dev/null
+++ b/gcc/config/riscv/riscv-apex-lto.cc
@@ -0,0 +1,254 @@
+/* LTO serialization for RISC-V APEX intrinsics.
+ 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
+. */
+
+/* RISC-V APEX (ARC Processor Extension) intrinsics are unique in GCC
+ because they are registered dynamically at compile-time via #pragma intrinsic
+ directives, rather than being statically defined like normal target builtins.
+
+ This creates a challenge for LTO (Link Time Optimization): when compiling
+ with -flto, each translation unit may register different APEX intrinsics via
+ pragmas. During the link-time optimization phase, all these intrinsic
+ definitions must be preserved and made available for code generation.
+
+ This file implements LTO serialization support for APEX intrinsics by:
+
+ 1. Writing Phase (produce_asm_for_decls):
+ - Iterates through all registered APEX intrinsics
+ - Serializes their metadata (name, mnemonic, opcode, instruction formats)
+ - Writes to a dedicated .gnu.lto_riscv_apex section in object files
+
+ 2. Reading Phase (read_cgraph_and_symbols):
+ - Reads .gnu.lto_riscv_apex sections from all input object files
+ - Reconstructs and re-registers all APEX intrinsics
+ - Makes them available for optimization and code generation
+
+ Without this support, LTO would lose APEX intrinsic definitions, causing
+ unavailable intrinsics errors during link-time optimization. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "cgraph.h"
+#include "lto-streamer.h"
+#include "ipa-utils.h"
+#include "data-streamer.h"
+#include "stringpool.h"
+#include "attribs.h"
+
+/* Declarations from riscv-builtins.cc for accessing
+ APEX builtin information. */
+extern int arcv_apex_get_builtin_count (void);
+extern void arcv_apex_get_builtin_info (int, const char **, const char **,
+ unsigned int *, unsigned int *);
+extern void arcv_apex_lto_register_builtin (const char *, const char *,
+ unsigned int, unsigned int, bool,
+ tree);
+extern const char *arcv_apex_get_fn_name (unsigned int);
+
+/* Write RISC-V APEX intrinsic information to the LTO bytecode stream.
+
+ This function is called during the compilation phase when producing LTO
+ bytecode. It serializes all APEX intrinsics that were registered via
+ #pragma directives in the current translation unit.
+
+ The serialization format for each intrinsic is:
+ - Function name length (uhwi)
+ - Function name characters
+ - Instruction name length (uhwi)
+ - Instruction name characters
+ - Opcode (uhwi)
+ - Instruction format flags (uhwi) */
+
+void
+arcv_apex_lto_write_section (void)
+{
+ /* Get the number of registered APEX builtins in this compilation unit. */
+ int apex_count = arcv_apex_get_builtin_count ();
+
+ /* If no APEX builtins were registered via pragmas, skip section creation.
+ This is common for translation units that don't use APEX intrinsics. */
+ if (apex_count == 0)
+ return;
+
+ /* Collect indices of intrinsics that are actually used and not optimized
+ away. Use an auto_vec to avoid manual memory management. */
+ auto_vec used_indices;
+ for (int i = 0; i < apex_count; i++)
+ {
+ const char *fn_name = arcv_apex_get_fn_name (i);
+ gcc_assert (fn_name);
+
+ /* Check if the intrinsic is still referenced in the program. */
+ symtab_node *snode = symtab_node::get_for_asmname (
+ get_identifier (fn_name));
+
+ /* Only keep intrinsics that exist and are actually used.
+ Check if the symbol is referred to anywhere in the program. */
+ if (snode && snode->referred_to_p ())
+ used_indices.safe_push (i);
+ }
+
+ /* If all intrinsics were optimized away, skip section creation. */
+ if (used_indices.is_empty ())
+ return;
+
+ /* Create a new LTO section for APEX intrinsics. */
+ struct lto_simple_output_block *ob
+ = lto_create_simple_output_block (LTO_section_riscv_apex);
+
+ if (!ob)
+ return;
+
+ /* Write the number of used APEX builtins so the reader knows
+ how many to expect. */
+ streamer_write_uhwi_stream (ob->main_stream, used_indices.length ());
+
+ /* Serialize only the intrinsics that are still used. */
+ for (unsigned int idx = 0; idx < used_indices.length (); idx++)
+ {
+ int i = used_indices[idx];
+ const char *fn_name = NULL;
+ const char *insn_name = NULL;
+ unsigned int opcode = 0;
+ unsigned int insn_formats = 0;
+
+ /* Get builtin information from the registry. */
+ arcv_apex_get_builtin_info (i, &fn_name, &insn_name,
+ &opcode, &insn_formats);
+
+ /* Function and instruction names must exist. */
+ gcc_assert (fn_name && insn_name);
+
+ /* Write function name as length-prefixed string. */
+ size_t name_len = strlen (fn_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, fn_name[j]);
+
+ /* Write instruction name as length-prefixed string. */
+ size_t insn_name_len = strlen (insn_name);
+ streamer_write_uhwi_stream (ob->main_stream, insn_name_len);
+ for (size_t j = 0; j < insn_name_len; j++)
+ streamer_write_char_stream (ob->main_stream, insn_name[j]);
+
+ /* Write opcode value. */
+ streamer_write_uhwi_stream (ob->main_stream, opcode);
+
+ /* Write instruction format flags. */
+ streamer_write_uhwi_stream (ob->main_stream, insn_formats);
+ }
+
+ lto_destroy_simple_output_block (ob);
+}
+
+/* Read RISC-V APEX intrinsic information from the LTO bytecode stream.
+
+ This function is called during the link-time optimization phase. It reads
+ the .gnu.lto_riscv_apex sections from all input object files and
+ re-registers all APEX intrinsics so they are available for optimization
+ and code generation in the LTRANS phase.
+
+ The function iterates over all input files, reads their APEX sections,
+ and re-registers each intrinsic by calling riscv_register_apex_builtin. */
+
+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;
+
+ /* Process each input file's APEX section. */
+ 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);
+
+ /* Skip files that don't have an APEX section
+ (did not use APEX intrinsics). */
+ if (!ib)
+ continue;
+
+ /* Read the count of APEX builtins in this file. */
+ unsigned int apex_count = streamer_read_uhwi (ib);
+ unsigned int registered_count = 0;
+
+ /* Deserialize each APEX intrinsic. */
+ for (unsigned int i = 0; i < apex_count; i++)
+ {
+ /* Read function name. */
+ 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';
+
+ /* Read instruction name. */
+ unsigned int insn_name_len = streamer_read_uhwi (ib);
+ char *insn_name = XNEWVEC (char, insn_name_len + 1);
+ for (unsigned int k = 0; k < insn_name_len; k++)
+ insn_name[k] = streamer_read_uchar (ib);
+ insn_name[insn_name_len] = '\0';
+
+ /* Read opcode and instruction format flags. */
+ unsigned int opcode = streamer_read_uhwi (ib);
+ unsigned int insn_formats = streamer_read_uhwi (ib);
+
+ /* Look up the function declaration in the merged symbol table.
+ During LTO, all function declarations from all compilation units
+ are merged into a single global symbol table. */
+ symtab_node *snode = symtab_node::get_for_asmname (
+ get_identifier (fn_name));
+
+ cgraph_node *node = dyn_cast (snode);
+ if (node)
+ {
+ tree fndecl = node->decl;
+ if (fndecl && TREE_CODE (fndecl) == FUNCTION_DECL)
+ {
+ /* Re-register the intrinsic so it's available for code generation.
+ The !flag_wpa parameter controls whether to print .extInstruction
+ directives (only needed in final LTRANS phase, not WPA phase). */
+ arcv_apex_lto_register_builtin (fn_name, insn_name, opcode,
+ insn_formats, !flag_wpa, fndecl);
+ registered_count++;
+ }
+ }
+
+ /* Free allocated memory. */
+ XDELETEVEC (fn_name);
+ XDELETEVEC (insn_name);
+ }
+
+ /* Verify we successfully re-registered all APEX intrinsics
+ from the section. If this fails, the LTO section is
+ likely corrupted. */
+ gcc_assert (registered_count == apex_count);
+
+ lto_destroy_simple_input_block (file_data, LTO_section_riscv_apex,
+ ib, data, len);
+ }
+}
+
diff --git a/gcc/config/riscv/riscv-builtins.cc b/gcc/config/riscv/riscv-builtins.cc
index 51f47e4bdf87..0cf09d9f6c0a 100644
--- a/gcc/config/riscv/riscv-builtins.cc
+++ b/gcc/config/riscv/riscv-builtins.cc
@@ -373,6 +373,12 @@ riscv_builtin_decl (unsigned int code, bool initialize_p ATTRIBUTE_UNUSED)
case RISCV_BUILTIN_VECTOR:
return riscv_vector::builtin_decl (subcode, initialize_p);
+
+ case RISCV_BUILTIN_APEX:
+ /* 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;
}
@@ -606,6 +612,15 @@ riscv_atomic_assign_expand_fenv (tree *hold, tree *clear, tree *update)
*update = NULL_TREE;
}
+
+/* Return the function name associated with a given subcode. */
+
+const char*
+arcv_apex_get_fn_name (unsigned int subcode)
+{
+ return arcv_apex_builtins[subcode].name;
+}
+
/* Return the APEX instruction name associated with a given subcode operand.
The subcode is an unsigned integer extracted from `op` that indexes into
@@ -648,6 +663,61 @@ arcv_apex_format_supports_p (unsigned int subcode, unsigned int insn_format)
return (d->insn_formats & insn_format);
}
+/* 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_find_builtin (const char *fn_name, const char *insn_name,
+ unsigned int opcode, unsigned int insn_formats,
+ location_t loc)
+{
+ for (int i = 0; i < arcv_apex_builtin_index; i++)
+ {
+ const struct arcv_apex_builtin_description *d = &arcv_apex_builtins[i];
+
+ /* Check if function name matches. */
+ if (strcmp (d->name, fn_name) == 0)
+ {
+ /* Validate all parameters match. */
+ bool mismatch = false;
+
+ if (strcmp (d->insn_name, insn_name) != 0)
+ {
+ warning_at (loc, 0, "APEX builtin %qs already registered with different "
+ "instruction name: %qs vs %qs",
+ fn_name, d->insn_name, insn_name);
+ 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->insn_formats != insn_formats)
+ {
+ warning_at (loc, 0, "APEX builtin %qs already registered with different "
+ "instruction formats: 0x%x vs 0x%x",
+ fn_name, d->insn_formats, insn_formats);
+ mismatch = true;
+ }
+
+ /* Return special value for conflicting registration. */
+ if (mismatch)
+ return -2;
+
+ /* Found matching registration. */
+ return i;
+ }
+ }
+
+ return -1; /* Not found. */
+}
+
/* 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:
@@ -1023,3 +1093,91 @@ arcv_apex_init_builtin (tree fndecl, const char *fn_name,
arcv_apex_builtin_index++;
}
+
+
+void
+arcv_apex_lto_register_builtin (const char *fn_name, const char *insn_name,
+ unsigned int opcode, unsigned int insn_formats,
+ bool wpa_p, tree fndecl)
+{
+ /* Get source location from function declaration. */
+ location_t loc = DECL_SOURCE_LOCATION (fndecl);
+
+ /* Check if this APEX builtin is already registered. */
+ int existing_idx = arcv_apex_lto_find_builtin (fn_name, insn_name, opcode,
+ insn_formats, loc);
+
+ if (existing_idx >= 0)
+ {
+ /* Assert that the function code is the same as the existing index. */
+ gcc_assert (fndecl->function_decl.function_code
+ == (unsigned) ((existing_idx << RISCV_BUILTIN_SHIFT)
+ + RISCV_BUILTIN_APEX));
+ return;
+ }
+
+ if (existing_idx == -2)
+ {
+ /* Conflicting registration - warnings already issued. */
+ error_at (loc, "APEX builtin %qs has conflicting definitions across "
+ "compilation units", fn_name);
+ return;
+ }
+
+ /* Not registered yet - proceed with new registration. */
+
+ /* Check for array overflow before storing. */
+ gcc_assert (arcv_apex_builtin_index < arcv_apex_builtins_limit);
+
+ /* Calculate instruction suffix for auto-resolved formats. */
+ bool suffix_p = (insn_formats & APEX_XD)
+ && (insn_formats & (APEX_XS | APEX_XI | APEX_XC));
+ const char *insn_suffix = suffix_p ? "i" : "";
+
+ /* Print .extInstruction section during WPA phase. */
+ if (wpa_p)
+ arcv_apex_print_insn_section (insn_name, insn_suffix, opcode, 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 builtin information. */
+ arcv_apex_builtins[arcv_apex_builtin_index] = {
+ arcv_apex_get_icode (insn_formats), xasprintf ("%s", fn_name),
+ xasprintf ("%s", insn_name), opcode, builtin_type, insn_formats,
+ xasprintf ("%s", insn_suffix) /* TODO: Remove insn_suffix from struct. */
+ };
+
+ /* Set function code for this builtin. */
+ fndecl->function_decl.function_code
+ = (arcv_apex_builtin_index << RISCV_BUILTIN_SHIFT) + RISCV_BUILTIN_APEX;
+
+ arcv_apex_builtin_index++;
+}
+
+/* Get the number of registered APEX builtins. */
+
+int
+arcv_apex_get_builtin_count (void)
+{
+ return arcv_apex_builtin_index;
+}
+
+/* Get information about a specific APEX builtin by index.
+ Returns the information about the builtin. */
+
+void
+arcv_apex_get_builtin_info (int index, const char **fn_name,
+ const char **insn_name, unsigned int *opcode,
+ unsigned int *insn_formats)
+{
+ gcc_assert (index >= 0 && index < arcv_apex_builtins_limit);
+ const struct arcv_apex_builtin_description *d = &arcv_apex_builtins[index];
+
+ *fn_name = d->name;
+ *insn_name = d->insn_name;
+ *opcode = d->opcode;
+ *insn_formats = d->insn_formats;
+}
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 886f297ddca7..78835ee595f6 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -814,6 +814,12 @@ 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, unsigned int);
+extern void arcv_apex_lto_register_builtin (const char *, const char *,
+ unsigned int, unsigned int,
+ bool, tree);
+extern int arcv_apex_get_builtin_count (void);
+extern void arcv_apex_get_builtin_info (int, const char **, const char **,
+ unsigned int *, unsigned int *);
#ifdef RTX_CODE
extern const char*
diff --git a/gcc/config/riscv/t-riscv b/gcc/config/riscv/t-riscv
index d9f2f4aa84ce..c52d5bc2a06b 100644
--- a/gcc/config/riscv/t-riscv
+++ b/gcc/config/riscv/t-riscv
@@ -18,6 +18,13 @@ riscv-builtins.o: $(srcdir)/config/riscv/riscv-builtins.cc $(CONFIG_H) \
$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
$(srcdir)/config/riscv/riscv-builtins.cc
+riscv-apex-lto.o: $(srcdir)/config/riscv/riscv-apex-lto.cc \
+ $(CONFIG_H) $(SYSTEM_H) coretypes.h $(BACKEND_H) $(TREE_H) $(GIMPLE_H) \
+ $(CGRAPH_H) $(LTO_STREAMER_H) $(IPA_UTILS_H) $(DATA_STREAMER_H) \
+ stringpool.h attribs.h
+ $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
+ $(srcdir)/config/riscv/riscv-apex-lto.cc
+
riscv-vector-builtins.o: $(srcdir)/config/riscv/riscv-vector-builtins.cc \
$(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) $(RTL_H) $(TM_P_H) \
memmodel.h insn-codes.h $(OPTABS_H) $(RECOG_H) $(DIAGNOSTIC_H) $(EXPR_H) \
diff --git a/gcc/lto-section-in.cc b/gcc/lto-section-in.cc
index 1dd9520137a1..2785268be4d2 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 8efda29f7676..06fa5583605c 100644
--- a/gcc/lto-streamer-out.cc
+++ b/gcc/lto-streamer-out.cc
@@ -3452,6 +3452,11 @@ produce_asm_for_decls (void)
/* Write command line opts. */
lto_write_options ();
+#ifdef RISCV_APEX
+ /* Write RISC-V APEX intrinsic information. */
+ 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 8c1d2d4947d6..edfe0ca03144 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 RISCV_APEX
+/* In config/riscv/riscv-apex-lto.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 64631201939a..688b3c830fa3 100644
--- a/gcc/lto/lto-common.cc
+++ b/gcc/lto/lto-common.cc
@@ -2961,6 +2961,11 @@ read_cgraph_and_symbols (unsigned nfiles, const char **fnames)
else
ipa_read_summaries ();
+#ifdef RISCV_APEX
+ /* Read RISC-V APEX intrinsic information. */
+ 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 4c3ba9694775..d6c585a656bd 100644
--- a/gcc/testsuite/gcc.target/riscv/apex/apex.exp
+++ b/gcc/testsuite/gcc.target/riscv/apex/apex.exp
@@ -14,27 +14,59 @@
# along with GCC; see the file COPYING3. If not see
# .
-# GCC testsuite that uses the `dg.exp' driver.
+# GCC testsuite for RISC-V APEX intrinsics.
+# This file handles both regular single-file tests and multi-file LTO tests.
# Exit immediately if this isn't a RISC-V target.
if ![istarget riscv*-*-*] then {
return
}
-# Load support procs.
+# Load all required libraries.
load_lib gcc-dg.exp
+load_lib standard.exp
+load_lib gcc.exp
+load_lib lto.exp
+load_lib scanltrans.exp
global DEFAULT_CFLAGS
if ![info exists DEFAULT_CFLAGS] then {
set DEFAULT_CFLAGS " -ansi -pedantic-errors"
}
-# Initialize `dg'.
+#
+# Run regular single-file tests
+#
dg-init
-# Main loop.
-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]] \
- "" $DEFAULT_CFLAGS
+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 "" $DEFAULT_CFLAGS
-# All done.
-dg-finish
\ No newline at end of file
+dg-finish
+
+#
+# Run multi-file LTO tests (if LTO is supported)
+#
+if { [check_effective_target_lto] } {
+ gcc_init
+ lto_init no-mathlib
+
+ set sid "riscv_apex_lto"
+
+ foreach src [lsort [find $srcdir/$subdir arcv-apex-lto-*_0.c]] {
+ if ![runtest_file_p $runtests $src] then {
+ continue
+ }
+
+ lto-execute $src $sid
+ }
+
+ 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 000000000000..263eec7f545b
--- /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 000000000000..9ec7e518ccb6
--- /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 000000000000..c035780c9006
--- /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 instruction name: '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 000000000000..7df812a4cd8b
--- /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 000000000000..866c7f509c23
--- /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: 0xe2 vs 0xe8" "" { 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 000000000000..4efb805f31f8
--- /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 000000000000..472459acba9e
--- /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 instruction name: '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: 0xe1 vs 0xe2" "" { 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 000000000000..1134e68e3550
--- /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 000000000000..fb0ae276394f
--- /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 000000000000..e02a0cd5b1e1
--- /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 000000000000..c384a5044259
--- /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 000000000000..493318bb53d3
--- /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 000000000000..3d8de370c544
--- /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 000000000000..9401957a0f9c
--- /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 000000000000..bcd9f68765aa
--- /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 000000000000..66757f6bbe5d
--- /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 000000000000..ac522c3101cc
--- /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 000000000000..9915f2306bfd
--- /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);
+}
diff --git a/gcc/testsuite/lib/lto.exp b/gcc/testsuite/lib/lto.exp
index 81519b580e13..7eb8dd26e177 100644
--- a/gcc/testsuite/lib/lto.exp
+++ b/gcc/testsuite/lib/lto.exp
@@ -323,6 +323,7 @@ proc lto-link-and-maybe-run { testname objlist dest optall optfile optstr } {
global tool
global compile_type
global board_info
+ global dg_lto_has_error
upvar dg-messages-by-file dg-messages-by-file
@@ -370,8 +371,31 @@ proc lto-link-and-maybe-run { testname objlist dest optall optfile optstr } {
# Prune unimportant visibility warnings before checking output.
set comp_output [lto_prune_warns $comp_output]
- if ![${tool}_check_compile "$testcase $testname link" $optstr \
- $dest $comp_output] then {
+ # Check if link succeeded
+ # If we expect errors, check manually without reporting
+ if { [info exists dg_lto_has_error] && $dg_lto_has_error } {
+ verbose "lto.exp: dg-lto-error present, checking for expected link failure" 2
+ # Check if link failed (executable not created or output has errors)
+ set link_failed [expr {![file_on_host exists $dest] || $comp_output != ""}]
+ if { $link_failed } {
+ # Link failure is expected due to dg-lto-error - pass the test
+ verbose "lto.exp: link failed as expected" 2
+ pass "$testcase $testname link $optstr"
+ } else {
+ # Link succeeded when it should have failed
+ fail "$testcase $testname link $optstr (expected failure)"
+ }
+ if { ![string compare "execute" $compile_type] } {
+ unresolved "$testcase $testname execute $optstr"
+ }
+ return
+ }
+
+ # Normal link checking (no dg-lto-error)
+ set link_ok [${tool}_check_compile "$testcase $testname link" $optstr \
+ $dest $comp_output]
+
+ if { !$link_ok } {
if { ![string compare "execute" $compile_type] } {
unresolved "$testcase $testname execute $optstr"
}
@@ -403,17 +427,25 @@ proc lto-can-handle-directive { op } {
# dg-warning and dg-message append to dg-messages.
upvar dg-messages dg-messages
+ # Track if we have dg-lto-error to expect link failure
+ global dg_lto_has_error
# A list of directives to recognize, and a list of directives
# to remap them to.
# For example, "dg-lto-warning" is implemented by calling "dg-warning".
- set directives { dg-lto-warning dg-lto-message dg-lto-note }
- set remapped_directives { dg-warning dg-message dg-note }
+ set directives { dg-lto-warning dg-lto-message dg-lto-note dg-lto-error }
+ set remapped_directives { dg-warning dg-message dg-note dg-error }
set idx [lsearch -exact $directives $cmd]
if { $idx != -1 } {
verbose "remapping from: $op" 4
+ # Mark that we have a dg-lto-error directive
+ if { $cmd == "dg-lto-error" } {
+ set dg_lto_has_error 1
+ verbose "lto.exp: dg-lto-error detected, expecting link failure" 2
+ }
+
set remapped_cmd [lindex $remapped_directives $idx]
set op [lreplace $op 0 0 $remapped_cmd]
@@ -629,6 +661,7 @@ proc lto-execute-1 { src1 sid } {
global LTO_OPTIONS
global dg-final-code
global testname_with_flags
+ global dg_lto_has_error
# Get extra flags for this test from the primary source file, and
# process other dg-* options that this suite supports. Warn about
@@ -637,6 +670,7 @@ proc lto-execute-1 { src1 sid } {
set compile_type "run"
set dg-do-what [list ${dg-do-what-default} "" P]
array set dg-messages-by-file [list]
+ set dg_lto_has_error 0
set extra_flags(0) [lto-get-options-main $src1]
set compile_xfail(0) ""
diff --git a/gcc/testsuite/lib/scanltrans.exp b/gcc/testsuite/lib/scanltrans.exp
index abfb2e4f4eb3..fd74669a7769 100644
--- a/gcc/testsuite/lib/scanltrans.exp
+++ b/gcc/testsuite/lib/scanltrans.exp
@@ -78,3 +78,123 @@ foreach ir { tree rtl } {
}
}]
}
+
+# Assembly scanning procedures for LTRANS output.
+# These scan the .ltrans*.s assembly files produced during LTO compilation.
+
+# Look for a pattern in all .ltrans*.s files produced by the LTO compiler.
+# This combines results from all LTRANS units for the current test.
+proc scan-ltrans-assembler { args } {
+ if { [llength $args] < 1 } {
+ error "scan-ltrans-assembler: too few arguments"
+ return
+ }
+ if { [llength $args] > 2 } {
+ error "scan-ltrans-assembler: too many arguments"
+ return
+ }
+ if { [llength $args] >= 2 } {
+ switch [dg-process-target [lindex $args 1]] {
+ "S" { }
+ "N" { return }
+ "F" { setup_xfail "*-*-*" }
+ "P" { }
+ }
+ }
+
+ set testcase [testname-for-summary]
+ set pattern [lindex $args 0]
+ set pp_pattern [make_pattern_printable $pattern]
+
+ # Get the testcase name (for LTO tests, this is the executable name like
+ # gcc-target-riscv-apex-arcv-apex-lto-test0-01.exe)
+ set testname [lindex $testcase 0]
+
+ # Strip .exe extension if present
+ set basename [file rootname $testname]
+
+ # Match only ltrans files for this specific test
+ # Pattern: basename.ltrans*.s (e.g., test-01.ltrans0.ltrans.s)
+ set output_files [dg_glob_remote "${basename}.ltrans*.s"]
+
+ if { $output_files == "" } {
+ verbose -log "$testcase: no ${basename}.ltrans*.s files found"
+ unresolved "$testcase scan-ltrans-assembler $pp_pattern"
+ return
+ }
+
+ set total_matches 0
+ foreach file $output_files {
+ set fd [open $file r]
+ set text [read $fd]
+ close $fd
+ set matches [regexp -all -- $pattern $text]
+ set total_matches [expr $total_matches + $matches]
+ }
+
+ if { $total_matches > 0 } {
+ pass "$testcase scan-ltrans-assembler $pp_pattern"
+ } else {
+ fail "$testcase scan-ltrans-assembler $pp_pattern"
+ }
+}
+set_required_options_for scan-ltrans-assembler
+
+# Check that a pattern appears exactly N times across all .ltrans.s files for this test.
+proc scan-ltrans-assembler-times { args } {
+ if { [llength $args] < 2 } {
+ error "scan-ltrans-assembler-times: too few arguments"
+ return
+ }
+ if { [llength $args] > 3 } {
+ error "scan-ltrans-assembler-times: too many arguments"
+ return
+ }
+ if { [llength $args] >= 3 } {
+ switch [dg-process-target [lindex $args 2]] {
+ "S" { }
+ "N" { return }
+ "F" { setup_xfail "*-*-*" }
+ "P" { }
+ }
+ }
+
+ set testcase [testname-for-summary]
+ set pattern [lindex $args 0]
+ set times [lindex $args 1]
+ set pp_pattern [make_pattern_printable $pattern]
+
+ # Get the testcase name (for LTO tests, this is the executable name like
+ # gcc-target-riscv-apex-arcv-apex-lto-test0-01.exe)
+ set testname [lindex $testcase 0]
+
+ # Strip .exe extension if present
+ set basename [file rootname $testname]
+
+ # Match only ltrans files for this specific test
+ # Pattern: basename.ltrans*.s (e.g., test-01.ltrans0.ltrans.s)
+ set output_files [dg_glob_remote "${basename}.ltrans*.s"]
+
+ if { $output_files == "" } {
+ verbose -log "$testcase: no ${basename}.ltrans*.s files found"
+ unresolved "$testcase scan-ltrans-assembler-times $pp_pattern $times"
+ return
+ }
+
+ set total_matches 0
+ foreach file $output_files {
+ set fd [open $file r]
+ set text [read $fd]
+ close $fd
+ set matches [regexp -all -- $pattern $text]
+ set total_matches [expr $total_matches + $matches]
+ }
+
+ if { $total_matches == $times } {
+ pass "$testcase scan-ltrans-assembler-times $pp_pattern $times"
+ } else {
+ verbose -log "$testcase: $pp_pattern found $total_matches times"
+ fail "$testcase scan-ltrans-assembler-times $pp_pattern $times"
+ }
+}
+set_required_options_for scan-ltrans-assembler-times