Skip to content

Commit 0cfd94c

Browse files
author
Luis Silva
committed
RISC-V: Add #pragma intrinsic frontend for APEX
This patch implements the C frontend support for APEX custom instructions via the #pragma intrinsic directive. This allows users to bind existing C function declarations to APEX custom instruction opcodes at compile time. Syntax: #pragma intrinsic(fn_name, "mnemonic", opcode, "format"...) Where: - fn_name: Previously declared C function to register as intrinsic - mnemonic: Assembly instruction name (normalized to lowercase) - opcode: Instruction opcode (0-255 for XD, 0-63 for XS, 0-31 for XI/XC) - format: One or more of "XD", "XS", "XI", "XC", or "side_effect" The pragma parser validates identifier syntax, looks up the function declaration via lookup_name, extracts format flags from the pragma arguments, and delegates to arcv_apex_register_builtin for actual registration. Format classes: - XD: Default register-register format (up to 3 registers, 8-bit opcode) - XS: Two-operand with 8-bit signed immediate (6-bit opcode) - XI: One-operand with 12-bit signed immediate (5-bit opcode) - XC: Accumulator format where destination is also source0 (5-bit opcode) - side_effect: Marks instruction as volatile to prevent optimization If no format is specified, the compiler infers it from the opcode value and function signature (see arcv_apex_infer_format in the middle-end patch). Example: int custom_add (int a, int b); #pragma intrinsic(custom_add, "myadd", 42, "XD") gcc/ChangeLog: * config/riscv/riscv-c.cc (arcv_apex_lookup_function): New helper to lookup function declarations. (arcv_apex_valid_identifier_p): New helper to validate identifiers. (arcv_apex_pragma_intrinsic): New function to handle #pragma intrinsic. (riscv_check_builtin_call): Add APEX builtin call validation. (riscv_resolve_overloaded_builtin): Add APEX builtin resolution. (riscv_register_pragmas): Register #pragma intrinsic handler. * config/riscv/riscv-protos.h (arcv_apex_register_builtin): Declare. gcc/testsuite/ChangeLog: * gcc.target/riscv/apex/apex.exp: New test. * gcc.target/riscv/apex/arcv-apex-err1.c: New test. * gcc.target/riscv/apex/arcv-apex-err10.c: New test. * gcc.target/riscv/apex/arcv-apex-err11.c: New test. * gcc.target/riscv/apex/arcv-apex-err12.c: New test. * gcc.target/riscv/apex/arcv-apex-err13.c: New test. * gcc.target/riscv/apex/arcv-apex-err14.c: New test. * gcc.target/riscv/apex/arcv-apex-err15.c: New test. * gcc.target/riscv/apex/arcv-apex-err16-rpx.c: New test. * gcc.target/riscv/apex/arcv-apex-err16.c: New test. * gcc.target/riscv/apex/arcv-apex-err17-rpx.c: New test. * gcc.target/riscv/apex/arcv-apex-err17.c: New test. * gcc.target/riscv/apex/arcv-apex-err18.c: New test. * gcc.target/riscv/apex/arcv-apex-err19.c: New test. * gcc.target/riscv/apex/arcv-apex-err2.c: New test. * gcc.target/riscv/apex/arcv-apex-err20.c: New test. * gcc.target/riscv/apex/arcv-apex-err3.c: New test. * gcc.target/riscv/apex/arcv-apex-err4.c: New test. * gcc.target/riscv/apex/arcv-apex-err5.c: New test. * gcc.target/riscv/apex/arcv-apex-err6.c: New test. * gcc.target/riscv/apex/arcv-apex-err7.c: New test. * gcc.target/riscv/apex/arcv-apex-err8.c: New test. * gcc.target/riscv/apex/arcv-apex-err9.c: New test. * 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. * gcc.target/riscv/apex/arcv-apex-test1.c: New test. * gcc.target/riscv/apex/arcv-apex-test2.c: New test. * gcc.target/riscv/apex/arcv-apex-test3.c: New test. * gcc.target/riscv/apex/arcv-apex-test4.c: New test. * gcc.target/riscv/apex/arcv-apex-test5.c: New test. * gcc.target/riscv/apex/arcv-apex-test6.c: New test. * gcc.target/riscv/apex/arcv-apex-test7.c: New test. * gcc.target/riscv/apex/arcv-apex-test8.c: New test. * gcc.target/riscv/apex/arcv-apex-test9.c: New test. * gcc.target/riscv/apex/arcv-apex-test_XC_1.c: New test. * gcc.target/riscv/apex/arcv-apex-test_XD_1.c: New test. * gcc.target/riscv/apex/arcv-apex-test_XI_1.c: New test. * gcc.target/riscv/apex/arcv-apex-test_XS_1.c: New test. Signed-off-by: Luis Silva <luiss@synopsys.com>
1 parent 1296859 commit 0cfd94c

