blob: 99e5a3e221453bd2b81cc1f52d5e08123f120ecd [file]
/* Subroutines used for LoongArch code generation.
Copyright (C) 2025-2026 Free Software Foundation, Inc.
Contributed by Loongson Ltd.
Based on AArch64 target for GNU compiler.
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
<http://www.gnu.org/licenses/>. */
#define IN_TARGET_CODE 1
#define INCLUDE_STRING
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "target.h"
#include "tree.h"
#include "tm_p.h"
#include "diagnostic.h"
#include "opts.h"
/* Enum describing the various ways we can handle attributes.
In many cases we can reuse the generic option handling machinery. */
enum loongarch_attr_opt_type
{
loongarch_attr_mask, /* Attribute should set a bit in target_flags. */
loongarch_attr_enum, /* Attribute sets an enum variable. */
loongarch_attr_bool /* Attribute sets or unsets a boolean variable. */
};
/* Describes the priority of each feature. The larger the value, the higher
the priority. The priority setting rule is vector priority.
The highest priority currently is "-mlasx".
The second highest is "-march=la64v1.1" (lsx and la64v1.1 enabled
instructions).
The third highest is "-mlsx".
*/
enum features_prio
{
LA_PRIO_NONE = 0,
LA_PRIO_LOONGARCH64,
LA_PRIO_STRICT_ALIGN,
LA_PRIO_FRECIPE,
LA_PRIO_DIV32 = LA_PRIO_FRECIPE,
LA_PRIO_LAM_BH = LA_PRIO_FRECIPE,
LA_PRIO_LAMCAS = LA_PRIO_FRECIPE,
LA_PRIO_SCQ = LA_PRIO_FRECIPE,
LA_PRIO_LD_SEQ_SA = LA_PRIO_FRECIPE,
LA_PRIO_LSX,
LA_PRIO_LA64V1_0,
LA_PRIO_LA64V1_1,
LA_PRIO_LASX,
LA_PRIO_MAX
};
/* All the information needed to handle a target attribute.
NAME is the name of the attribute.
ATTR_TYPE specifies the type of behavior of the attribute as described
in the definition of enum loongarch_attr_opt_type.
ALLOW_NEG is true if the attribute supports a "no-" form.
OPT_NUM is the enum specifying the option that the attribute modifies.
This is needed for attributes that mirror the behavior of a command-line
option, that is it has ATTR_TYPE loongarch_attr_mask. */
struct loongarch_attribute_info
{
const char *name;
unsigned int opt_mask;
enum loongarch_attr_opt_type attr_type;
enum opt_code opt_num;
bool allow_neg;
const loongarch_fmv_feature_mask feat_mask;
const unsigned int arch_ver;
enum features_prio priority;
};
/* Construct a loongarch_attributes from the given arguments.
OPTS is the name of the compilation option after the "-m" string.
OPTNUM is the opt_code corresponding to the compilation option.
OPTMASK is the mask corresponding to the mutation option. If the
compilation option does not have a corresponding mask, pass 0.
*/
#define LARCH_ATTR_MASK(OPTS, OPTNUM, OPTMASK, FEATMASK, PRIO) \
{ \
OPTS, OPTMASK, loongarch_attr_mask, OPTNUM, true, 1ULL << FEATMASK, \
N_ARCH_TYPES, PRIO \
},
#define LARCH_ATTR_ENUM(OPTS, OPTNUM, PRIO) \
{ \
OPTS, 0, loongarch_attr_enum, OPTNUM, false, 0, N_ARCH_TYPES, PRIO \
},
#define LARCH_ATTR_BOOL(OPTS, OPTNUM, OPTMASK, FEATMASK, ARCH_V, PRIO) \
{ \
OPTS, OPTMASK, loongarch_attr_bool, OPTNUM, true, 1ULL << FEATMASK, ARCH_V, \
PRIO \
},
/* The target attributes that we support. */
static const struct loongarch_attribute_info loongarch_attributes[] =
{
LARCH_ATTR_MASK ("strict-align", OPT_mstrict_align, MASK_STRICT_ALIGN,
FEAT_UAL, LA_PRIO_STRICT_ALIGN)
LARCH_ATTR_ENUM ("cmodel", OPT_mcmodel_, LA_PRIO_NONE)
LARCH_ATTR_ENUM ("arch", OPT_march_, LA_PRIO_NONE)
LARCH_ATTR_ENUM ("tune", OPT_mtune_, LA_PRIO_NONE)
LARCH_ATTR_BOOL ("lsx", OPT_mlsx, 0, FEAT_LSX, ARCH_LA64V1_0, LA_PRIO_LSX)
LARCH_ATTR_BOOL ("lasx", OPT_mlasx, 0, FEAT_LASX | FEAT_LSX, 0, LA_PRIO_LASX)
#include "loongarch-evol-attr.def"
{ NULL, 0, loongarch_attr_bool, OPT____, false, 0, N_ARCH_TYPES, LA_PRIO_NONE }
};
#undef LARCH_ATTR_MASK
#undef LARCH_ATTR_ENUM
#undef LARCH_ATTR_BOOL
static void
loongarch_handle_option (struct gcc_options *opts,
struct gcc_options *opts_set ATTRIBUTE_UNUSED,
const struct cl_decoded_option *decoded,
location_t loc ATTRIBUTE_UNUSED,
unsigned int opt_mask ATTRIBUTE_UNUSED)
{
size_t code = decoded->opt_index;
int val = decoded->value;
switch (code)
{
case OPT_mstrict_align:
if (val)
opts->x_target_flags |= opt_mask;
else
opts->x_target_flags &= ~opt_mask;
break;
case OPT_mcmodel_:
opts->x_la_opt_cmodel = val;
break;
case OPT_march_:
opts->x_la_opt_cpu_arch = val;
/* Set these variables to the initial values so that they can be reset
in the loongarch_config_target function according to the ARCH
settings. */
opts->x_la_opt_simd = M_OPT_UNSET;
opts->x_la_opt_fpu = M_OPT_UNSET;
opts->x_la_isa_evolution = 0;
break;
case OPT_mtune_:
opts->x_la_opt_cpu_tune = val;
/* Set these variables to the initial values so that they can be reset
in the loongarch_target_option_override function according to the TUNE
settings. */
opts->x_str_align_functions = NULL;
opts->x_str_align_loops = NULL;
opts->x_str_align_jumps = NULL;
break;
default:
gcc_unreachable ();
}
}
/* Parse ARG_STR which contains the definition of one target attribute.
Show appropriate errors if any or return true if the attribute is valid. */
static bool
loongarch_process_one_target_attr (char *arg_str, location_t loc)
{
bool invert = false;
size_t len = strlen (arg_str);
if (len == 0)
{
error_at (loc, "malformed %<target()%> pragma or attribute");
return false;
}
char *str_to_check = (char *) alloca (len + 1);
strcpy (str_to_check, arg_str);
if (len > 3 && startswith (str_to_check, "no-"))
{
invert = true;
str_to_check += 3;
}
char *arg = strchr (str_to_check, '=');
/* If we found opt=foo then terminate STR_TO_CHECK at the '='
and point ARG to "foo". */
if (arg)
{
*arg = '\0';
arg++;
}
const struct loongarch_attribute_info *p_attr;
bool found = false;
for (p_attr = loongarch_attributes; p_attr->name; p_attr++)
{
/* If the names don't match up, or the user has given an argument
to an attribute that doesn't accept one, or didn't give an argument
to an attribute that expects one, fail to match. */
if (strcmp (str_to_check, p_attr->name) != 0)
continue;
found = true;
/* If the name matches but the attribute does not allow "no-" versions
then we can't match. */
if (invert && !p_attr->allow_neg)
{
error_at (loc, "pragma or attribute %<target(\"%s\")%> does not "
"allow a negated form", str_to_check);
return false;
}
switch (p_attr->attr_type)
{
/* Either set or unset a boolean option. */
case loongarch_attr_mask:
{
struct cl_decoded_option decoded;
/* We only need to specify the option number.
loongarch_handle_option will know which mask to apply. */
decoded.opt_index = p_attr->opt_num;
decoded.value = !invert;
loongarch_handle_option (&global_options, &global_options_set,
&decoded, input_location,
p_attr->opt_mask);
break;
}
/* Use the option setting machinery to set an option to an enum. */
case loongarch_attr_enum:
{
if (!arg)
{
error_at (loc, "the value of pragma or attribute "
"%<target(\"%s\")%> not be empty", str_to_check);
return false;
}
bool valid;
int value;
struct cl_decoded_option decoded;
valid = opt_enum_arg_to_value (p_attr->opt_num, arg,
&value, CL_TARGET);
decoded.opt_index = p_attr->opt_num;
decoded.value = value;
if (valid)
loongarch_handle_option (&global_options,
&global_options_set,
&decoded, input_location,
p_attr->opt_mask);
else
error_at (loc, "pragma or attribute %<target(\"%s=%s\")%> is "
"not valid", str_to_check, arg);
break;
}
/* Either set or unset a boolean option. */
case loongarch_attr_bool:
{
struct cl_decoded_option decoded;
generate_option (p_attr->opt_num, NULL, !invert,
CL_TARGET, &decoded);
switch (decoded.opt_index)
{
case OPT_mlsx:
global_options.x_la_opt_simd
= decoded.value
? (la_opt_simd == ISA_EXT_SIMD_LASX
? ISA_EXT_SIMD_LASX : ISA_EXT_SIMD_LSX)
: ISA_EXT_NONE;
break;
case OPT_mlasx:
global_options.x_la_opt_simd
= decoded.value
? ISA_EXT_SIMD_LASX
: (la_opt_simd == ISA_EXT_SIMD_LASX
|| la_opt_simd == ISA_EXT_SIMD_LSX
? ISA_EXT_SIMD_LSX : ISA_EXT_NONE);
break;
default:
{
if (decoded.value)
global_options.x_la_isa_evolution |= p_attr->opt_mask;
else
global_options.x_la_isa_evolution &= ~p_attr->opt_mask;
global_options_set.x_la_isa_evolution |= p_attr->opt_mask;
}
}
break;
}
default:
gcc_unreachable ();
}
}
/* If we reached here we either have found an attribute and validated
it or didn't match any. If we matched an attribute but its arguments
were malformed we will have returned false already. */
if (!found)
error_at (loc, "attribute %<target%> argument %qs is unknown",
arg_str);
return found;
}
/* Count how many times the character C appears in
NULL-terminated string STR. */
static unsigned int
num_occurences_in_str (char c, char *str)
{
unsigned int res = 0;
while (*str != '\0')
{
if (*str == c)
res++;
str++;
}
return res;
}
/* Parse the tree in ARGS that contains the target attribute information
and update the global target options space. */
bool
loongarch_process_target_attr (tree args, tree fndecl)
{
location_t loc
= fndecl == NULL ? UNKNOWN_LOCATION : DECL_SOURCE_LOCATION (fndecl);
if (TREE_CODE (args) == TREE_LIST)
{
do
{
tree head = TREE_VALUE (args);
if (head)
{
if (!loongarch_process_target_attr (head, fndecl))
return false;
}
args = TREE_CHAIN (args);
} while (args);
return true;
}
if (TREE_CODE (args) != STRING_CST)
{
error_at (loc, "attribute %<target%> argument not a string");
return false;
}
size_t len = strlen (TREE_STRING_POINTER (args));
auto_vec<char, 32> buffer;
buffer.safe_grow (len + 1);
char *str_to_check = buffer.address ();
memcpy (str_to_check, TREE_STRING_POINTER (args), len + 1);
if (len == 0)
{
error_at (loc, "malformed %<target()%> pragma or attribute");
return false;
}
/* Used to catch empty spaces between commas i.e.
attribute ((target ("attr1,,attr2"))). */
unsigned int num_commas = num_occurences_in_str (',', str_to_check);
/* Handle multiple target attributes separated by ','. */
char *token = strtok_r (str_to_check, ",", &str_to_check);
unsigned int num_attrs = 0;
while (token)
{
num_attrs++;
if (!loongarch_process_one_target_attr (token, loc))
return false;
token = strtok_r (NULL, ",", &str_to_check);
}
if (num_attrs != num_commas + 1)
{
error_at (loc, "malformed %<target(\"%s\")%> pragma or attribute",
TREE_STRING_POINTER (args));
return false;
}
return true;
}
/* Implement TARGET_OPTION_VALID_ATTRIBUTE_P. This is used to
process attribute ((target ("..."))). */
bool
loongarch_option_valid_attribute_p (tree fndecl, tree, tree args, int)
{
struct cl_target_option cur_target;
bool ret;
tree old_optimize;
tree new_target, new_optimize;
tree existing_target = DECL_FUNCTION_SPECIFIC_TARGET (fndecl);
/* If what we're processing is the current pragma string then the
target option node is already stored in target_option_current_node
by loongarch_pragma_target_parse in loongarch-target-attr.cc.
Use that to avoid having to re-parse the string. */
if (!existing_target && args == current_target_pragma)
{
DECL_FUNCTION_SPECIFIC_TARGET (fndecl) = target_option_current_node;
return true;
}
tree func_optimize = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (fndecl);
old_optimize
= build_optimization_node (&global_options, &global_options_set);
/* If the function changed the optimization levels as well as setting
target options, start with the optimizations specified. */
if (func_optimize && func_optimize != old_optimize)
cl_optimization_restore (&global_options, &global_options_set,
TREE_OPTIMIZATION (func_optimize));
/* Save the current target options to restore at the end. */
cl_target_option_save (&cur_target, &global_options, &global_options_set);
/* If fndecl already has some target attributes applied to it, unpack
them so that we add this attribute on top of them, rather than
overwriting them. */
if (existing_target)
{
struct cl_target_option *existing_options
= TREE_TARGET_OPTION (existing_target);
if (existing_options)
cl_target_option_restore (&global_options, &global_options_set,
existing_options);
}
else
cl_target_option_restore (&global_options, &global_options_set,
TREE_TARGET_OPTION (target_option_current_node));
ret = loongarch_process_target_attr (args, fndecl);
/* Set up any additional state. */
if (ret)
{
loongarch_option_override_internal (&la_target,
&global_options,
&global_options_set);
new_target = build_target_option_node (&global_options,
&global_options_set);
}
else
new_target = NULL;
new_optimize = build_optimization_node (&global_options,
&global_options_set);
if (fndecl && ret)
{
DECL_FUNCTION_SPECIFIC_TARGET (fndecl) = new_target;
if (old_optimize != new_optimize)
DECL_FUNCTION_SPECIFIC_OPTIMIZATION (fndecl) = new_optimize;
}
cl_target_option_restore (&global_options, &global_options_set, &cur_target);
if (old_optimize != new_optimize)
cl_optimization_restore (&global_options, &global_options_set,
TREE_OPTIMIZATION (old_optimize));
return ret;
}
/* Parse a function multiversioning feature string STR, as found in a
target_version or target_clones attribute.
If FEATURE_MASK is nonnull, then assign to it a bitmask representing
the set of features explicitly specified in the feature string.
If FEATURE_PRIORITY is nonnull, set one or two unsigned integer values
presenting the priority of the feature string. When the priority is
set explicitly in the attribute string, the number of members of
feature_priority is 2, feature_priority[0] is the priority set in the
code, and feature_priority[1] is the priority calculated from the
feature string. When the priority is not set in the attribute string,
the number of members of feature_priority is 1, and its value is the
priority calculated by the feature string. */
bool
loongarch_parse_fmv_features (location_t loc, string_slice str,
loongarch_fmv_feature_mask *feature_mask,
auto_vec<unsigned int> *feature_priority)
{
if (feature_mask)
*feature_mask = 0;
string_slice attr_str = string_slice::tokenize (&str, ";");
attr_str = attr_str.strip ();
if (attr_str == "default")
{
if (str.is_valid ())
{
error_at (loc, "\"default\" cannot be set together with other "
"features in %qs", attr_str.begin ());
return false;
}
if (feature_priority)
feature_priority->safe_push (LA_PRIO_NONE);
return true;
}
if (attr_str.empty ())
{
error_at (loc, "character before %<;%> in attribute %qs cannot be empty",
attr_str.begin ());
return false;
}
/* At this time, str stores the string with the priority set in attribute.
If it does not exist, it is illegal. */
if (str.is_valid ())
{
if (str.empty ())
{
error_at (loc, "in attribute %qs priority cannot be empty",
attr_str.begin ());
return false;
}
string_slice prio_str = string_slice::tokenize (&str, ";");
if (str.is_valid ())
{
error_at (loc, "in attribute %qs the number of features "
"cannot exceed two", attr_str.begin ());
return false;
}
prio_str = prio_str.strip ();
string_slice name = string_slice::tokenize (&prio_str, "=");
if (name == "priority" && prio_str.is_valid ())
{
unsigned int tmp_prio = 0;
unsigned int len = 0;
for (char c : prio_str)
{
if (ISDIGIT (c))
len++;
else
break;
}
if (len != prio_str.size ()
|| sscanf (prio_str.begin (), "%u", &tmp_prio) != 1)
{
error_at (loc, "Setting the priority value to %qs is "
"illegal in attribute %qs", prio_str.begin (),
attr_str.begin ());
return false;
}
if (feature_priority)
feature_priority->safe_push (tmp_prio + LA_PRIO_MAX);
}
else
{
error_at (loc, "in attribute %qs, the second feature should be "
"\"priority=%<num%>\" instead of %qs", attr_str.begin (),
name.begin ());
return false;
}
}
if (attr_str.is_valid ())
{
int num_features = ARRAY_SIZE (loongarch_attributes);
/* Handle arch= if specified. For priority, set it to be 1 more than
the best instruction set the processor can handle. */
if (strstr (attr_str.begin (), "arch=") != NULL)
{
string_slice arch_name = attr_str;
string_slice::tokenize (&arch_name, "=");
if (arch_name.empty ())
{
error_at (loc, "in attribute %qs you need to set a legal value "
"for \"arch\"", attr_str.begin ());
return false;
}
loongarch_fmv_feature_mask tmp_mask = 0ULL;
unsigned int tmp_prio = 0;
if (arch_name == "loongarch64")
{
tmp_mask = 1UL << FEAT_LA64;
tmp_prio = LA_PRIO_LOONGARCH64;
}
else if (arch_name == "la64v1.0")
{
tmp_mask = 1ULL << FEAT_LA64;
for (int i = 0; i < num_features; i++)
if (loongarch_attributes[i].arch_ver == ARCH_LA64V1_0)
tmp_mask |= loongarch_attributes[i].feat_mask;
tmp_prio = LA_PRIO_LA64V1_0;
}
else if (arch_name == "la64v1.1")
{
tmp_mask = 1ULL << FEAT_LA64;
for (int i = 0; i < num_features; i++)
if (loongarch_attributes[i].arch_ver == ARCH_LA64V1_0
|| loongarch_attributes[i].arch_ver == ARCH_LA64V1_1)
tmp_mask |= loongarch_attributes[i].feat_mask;
tmp_prio = LA_PRIO_LA64V1_1;
}
else
{
if (loc != UNKNOWN_LOCATION)
warning_at (loc, OPT_Wattributes,
"in attribute %qs you need to set a legal value "
"for \"arch\"", attr_str.begin ());
return false;
}
if (feature_mask)
*feature_mask = tmp_mask;
if (feature_priority)
feature_priority->safe_push (tmp_prio);
}
else
{
int i;
for (i = 0; i < num_features - 1; i++)
{
if (loongarch_attributes[i].name == attr_str
|| strstr (attr_str.begin (),
loongarch_attributes[i].name) != NULL)
{
if (loongarch_attributes[i].feat_mask == 0)
{
if (loc != UNKNOWN_LOCATION)
warning_at (loc, OPT_Wattributes,
"attribute %qs is not supported in "
"%<target_version%> or %<target_clones%>",
attr_str.begin ());
return false;
}
if (feature_mask)
*feature_mask = loongarch_attributes[i].feat_mask;
if (feature_priority)
feature_priority->safe_push (loongarch_attributes[i].priority);
break;
}
}
if (i == num_features - 1)
{
if (loc != UNKNOWN_LOCATION)
warning_at (loc, OPT_Wattributes,
"%qs is not supported in target attribute",
attr_str.begin ());
return false;
}
}
}
if (feature_priority)
gcc_assert (feature_priority->length () == 1
|| feature_priority->length () == 2);
return true;
}
/* Compare priorities of two version decls. Return:
1: decl1 has a higher priority
-1: decl2 has a higher priority
0: decl1 and decl2 have the same priority.
*/
int
loongarch_compare_version_priority (tree decl1, tree decl2)
{
auto_vec<unsigned int> prio1, prio2;
get_feature_mask_for_version (decl1, NULL, &prio1);
get_feature_mask_for_version (decl2, NULL, &prio2);
unsigned int max_prio1
= prio1.length () == 2 ? MAX (prio1[0], prio1[1]) : prio1[0];
unsigned int max_prio2
= prio2.length () == 2 ? MAX (prio2[0], prio2[1]) : prio2[0];
if (max_prio1 != max_prio2)
return max_prio1 > max_prio2 ? 1 : -1;
/* If max_prio1 == max_prio2, and max_prio1 >= LA_PRIO_MAX,
it means that the attribute strings of decl1 and decl2 are both
set with priorities, and the priority values are the same.
So next we use the priority calculated by the attribute string to
compare. */
if (max_prio1 >= LA_PRIO_MAX)
{
unsigned int min_prio1
= prio1.length () == 2 ? MIN (prio1[0], prio1[1]) : prio1[0];
unsigned int min_prio2
= prio2.length () == 2 ? MIN (prio2[0], prio2[1]) : prio2[0];
if (min_prio1 != min_prio2)
return min_prio1 > min_prio2 ? 1 : -1;
}
return 0;
}