On Mon, 25 May 2026, Kito Cheng wrote:

> Hi Luis:
> 
> Really happy to see you address this issue correctly :)
> 
> I am not expert on the LTO part, but I would suggest you to split this
> into two part:
> 1) Implement a target hook to generalized the interface for target
> specific data for LTO section
> 2) Introduce APEX/RISC-V specific implementation
> 
> I think introducing target specific on generic code is OK, but that
> should introduce via adding new hook.
> 
> cc Richard B. and Jan H. for more input on this since this is out of
> RISC-V scope

Please give an overview of what APEX is and why LTO currently
cannot handle it.

Richard.

> 
> Luis Silva <[email protected]> 於 2026年5月25日週一 下午10:26寫道:
> 
> >
> > 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
> >
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to