56 files changed

Lines changed: 1250 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

gcc/config/riscv/riscv-c.cc

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,228 @@ along with GCC; see the file COPYING3. If not see
3131
#include "target.h"
3232
#include "tm_p.h"
3333
#include "riscv-subset.h"
34+
#include "stringpool.h" /* Used for "get_identifier ()" */
35+
#include "arcv.h"
3436

3537
#define builtin_define(TXT) cpp_define (pfile, TXT)
3638

39+
/* Look up the user-defined function declaration by name.
40+
41+
Given a function name as a string, this function returns the corresponding
42+
tree node for its declaration, if it exists. If the function is not declared
43+
in the current scope, an error is reported. */
44+
45+
tree
46+
arcv_apex_lookup_function (const char *fn_name)
47+
{
48+
/* Convert the raw string to an interned IDENTIFIER_NODE. */
49+
tree id = get_identifier (fn_name);
50+
51+
/* Try the current scope (and outer scopes) for a matching declaration. */
52+
tree fndecl = lookup_name (id);
53+
54+
/* Verify that we really found a function. */
55+
if (fndecl == NULL_TREE || TREE_CODE (fndecl) != FUNCTION_DECL)
56+
{
57+
error_at (input_location, "%qs is not declared as a function", fn_name);
58+
return NULL_TREE;
59+
}
60+
61+
return fndecl;
62+
}
63+
64+
/* Return true if S is a valid APEX-intrinsic identifier.
65+
Rules:
66+
- The identifier must begin with an ASCII letter (A–Z or a–z) or
67+
an underscore ('_').
68+
- All remaining characters must be ASCII letters, digits (0–9) or
69+
underscores ('_').
70+
- Other symbols are not allowed.
71+
Examples of valid identifiers: "add", "_bar", "Mul3", "op123".
72+
Examples of invalid identifiers: "1foo", "baz.qux", "foo%". */
73+
74+
static bool
75+
arcv_apex_valid_identifier_p (const char *s)
76+
{
77+
if (!s || !s[0])
78+
return false;
79+
80+
if (!(ISALPHA (s[0]) || s[0] == '_'))
81+
return false;
82+
83+
for (const char *p = s + 1; *p; ++p)
84+
if (!ISALNUM (*p) && *p != '_')
85+
return false;
86+
87+
return true;
88+
}
89+
90+
/* Parses and handles `#pragma intrinsic` for APEX custom instructions.
91+
92+
Syntax:
93+
#pragma intrinsic(fn_name, "mnemonic", opcode, "format"...)
94+
95+
Arguments:
96+
- fn_name: C function identifier to register as an intrinsic builtin
97+
- mnemonic: Assembly instruction name (e.g., "add", "mul")
98+
- opcode: Instruction opcode (0-255 for XD, 0-63 for XS, 0-31 for XI/XC)
99+
- format: One or more format specifiers:
100+
- "XD": Default format (register-register, up to 3 operands)
101+
- "XS": Two-operand with 8-bit signed immediate
102+
- "XI": One-operand with immediate
103+
- "XC": Accumulator format (rd is also src0)
104+
- "side_effect": Mark as volatile (prevents optimization) */
105+
106+
static void
107+
arcv_apex_pragma_intrinsic (cpp_reader *)
108+
{
109+
110+
enum cpp_ttype token;
111+
tree x;
112+
113+
/* Parse open Parenthesis '(' */
114+
if (pragma_lex (&x) != CPP_OPEN_PAREN)
115+
{
116+
error ("missing %<(%< after %<#pragma intrinsic%<");
117+
return;
118+
}
119+
120+
/* Parse the function identifier to be marked as intrinsic. */
121+
if (pragma_lex (&x) != CPP_NAME)
122+
{
123+
warning (0, "expected identifier in '#pragma intrinsic' - ignoring");
124+
return;
125+
}
126+
const char *fn_name = IDENTIFIER_POINTER (x);
127+
128+
/* Note: We intentionally do not validate the presence of commas strictly.
129+
If a comma is missing after the function identifier or after the
130+
instruction name, parsing will continue, but the following token
131+
will not match the expected type (e.g., string or number), and we’ll
132+
always end up reporting a missing or invalid attribute.
133+
134+
Because of this, explicitly diagnosing missing commas would be redundant
135+
and would only clutter the error output. This behavior is intentional. */
136+
pragma_lex (&x);
137+
138+
/* Parse the mnemonic string, e.g., "add", "mul". */
139+
token = pragma_lex (&x);
140+
const char *mnemonic_raw = "";
141+
if (token == CPP_STRING)
142+
mnemonic_raw = TREE_STRING_POINTER (x);
143+
else if (token == CPP_NAME)
144+
mnemonic_raw = IDENTIFIER_POINTER (x);
145+
146+
/* If the mnemonic is empty or absent, report an error. */
147+
if (mnemonic_raw[0] == '\0')
148+
error ("pragma intrinsic: APEX attribute 'name' is missing");
149+
/* Reject mnemonics that do not follow APEX identifier rules. */
150+
else if (!arcv_apex_valid_identifier_p (mnemonic_raw))
151+
error ("pragma intrinsic: APEX name %qs is not lexically valid",
152+
mnemonic_raw);
153+
154+
/* Convert mnemonic to lowercase to normalize it for the assembler. */
155+
char *mnemonic = xstrdup (mnemonic_raw);
156+
for (char *p = mnemonic; *p; p++)
157+
*p = TOLOWER (*p);
158+
159+
/* See note above regarding comma handling. */
160+
pragma_lex (&x);
161+
162+
/* Parse the opcode value (must be an integer). */
163+
if (pragma_lex (&x) != CPP_NUMBER)
164+
{
165+
error ("pragma intrinsic: APEX attribute 'opcode' is missing");
166+
free (mnemonic);
167+
return;
168+
}
169+
unsigned HOST_WIDE_INT opcode = TREE_INT_CST_LOW (x);
170+
171+
/* Start with no formats selected. If none are explicitly provided,
172+
formats will be determined later at "arcv_resolve_insn_format ()". */
173+
unsigned int format_flags = APEX_NONE;
174+
175+
const char *attribute = NULL;
176+
177+
/* Parse zero or more instruction format specifiers. */
178+
while (1)
179+
{
180+
token = pragma_lex (&x);
181+
182+
/* Break if end of argument list reached. */
183+
if (token == CPP_CLOSE_PAREN)
184+
break;
185+
186+
/* Expect comma before each format string. */
187+
if (token != CPP_COMMA)
188+
{
189+
error ("pragma intrinsic: expected %<,%> or %<)%>");
190+
return;
191+
}
192+
193+
token = pragma_lex (&x);
194+
195+
switch (token)
196+
{
197+
case CPP_STRING:
198+
attribute = TREE_STRING_POINTER (x);
199+
break;
200+
case CPP_NAME:
201+
attribute = IDENTIFIER_POINTER (x);
202+
break;
203+
default:
204+
error ("pragma intrinsic: expected attribute");
205+
return;
206+
}
207+
208+
/* On first valid format specifier, override the default (NONE). */
209+
if (strcmp (attribute, "XD") == 0)
210+
format_flags |= APEX_XD;
211+
else if (strcmp (attribute, "XS") == 0)
212+
format_flags |= APEX_XS;
213+
else if (strcmp (attribute, "XI") == 0)
214+
format_flags |= APEX_XI;
215+
else if (strcmp (attribute, "XC") == 0)
216+
format_flags |= APEX_XC;
217+
else if (strcmp (attribute, "side_effect") == 0)
218+
format_flags |= APEX_VOLATILE;
219+
else
220+
{
221+
error ("pragma intrinsic: APEX attribute %qs is not recognized",
222+
attribute);
223+
free (mnemonic);
224+
return;
225+
}
226+
227+
}
228+
229+
/* Check for incompatible APEX instruction format combinations.
230+
The XI format cannot be combined with XD, XS, or XC. */
231+
if ((format_flags & APEX_XI)
232+
&& (format_flags & (APEX_XD | APEX_XS | APEX_XC)))
233+
{
234+
error ("pragma intrinsic: APEX formats 'XI' and '%s' are not compatible",
235+
attribute);
236+
free (mnemonic);
237+
return;
238+
}
239+
240+
/* Lookup the user-defined function declaration of the APEX intrinsic. */
241+
tree fndecl = arcv_apex_lookup_function (fn_name);
242+
243+
/* If lookup failed, clean up and return early. */
244+
if (fndecl == NULL_TREE)
245+
{
246+
free (mnemonic);
247+
return;
248+
}
249+
250+
/* Register the specified function as an APEX intrinsic. */
251+
arcv_apex_register_builtin (fndecl, fn_name, mnemonic, format_flags, opcode);
252+
253+
free (mnemonic);
254+
}
255+
37256
static int
38257
riscv_ext_version_value (unsigned major, unsigned minor)
39258
{
@@ -271,6 +490,9 @@ riscv_check_builtin_call (location_t loc, vec<location_t> arg_loc, tree fndecl,
271490
case RISCV_BUILTIN_VECTOR:
272491
return riscv_vector::check_builtin_call (loc, arg_loc, subcode,
273492
fndecl, nargs, args);
493+
494+
case RISCV_BUILTIN_APEX:
495+
return true;
274496
}
275497
gcc_unreachable ();
276498
}
@@ -297,6 +519,8 @@ riscv_resolve_overloaded_builtin (location_t loc, tree fndecl,
297519
new_fndecl = riscv_vector::resolve_overloaded_builtin (loc, subcode,
298520
fndecl, arglist);
299521
break;
522+
case RISCV_BUILTIN_APEX:
523+
break;
300524
default:
301525
gcc_unreachable ();
302526
}
@@ -315,6 +539,6 @@ riscv_register_pragmas (void)
315539
{
316540
targetm.resolve_overloaded_builtin = riscv_resolve_overloaded_builtin;
317541
targetm.check_builtin_call = riscv_check_builtin_call;
318-
targetm.target_option.pragma_parse = riscv_pragma_target_parse;
319542
c_register_pragma ("riscv", "intrinsic", riscv_pragma_intrinsic);
543+
c_register_pragma_with_expansion (0, "intrinsic", arcv_apex_pragma_intrinsic);
320544
}

gcc/config/riscv/riscv-protos.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,8 @@ extern const char *th_asm_output_opcode (FILE *asm_out_file, const char *p);
835835
extern const char* arcv_apex_asm_mnemonic (rtx, bool);
836836
extern bool arcv_apex_format_enabled_p (unsigned int, unsigned int);
837837
extern rtx arcv_apex_expand_builtin (unsigned int, tree, rtx);
838+
extern void arcv_apex_register_builtin (tree, const char *, const char *,
839+
unsigned int, unsigned int);
838840

839841
/* Routines implemented in arcv.cc. */
840842
extern void arcv_apex_emit_ext_directive (const char *, int, unsigned int);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright (C) 2024-2025 Free Software Foundation, Inc.
2+
3+
# This program is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation; either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with GCC; see the file COPYING3. If not see
15+
# <http://www.gnu.org/licenses/>.
16+
17+
# GCC testsuite for RISC-V APEX custom instructions.
18+
19+
# Exit immediately if this isn't a RISC-V target.
20+
if ![istarget riscv*-*-*] then {
21+
return
22+
}
23+
24+
# Load support libraries.
25+
load_lib gcc-dg.exp
26+
load_lib standard.exp
27+
load_lib lto.exp
28+
load_lib scanltrans.exp
29+
30+
# Run regular single-file tests.
31+
dg-init
32+
set tests [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]]
33+
set non_lto_tests [list]
34+
foreach test $tests {
35+
if {![string match "*arcv-apex-lto-*" $test]} {
36+
lappend non_lto_tests $test
37+
}
38+
}
39+
dg-runtest $non_lto_tests "" ""
40+
dg-finish
41+
42+
# Run multi-file LTO tests if supported.
43+
if [check_effective_target_lto] {
44+
lto_init no-mathlib
45+
46+
foreach src [lsort [find $srcdir/$subdir arcv-apex-lto-*_0.c]] {
47+
if [runtest_file_p $runtests $src] {
48+
lto-execute $src "riscv_apex_lto"
49+
}
50+
}
51+
52+
lto_finish
53+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* { dg-do compile } */
2+
3+
int foo (int,int);
4+
#pragma intrinsic (foo,FOP,10,Key=>"bar") /* { dg-error "pragma intrinsic: APEX attribute 'Key' is not recognized" } */
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* { dg-do compile } */
2+
3+
int foo (int);
4+
#pragma intrinsic (foo, FOP, 10, "XI")
5+
6+
int
7+
main (void)
8+
{
9+
int a;
10+
a = foo (-2049); /* { dg-error "argument value -2049 is outside the valid range \\\[-2048, 2047\\\]" } */
11+
a += foo (2048); /* { dg-error "argument value 2048 is outside the valid range \\\[-2048, 2047\\\]" } */
12+
a += foo (1000000); /* { dg-error "argument value 1000000 is outside the valid range \\\[-2048, 2047\\\]" } */
13+
return a;
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* { dg-do compile } */
2+
/* { dg-options "-O0" } */
3+
4+
int foo (int, int);
5+
#pragma intrinsic (foo, FOP, 10, "XC")
6+
7+
int main() {
8+
int x = 12;
9+
int y = foo (x, 100000); /* { dg-error "argument value 100000 is outside the valid range \\\[-2048, 2047\\\]" } */
10+
return foo (y, x); /* { dg-error "argument to 'foo' must be a constant integer" } */
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* { dg-do compile } */
2+
/* { dg-options "-O0" } */
3+
4+
int foo (int, int);
5+
#pragma intrinsic (foo, FOP, 10, "XS")
6+
7+
int main() {
8+
int x = 12;
9+
int y = foo (x, 100000); /* { dg-error "argument value 100000 is outside the valid range \\\[-128, 127\\\]" } */
10+
return foo (y, x); /* { dg-error "argument to 'foo' must be a constant integer" } */
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* { dg-do compile } */
2+
/* { dg-options "-O0" } */
3+
4+
void foo (int, int);
5+
int bar (int);
6+
7+
#pragma intrinsic (foo, FOP, 10, "XS")
8+
#pragma intrinsic (bar, BAR, 11, "XI")
9+
10+
int main() {
11+
foo (12, 100000); /* { dg-error "argument value 100000 is outside the valid range \\\[-128, 127\\\]" } */
12+
return bar (100000); /* { dg-error "argument value 100000 is outside the valid range \\\[-2048, 2047\\\]" } */
13+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* { dg-do compile } */
2+
3+
void foo (int, int);
4+
#pragma intrinsic (foo, FOP, 10, "XC") /* { dg-error "pragma intrinsic: APEX function 'foo' must return the same type as the first parameter for the 'XC' format class" } */
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* { dg-do compile } */
2+
3+
int foo (int);
4+
int bar0 ();
5+
int bar3 (int,int,int);
6+
7+
#pragma intrinsic (foo, FOP, 10, "XS") /* { dg-error "pragma intrinsic: APEX function 'foo' must have 2 scalar parameter\\(s\\) for the 'XS' format class" } */
8+
#pragma intrinsic (bar0, bar0, 11, "XS") /* { dg-error "pragma intrinsic: APEX function 'bar0' must have 2 scalar parameter\\(s\\) for the 'XS' format class" } */
9+
#pragma intrinsic (bar3, bar3, 12, "XS") /* { dg-warning "pragma intrinsic: Associated function can have no more than 2 parameters" } */

0 commit comments

Comments
 (0)