mirror of
https://github.com/autc04/Retro68.git
synced 2024-06-03 00:29:47 +00:00
927 lines
27 KiB
C++
927 lines
27 KiB
C++
/* intrinsics.cc -- D language compiler intrinsics.
|
|
Copyright (C) 2006-2022 Free Software Foundation, Inc.
|
|
|
|
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/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
|
|
#include "dmd/declaration.h"
|
|
#include "dmd/expression.h"
|
|
#include "dmd/identifier.h"
|
|
#include "dmd/mangle.h"
|
|
#include "dmd/module.h"
|
|
#include "dmd/template.h"
|
|
|
|
#include "tm.h"
|
|
#include "function.h"
|
|
#include "tree.h"
|
|
#include "fold-const.h"
|
|
#include "stringpool.h"
|
|
#include "builtins.h"
|
|
|
|
#include "d-tree.h"
|
|
|
|
|
|
/* An internal struct used to hold information on D intrinsics. */
|
|
|
|
struct intrinsic_decl
|
|
{
|
|
/* The DECL_INTRINSIC_CODE of this decl. */
|
|
intrinsic_code code;
|
|
|
|
/* The DECL_FUNCTION_CODE of this decl, if it directly maps to any. */
|
|
built_in_function built_in;
|
|
|
|
/* The name of the intrinsic. */
|
|
const char *name;
|
|
|
|
/* The module where the intrinsic is located. */
|
|
const char *module;
|
|
|
|
/* The mangled signature decoration of the intrinsic. */
|
|
const char *deco;
|
|
|
|
/* True if the intrinsic is only handled in CTFE. */
|
|
bool ctfeonly;
|
|
};
|
|
|
|
static const intrinsic_decl intrinsic_decls[] =
|
|
{
|
|
#define DEF_D_INTRINSIC(CODE, BUILTIN, NAME, MODULE, DECO, CTFE) \
|
|
{ CODE, BUILTIN, NAME, MODULE, DECO, CTFE },
|
|
|
|
#include "intrinsics.def"
|
|
|
|
#undef DEF_D_INTRINSIC
|
|
};
|
|
|
|
/* Checks if DECL is an intrinsic or run time library function that requires
|
|
special processing. Sets DECL_INTRINSIC_CODE so it can be identified
|
|
later in maybe_expand_intrinsic. */
|
|
|
|
void
|
|
maybe_set_intrinsic (FuncDeclaration *decl)
|
|
{
|
|
if (!decl->ident || decl->builtin != BUILTIN::unknown)
|
|
return;
|
|
|
|
/* The builtin flag is updated only if we can evaluate the intrinsic
|
|
at compile-time. Such as the math or bitop intrinsics. */
|
|
decl->builtin = BUILTIN::unimp;
|
|
|
|
/* Check if it's a compiler intrinsic. We only require that any
|
|
internally recognised intrinsics are declared in a module with
|
|
an explicit module declaration. */
|
|
Module *m = decl->getModule ();
|
|
|
|
if (!m || !m->md)
|
|
return;
|
|
|
|
TemplateInstance *ti = decl->isInstantiated ();
|
|
TemplateDeclaration *td = ti ? ti->tempdecl->isTemplateDeclaration () : NULL;
|
|
|
|
const char *tname = decl->ident->toChars ();
|
|
const char *tmodule = m->md->toChars ();
|
|
const char *tdeco = (td == NULL) ? decl->type->deco : NULL;
|
|
|
|
/* Look through all D intrinsics. */
|
|
for (size_t i = 0; i < (int) INTRINSIC_LAST; i++)
|
|
{
|
|
if (!intrinsic_decls[i].name)
|
|
continue;
|
|
|
|
if (strcmp (intrinsic_decls[i].name, tname) != 0
|
|
|| strcmp (intrinsic_decls[i].module, tmodule) != 0)
|
|
continue;
|
|
|
|
/* Instantiated functions would have the wrong type deco, get it from the
|
|
template member instead. */
|
|
if (tdeco == NULL)
|
|
{
|
|
if (!td || !td->onemember)
|
|
return;
|
|
|
|
FuncDeclaration *fd = td->onemember->isFuncDeclaration ();
|
|
if (fd == NULL)
|
|
return;
|
|
|
|
OutBuffer buf;
|
|
mangleToBuffer (fd->type, &buf);
|
|
tdeco = buf.extractChars ();
|
|
}
|
|
|
|
/* Matching the type deco may be a bit too strict, as it means that all
|
|
function attributes that end up in the signature must be kept aligned
|
|
between the compiler and library declaration. */
|
|
if (strcmp (intrinsic_decls[i].deco, tdeco) == 0)
|
|
{
|
|
intrinsic_code code = intrinsic_decls[i].code;
|
|
|
|
if (decl->csym == NULL)
|
|
get_symbol_decl (decl);
|
|
|
|
/* If there is no function body, then the implementation is always
|
|
provided by the compiler. */
|
|
if (!decl->fbody)
|
|
set_decl_built_in_function (decl->csym, BUILT_IN_FRONTEND, code);
|
|
|
|
/* Infer whether the intrinsic can be used for CTFE, let the
|
|
front-end know that it can be evaluated at compile-time. */
|
|
switch (code)
|
|
{
|
|
case INTRINSIC_VA_ARG:
|
|
case INTRINSIC_C_VA_ARG:
|
|
case INTRINSIC_VASTART:
|
|
case INTRINSIC_ADDS:
|
|
case INTRINSIC_ADDSL:
|
|
case INTRINSIC_ADDU:
|
|
case INTRINSIC_ADDUL:
|
|
case INTRINSIC_SUBS:
|
|
case INTRINSIC_SUBSL:
|
|
case INTRINSIC_SUBU:
|
|
case INTRINSIC_SUBUL:
|
|
case INTRINSIC_MULS:
|
|
case INTRINSIC_MULSL:
|
|
case INTRINSIC_MULU:
|
|
case INTRINSIC_MULUI:
|
|
case INTRINSIC_MULUL:
|
|
case INTRINSIC_NEGS:
|
|
case INTRINSIC_NEGSL:
|
|
case INTRINSIC_VLOAD8:
|
|
case INTRINSIC_VLOAD16:
|
|
case INTRINSIC_VLOAD32:
|
|
case INTRINSIC_VLOAD64:
|
|
case INTRINSIC_VSTORE8:
|
|
case INTRINSIC_VSTORE16:
|
|
case INTRINSIC_VSTORE32:
|
|
case INTRINSIC_VSTORE64:
|
|
break;
|
|
|
|
case INTRINSIC_POW:
|
|
{
|
|
/* Check that this overload of pow() is has an equivalent
|
|
built-in function. It could be `int pow(int, int)'. */
|
|
tree rettype = TREE_TYPE (TREE_TYPE (decl->csym));
|
|
if (mathfn_built_in (rettype, BUILT_IN_POW) != NULL_TREE)
|
|
decl->builtin = BUILTIN::gcc;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
decl->builtin = BUILTIN::gcc;
|
|
break;
|
|
}
|
|
|
|
/* The intrinsic was marked as CTFE-only. */
|
|
if (intrinsic_decls[i].ctfeonly)
|
|
DECL_BUILT_IN_CTFE (decl->csym) = 1;
|
|
|
|
DECL_INTRINSIC_CODE (decl->csym) = code;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Construct a function call to the built-in function CODE, N is the number of
|
|
arguments, and the `...' parameters are the argument expressions.
|
|
The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
call_builtin_fn (tree callexp, built_in_function code, int n, ...)
|
|
{
|
|
tree *argarray = XALLOCAVEC (tree, n);
|
|
va_list ap;
|
|
|
|
va_start (ap, n);
|
|
for (int i = 0; i < n; i++)
|
|
argarray[i] = va_arg (ap, tree);
|
|
va_end (ap);
|
|
|
|
tree exp = build_call_expr_loc_array (EXPR_LOCATION (callexp),
|
|
builtin_decl_explicit (code),
|
|
n, argarray);
|
|
return convert (TREE_TYPE (callexp), fold (exp));
|
|
}
|
|
|
|
/* Expand a front-end instrinsic call to bsf(). This takes one argument,
|
|
the signature to which can be either:
|
|
|
|
int bsf (uint arg);
|
|
int bsf (ulong arg);
|
|
|
|
This scans all bits in the given argument starting with the first,
|
|
returning the bit number of the first bit set. The original call
|
|
expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_bsf (tree callexp)
|
|
{
|
|
/* The bsr() intrinsic gets turned into __builtin_ctz(arg).
|
|
The return value is supposed to be undefined if arg is zero. */
|
|
tree arg = CALL_EXPR_ARG (callexp, 0);
|
|
int argsize = TYPE_PRECISION (TREE_TYPE (arg));
|
|
|
|
/* Which variant of __builtin_ctz* should we call? */
|
|
built_in_function code = (argsize <= INT_TYPE_SIZE) ? BUILT_IN_CTZ
|
|
: (argsize <= LONG_TYPE_SIZE) ? BUILT_IN_CTZL
|
|
: (argsize <= LONG_LONG_TYPE_SIZE) ? BUILT_IN_CTZLL
|
|
: END_BUILTINS;
|
|
|
|
gcc_assert (code != END_BUILTINS);
|
|
|
|
return call_builtin_fn (callexp, code, 1, arg);
|
|
}
|
|
|
|
/* Expand a front-end instrinsic call to bsr(). This takes one argument,
|
|
the signature to which can be either:
|
|
|
|
int bsr (uint arg);
|
|
int bsr (ulong arg);
|
|
|
|
This scans all bits in the given argument from the most significant bit
|
|
to the least significant, returning the bit number of the first bit set.
|
|
The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_bsr (tree callexp)
|
|
{
|
|
/* The bsr() intrinsic gets turned into (size - 1) - __builtin_clz(arg).
|
|
The return value is supposed to be undefined if arg is zero. */
|
|
tree arg = CALL_EXPR_ARG (callexp, 0);
|
|
tree type = TREE_TYPE (arg);
|
|
int argsize = TYPE_PRECISION (type);
|
|
|
|
/* Which variant of __builtin_clz* should we call? */
|
|
built_in_function code = (argsize <= INT_TYPE_SIZE) ? BUILT_IN_CLZ
|
|
: (argsize <= LONG_TYPE_SIZE) ? BUILT_IN_CLZL
|
|
: (argsize <= LONG_LONG_TYPE_SIZE) ? BUILT_IN_CLZLL
|
|
: END_BUILTINS;
|
|
|
|
gcc_assert (code != END_BUILTINS);
|
|
|
|
tree result = call_builtin_fn (callexp, code, 1, arg);
|
|
|
|
/* Handle int -> long conversions. */
|
|
if (TREE_TYPE (result) != type)
|
|
result = fold_convert (type, result);
|
|
|
|
result = fold_build2 (MINUS_EXPR, type,
|
|
build_integer_cst (argsize - 1, type), result);
|
|
return fold_convert (TREE_TYPE (callexp), result);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to INTRINSIC, which is either a call to
|
|
bt(), btc(), btr(), or bts(). These intrinsics expect to take two arguments,
|
|
the signature to which is:
|
|
|
|
int bt (size_t* ptr, size_t bitnum);
|
|
|
|
All intrinsics test if a bit is set and return the result of that condition.
|
|
Variants of `bt' will then update that bit. `btc' compliments the bit, `bts'
|
|
sets the bit, and `btr' resets the bit. The original call expression is
|
|
held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_bt (intrinsic_code intrinsic, tree callexp)
|
|
{
|
|
tree ptr = CALL_EXPR_ARG (callexp, 0);
|
|
tree bitnum = CALL_EXPR_ARG (callexp, 1);
|
|
tree type = TREE_TYPE (TREE_TYPE (ptr));
|
|
|
|
/* size_t bitsize = sizeof(*ptr) * BITS_PER_UNIT; */
|
|
tree bitsize = fold_convert (type, TYPE_SIZE (TREE_TYPE (ptr)));
|
|
|
|
/* ptr[bitnum / bitsize] */
|
|
ptr = build_array_index (ptr, fold_build2 (TRUNC_DIV_EXPR, type,
|
|
bitnum, bitsize));
|
|
ptr = indirect_ref (type, ptr);
|
|
|
|
/* mask = 1 << (bitnum % bitsize); */
|
|
bitnum = fold_build2 (TRUNC_MOD_EXPR, type, bitnum, bitsize);
|
|
bitnum = fold_build2 (LSHIFT_EXPR, type, build_one_cst (type), bitnum);
|
|
|
|
/* cond = ptr[bitnum / size] & mask; */
|
|
tree cond = fold_build2 (BIT_AND_EXPR, type, ptr, bitnum);
|
|
|
|
/* cond ? -1 : 0; */
|
|
cond = build_condition (TREE_TYPE (callexp), d_truthvalue_conversion (cond),
|
|
build_minus_one_cst (TREE_TYPE (callexp)),
|
|
build_zero_cst (TREE_TYPE (callexp)));
|
|
|
|
/* Update the bit as needed, only testing the bit for bt(). */
|
|
tree_code code;
|
|
|
|
switch (intrinsic)
|
|
{
|
|
case INTRINSIC_BT:
|
|
case INTRINSIC_BT64:
|
|
return cond;
|
|
|
|
case INTRINSIC_BTC:
|
|
case INTRINSIC_BTC64:
|
|
code = BIT_XOR_EXPR;
|
|
break;
|
|
|
|
case INTRINSIC_BTR:
|
|
case INTRINSIC_BTR64:
|
|
bitnum = fold_build1 (BIT_NOT_EXPR, TREE_TYPE (bitnum), bitnum);
|
|
code = BIT_AND_EXPR;
|
|
break;
|
|
|
|
case INTRINSIC_BTS:
|
|
case INTRINSIC_BTS64:
|
|
code = BIT_IOR_EXPR;
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
/* ptr[bitnum / size] op= mask; */
|
|
ptr = modify_expr (ptr, fold_build2 (code, TREE_TYPE (ptr), ptr, bitnum));
|
|
|
|
/* Store the condition result in a temporary, and return expressions in
|
|
correct order of evaluation. */
|
|
tree tmp = build_local_temp (TREE_TYPE (callexp));
|
|
cond = modify_expr (tmp, cond);
|
|
|
|
return compound_expr (cond, compound_expr (ptr, tmp));
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to popcnt(). This takes one argument, the
|
|
signature to which can be either:
|
|
|
|
int popcnt (uint arg);
|
|
int popcnt (ulong arg);
|
|
|
|
Calculates the number of set bits in an integer. The original call
|
|
expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_popcnt (tree callexp)
|
|
{
|
|
tree arg = CALL_EXPR_ARG (callexp, 0);
|
|
int argsize = TYPE_PRECISION (TREE_TYPE (arg));
|
|
|
|
/* Which variant of __builtin_popcount* should we call? */
|
|
built_in_function code = (argsize <= INT_TYPE_SIZE) ? BUILT_IN_POPCOUNT
|
|
: (argsize <= LONG_TYPE_SIZE) ? BUILT_IN_POPCOUNTL
|
|
: (argsize <= LONG_LONG_TYPE_SIZE) ? BUILT_IN_POPCOUNTLL
|
|
: END_BUILTINS;
|
|
|
|
gcc_assert (code != END_BUILTINS);
|
|
|
|
return call_builtin_fn (callexp, code, 1, arg);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to INTRINSIC, which is either a call to
|
|
rol() or ror(). These intrinsics expect to take one or two arguments,
|
|
the signature to which can be either:
|
|
|
|
T rol(T) (const T value, const uint count);
|
|
T rol(uint count, T) (const T value);
|
|
T ror(T) (const T value, const uint count);
|
|
T ror(uint count, T) (const T value);
|
|
|
|
This bitwise rotates VALUE left or right by COUNT bit positions. */
|
|
|
|
static tree
|
|
expand_intrinsic_rotate (intrinsic_code intrinsic, tree callexp)
|
|
{
|
|
tree type = TREE_TYPE (callexp);
|
|
tree value = CALL_EXPR_ARG (callexp, 0);
|
|
tree count;
|
|
tree_code code;
|
|
|
|
/* Get the equivalent tree code for the intrinsic. */
|
|
if (intrinsic == INTRINSIC_ROL || intrinsic == INTRINSIC_ROL_TIARG)
|
|
code = LROTATE_EXPR;
|
|
else if (intrinsic == INTRINSIC_ROR || intrinsic == INTRINSIC_ROR_TIARG)
|
|
code = RROTATE_EXPR;
|
|
else
|
|
gcc_unreachable ();
|
|
|
|
/* Get the COUNT parameter. Either from the call expression arguments or the
|
|
template instantiation arguments. */
|
|
if (intrinsic == INTRINSIC_ROL || intrinsic == INTRINSIC_ROR)
|
|
count = CALL_EXPR_ARG (callexp, 1);
|
|
else
|
|
{
|
|
tree callee = CALL_EXPR_FN (callexp);
|
|
|
|
if (TREE_CODE (callee) == ADDR_EXPR)
|
|
callee = TREE_OPERAND (callee, 0);
|
|
|
|
/* Retrieve from the encoded template instantation. */
|
|
TemplateInstance *ti = DECL_LANG_FRONTEND (callee)->isInstantiated ();
|
|
gcc_assert (ti && ti->tiargs && ti->tiargs->length == 2);
|
|
|
|
Expression *e = isExpression ((*ti->tiargs)[0]);
|
|
gcc_assert (e && e->op == EXP::int64);
|
|
count = build_expr (e, true);
|
|
}
|
|
|
|
return fold_build2 (code, type, value, count);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to copysign(). This takes two arguments,
|
|
the signature to which can be either:
|
|
|
|
float copysign (T to, float from);
|
|
double copysign (T to, double from);
|
|
real copysign (T to, real from);
|
|
|
|
This computes a value composed of TO with the sign bit of FROM. The original
|
|
call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_copysign (tree callexp)
|
|
{
|
|
tree to = CALL_EXPR_ARG (callexp, 0);
|
|
tree from = CALL_EXPR_ARG (callexp, 1);
|
|
tree type = TREE_TYPE (to);
|
|
|
|
/* Convert parameters to the same type. Prefer the first parameter unless it
|
|
is an integral type. */
|
|
if (INTEGRAL_TYPE_P (type))
|
|
{
|
|
to = fold_convert (TREE_TYPE (from), to);
|
|
type = TREE_TYPE (to);
|
|
}
|
|
else
|
|
from = fold_convert (type, from);
|
|
|
|
/* Which variant of __builtin_copysign* should we call? */
|
|
built_in_function code = (type == float_type_node) ? BUILT_IN_COPYSIGNF
|
|
: (type == double_type_node) ? BUILT_IN_COPYSIGN
|
|
: (type == long_double_type_node) ? BUILT_IN_COPYSIGNL
|
|
: END_BUILTINS;
|
|
|
|
gcc_assert (code != END_BUILTINS);
|
|
|
|
return call_builtin_fn (callexp, code, 2, to, from);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to pow(). This takes two arguments, the
|
|
signature to which can be either:
|
|
|
|
float pow (float base, T exponent);
|
|
double pow (double base, T exponent);
|
|
real pow (real base, T exponent);
|
|
|
|
This computes the value of BASE raised to the power of EXPONENT.
|
|
The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_pow (tree callexp)
|
|
{
|
|
tree base = CALL_EXPR_ARG (callexp, 0);
|
|
tree exponent = CALL_EXPR_ARG (callexp, 1);
|
|
tree exptype = TREE_TYPE (exponent);
|
|
|
|
/* Which variant of __builtin_pow* should we call? */
|
|
built_in_function code = SCALAR_FLOAT_TYPE_P (exptype) ? BUILT_IN_POW
|
|
: INTEGRAL_TYPE_P (exptype) ? BUILT_IN_POWI
|
|
: END_BUILTINS;
|
|
gcc_assert (code != END_BUILTINS);
|
|
|
|
tree builtin = mathfn_built_in (TREE_TYPE (base), code);
|
|
gcc_assert (builtin != NULL_TREE);
|
|
|
|
return call_builtin_fn (callexp, DECL_FUNCTION_CODE (builtin), 2,
|
|
base, exponent);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to toPrec(). This takes one argument, the
|
|
signature to which can be either:
|
|
|
|
T toPrec(T)(float f);
|
|
T toPrec(T)(double f);
|
|
T toPrec(T)(real f);
|
|
|
|
This rounds the argument F to the precision of the specified floating
|
|
point type T. The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_toprec (tree callexp)
|
|
{
|
|
tree f = CALL_EXPR_ARG (callexp, 0);
|
|
tree type = TREE_TYPE (callexp);
|
|
|
|
return convert (type, f);
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to va_arg(). This takes either one or two
|
|
arguments, the signature to which can be either:
|
|
|
|
T va_arg(T) (ref va_list ap);
|
|
void va_arg(T) (va_list ap, ref T parmn);
|
|
|
|
This retrieves the next variadic parameter that is type T from the given
|
|
va_list. If also given, store the value into parmn, otherwise return it.
|
|
The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_vaarg (tree callexp)
|
|
{
|
|
tree ap = CALL_EXPR_ARG (callexp, 0);
|
|
tree parmn = NULL_TREE;
|
|
tree type;
|
|
|
|
STRIP_NOPS (ap);
|
|
|
|
if (call_expr_nargs (callexp) == 1)
|
|
type = TREE_TYPE (callexp);
|
|
else
|
|
{
|
|
parmn = CALL_EXPR_ARG (callexp, 1);
|
|
STRIP_NOPS (parmn);
|
|
|
|
/* The `ref' argument to va_arg is either an address or reference,
|
|
get the value of it. */
|
|
if (TREE_CODE (parmn) == PARM_DECL && POINTER_TYPE_P (TREE_TYPE (parmn)))
|
|
parmn = build_deref (parmn);
|
|
else
|
|
{
|
|
gcc_assert (TREE_CODE (parmn) == ADDR_EXPR);
|
|
parmn = TREE_OPERAND (parmn, 0);
|
|
}
|
|
|
|
type = TREE_TYPE (parmn);
|
|
}
|
|
|
|
/* (T) VA_ARG_EXP<ap>; */
|
|
tree exp = build1_loc (EXPR_LOCATION (callexp), VA_ARG_EXPR, type, ap);
|
|
|
|
/* parmn = (T) VA_ARG_EXP<ap>; */
|
|
if (parmn != NULL_TREE)
|
|
exp = modify_expr (parmn, exp);
|
|
|
|
return exp;
|
|
}
|
|
|
|
/* Expand a front-end intrinsic call to va_start(), which takes two arguments,
|
|
the signature to which is:
|
|
|
|
void va_start(T) (out va_list ap, ref T parmn);
|
|
|
|
This initializes the va_list type, where parmn should be the last named
|
|
parameter. The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_vastart (tree callexp)
|
|
{
|
|
tree ap = CALL_EXPR_ARG (callexp, 0);
|
|
tree parmn = CALL_EXPR_ARG (callexp, 1);
|
|
|
|
STRIP_NOPS (ap);
|
|
STRIP_NOPS (parmn);
|
|
|
|
/* The va_list argument should already have its address taken. The second
|
|
argument, however, is inout and that needs to be fixed to prevent a
|
|
warning. Could be casting, so need to check type too? */
|
|
gcc_assert (TREE_CODE (ap) == ADDR_EXPR
|
|
|| (TREE_CODE (ap) == PARM_DECL
|
|
&& POINTER_TYPE_P (TREE_TYPE (ap))));
|
|
|
|
/* Assuming nobody tries to change the return type. */
|
|
if (TREE_CODE (parmn) != PARM_DECL)
|
|
{
|
|
gcc_assert (TREE_CODE (parmn) == ADDR_EXPR);
|
|
parmn = TREE_OPERAND (parmn, 0);
|
|
}
|
|
|
|
return call_builtin_fn (callexp, BUILT_IN_VA_START, 2, ap, parmn);
|
|
}
|
|
|
|
/* Expand a front-end instrinsic call to INTRINSIC, which is either a call to
|
|
adds(), addu(), subs(), subu(), negs(), muls(), or mulu(). These intrinsics
|
|
expect to take two or three arguments, the signature to which can be either:
|
|
|
|
int adds (int x, int y, ref bool overflow);
|
|
long adds (long x, long y, ref bool overflow);
|
|
int negs (int x, ref bool overflow);
|
|
long negs (long x, ref bool overflow);
|
|
|
|
This performs an operation on two signed or unsigned integers, checking for
|
|
overflow. The overflow is sticky, meaning that a sequence of operations
|
|
can be done and overflow need only be checked at the end. The original call
|
|
expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_intrinsic_checkedint (intrinsic_code intrinsic, tree callexp)
|
|
{
|
|
tree type = TREE_TYPE (callexp);
|
|
tree x;
|
|
tree y;
|
|
tree overflow;
|
|
internal_fn icode;
|
|
|
|
/* Which variant of *_OVERFLOW should we generate? */
|
|
switch (intrinsic)
|
|
{
|
|
case INTRINSIC_ADDS:
|
|
case INTRINSIC_ADDSL:
|
|
case INTRINSIC_ADDU:
|
|
case INTRINSIC_ADDUL:
|
|
x = CALL_EXPR_ARG (callexp, 0);
|
|
y = CALL_EXPR_ARG (callexp, 1);
|
|
overflow = CALL_EXPR_ARG (callexp, 2);
|
|
icode = IFN_ADD_OVERFLOW;
|
|
break;
|
|
|
|
case INTRINSIC_SUBS:
|
|
case INTRINSIC_SUBSL:
|
|
case INTRINSIC_SUBU:
|
|
case INTRINSIC_SUBUL:
|
|
x = CALL_EXPR_ARG (callexp, 0);
|
|
y = CALL_EXPR_ARG (callexp, 1);
|
|
overflow = CALL_EXPR_ARG (callexp, 2);
|
|
icode = IFN_SUB_OVERFLOW;
|
|
break;
|
|
|
|
case INTRINSIC_MULS:
|
|
case INTRINSIC_MULSL:
|
|
case INTRINSIC_MULU:
|
|
case INTRINSIC_MULUI:
|
|
case INTRINSIC_MULUL:
|
|
x = CALL_EXPR_ARG (callexp, 0);
|
|
y = CALL_EXPR_ARG (callexp, 1);
|
|
overflow = CALL_EXPR_ARG (callexp, 2);
|
|
icode = IFN_MUL_OVERFLOW;
|
|
break;
|
|
|
|
case INTRINSIC_NEGS:
|
|
case INTRINSIC_NEGSL:
|
|
/* The negs() intrinsic gets turned into SUB_OVERFLOW (0, y). */
|
|
x = fold_convert (type, integer_zero_node);
|
|
y = CALL_EXPR_ARG (callexp, 0);
|
|
overflow = CALL_EXPR_ARG (callexp, 1);
|
|
icode = IFN_SUB_OVERFLOW;
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
tree result
|
|
= build_call_expr_internal_loc (EXPR_LOCATION (callexp), icode,
|
|
build_complex_type (type), 2, x, y);
|
|
|
|
STRIP_NOPS (overflow);
|
|
overflow = build_deref (overflow);
|
|
|
|
/* Assign returned result to overflow parameter, however if overflow is
|
|
already true, maintain its value. */
|
|
type = TREE_TYPE (overflow);
|
|
result = save_expr (result);
|
|
|
|
tree exp = fold_build2 (BIT_IOR_EXPR, type, overflow,
|
|
fold_convert (type, imaginary_part (result)));
|
|
exp = modify_expr (overflow, exp);
|
|
|
|
/* Return the value of result. */
|
|
return compound_expr (exp, real_part (result));
|
|
}
|
|
|
|
/* Expand a front-end instrinsic call to volatileLoad(). This takes one
|
|
argument, the signature to which can be either:
|
|
|
|
ubyte volatileLoad (ubyte* ptr);
|
|
ushort volatileLoad (ushort* ptr);
|
|
uint volatileLoad (uint* ptr);
|
|
ulong volatileLoad (ulong* ptr);
|
|
|
|
This reads a value from the memory location indicated by ptr. Calls to
|
|
them are be guaranteed to not be removed (such as during DCE) or reordered
|
|
in the same thread. The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_volatile_load (tree callexp)
|
|
{
|
|
tree ptr = CALL_EXPR_ARG (callexp, 0);
|
|
tree ptrtype = TREE_TYPE (ptr);
|
|
gcc_assert (POINTER_TYPE_P (ptrtype));
|
|
|
|
/* (T) *(volatile T *) ptr; */
|
|
tree type = build_qualified_type (TREE_TYPE (ptrtype), TYPE_QUAL_VOLATILE);
|
|
tree result = indirect_ref (type, ptr);
|
|
TREE_THIS_VOLATILE (result) = 1;
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Expand a front-end instrinsic call to volatileStore(). This takes two
|
|
arguments, the signature to which can be either:
|
|
|
|
void volatileStore (ubyte* ptr, ubyte value);
|
|
void volatileStore (ushort* ptr, ushort value);
|
|
void volatileStore (uint* ptr, uint value);
|
|
void volatileStore (ulong* ptr, ulong value);
|
|
|
|
This writes a value to the memory location indicated by ptr. Calls to
|
|
them are be guaranteed to not be removed (such as during DCE) or reordered
|
|
in the same thread. The original call expression is held in CALLEXP. */
|
|
|
|
static tree
|
|
expand_volatile_store (tree callexp)
|
|
{
|
|
tree ptr = CALL_EXPR_ARG (callexp, 0);
|
|
tree ptrtype = TREE_TYPE (ptr);
|
|
gcc_assert (POINTER_TYPE_P (ptrtype));
|
|
|
|
/* (T) *(volatile T *) ptr; */
|
|
tree type = build_qualified_type (TREE_TYPE (ptrtype), TYPE_QUAL_VOLATILE);
|
|
tree result = indirect_ref (type, ptr);
|
|
TREE_THIS_VOLATILE (result) = 1;
|
|
|
|
/* (*(volatile T *) ptr) = value; */
|
|
tree value = CALL_EXPR_ARG (callexp, 1);
|
|
return modify_expr (result, value);
|
|
}
|
|
|
|
/* If CALLEXP is for an intrinsic , expand and return inlined compiler
|
|
generated instructions. Most map directly to GCC builtins, others
|
|
require a little extra work around them. */
|
|
|
|
tree
|
|
maybe_expand_intrinsic (tree callexp)
|
|
{
|
|
tree callee = CALL_EXPR_FN (callexp);
|
|
|
|
if (TREE_CODE (callee) == ADDR_EXPR)
|
|
callee = TREE_OPERAND (callee, 0);
|
|
|
|
if (TREE_CODE (callee) != FUNCTION_DECL)
|
|
return callexp;
|
|
|
|
/* Don't expand CTFE-only intrinsics outside of semantic processing. */
|
|
if (DECL_BUILT_IN_CTFE (callee) && !doing_semantic_analysis_p)
|
|
return callexp;
|
|
|
|
intrinsic_code intrinsic = DECL_INTRINSIC_CODE (callee);
|
|
built_in_function code;
|
|
|
|
switch (intrinsic)
|
|
{
|
|
case INTRINSIC_NONE:
|
|
return callexp;
|
|
|
|
case INTRINSIC_BSF:
|
|
case INTRINSIC_BSF64:
|
|
return expand_intrinsic_bsf (callexp);
|
|
|
|
case INTRINSIC_BSR:
|
|
case INTRINSIC_BSR64:
|
|
return expand_intrinsic_bsr (callexp);
|
|
|
|
case INTRINSIC_BT:
|
|
case INTRINSIC_BT64:
|
|
case INTRINSIC_BTC:
|
|
case INTRINSIC_BTC64:
|
|
case INTRINSIC_BTR:
|
|
case INTRINSIC_BTR64:
|
|
case INTRINSIC_BTS:
|
|
case INTRINSIC_BTS64:
|
|
return expand_intrinsic_bt (intrinsic, callexp);
|
|
|
|
case INTRINSIC_POPCNT32:
|
|
case INTRINSIC_POPCNT64:
|
|
return expand_intrinsic_popcnt (callexp);
|
|
|
|
case INTRINSIC_ROL:
|
|
case INTRINSIC_ROL_TIARG:
|
|
case INTRINSIC_ROR:
|
|
case INTRINSIC_ROR_TIARG:
|
|
return expand_intrinsic_rotate (intrinsic, callexp);
|
|
|
|
case INTRINSIC_BSWAP16:
|
|
case INTRINSIC_BSWAP32:
|
|
case INTRINSIC_BSWAP64:
|
|
case INTRINSIC_CEIL:
|
|
case INTRINSIC_CEILF:
|
|
case INTRINSIC_CEILL:
|
|
case INTRINSIC_COS:
|
|
case INTRINSIC_COSF:
|
|
case INTRINSIC_COSL:
|
|
case INTRINSIC_EXP:
|
|
case INTRINSIC_EXP2:
|
|
case INTRINSIC_EXPM1:
|
|
case INTRINSIC_FABS:
|
|
case INTRINSIC_FABSF:
|
|
case INTRINSIC_FABSL:
|
|
case INTRINSIC_FLOOR:
|
|
case INTRINSIC_FLOORF:
|
|
case INTRINSIC_FLOORL:
|
|
case INTRINSIC_ISFINITE:
|
|
case INTRINSIC_ISINFINITY:
|
|
case INTRINSIC_ISNAN:
|
|
case INTRINSIC_LOG:
|
|
case INTRINSIC_LOG10:
|
|
case INTRINSIC_LOG2:
|
|
case INTRINSIC_RINT:
|
|
case INTRINSIC_RINTF:
|
|
case INTRINSIC_RINTL:
|
|
case INTRINSIC_RNDTOL:
|
|
case INTRINSIC_RNDTOLF:
|
|
case INTRINSIC_RNDTOLL:
|
|
case INTRINSIC_ROUND:
|
|
case INTRINSIC_SIN:
|
|
case INTRINSIC_SINF:
|
|
case INTRINSIC_SINL:
|
|
case INTRINSIC_SQRT:
|
|
case INTRINSIC_SQRTF:
|
|
case INTRINSIC_SQRTL:
|
|
case INTRINSIC_TAN:
|
|
case INTRINSIC_TRUNC:
|
|
code = intrinsic_decls[intrinsic].built_in;
|
|
gcc_assert (code != BUILT_IN_NONE);
|
|
return call_builtin_fn (callexp, code, 1,
|
|
CALL_EXPR_ARG (callexp, 0));
|
|
|
|
case INTRINSIC_FMAX:
|
|
case INTRINSIC_FMIN:
|
|
case INTRINSIC_LDEXP:
|
|
case INTRINSIC_LDEXPF:
|
|
case INTRINSIC_LDEXPL:
|
|
code = intrinsic_decls[intrinsic].built_in;
|
|
gcc_assert (code != BUILT_IN_NONE);
|
|
return call_builtin_fn (callexp, code, 2,
|
|
CALL_EXPR_ARG (callexp, 0),
|
|
CALL_EXPR_ARG (callexp, 1));
|
|
|
|
case INTRINSIC_FMA:
|
|
code = intrinsic_decls[intrinsic].built_in;
|
|
gcc_assert (code != BUILT_IN_NONE);
|
|
return call_builtin_fn (callexp, code, 3,
|
|
CALL_EXPR_ARG (callexp, 0),
|
|
CALL_EXPR_ARG (callexp, 1),
|
|
CALL_EXPR_ARG (callexp, 2));
|
|
|
|
case INTRINSIC_COPYSIGN:
|
|
case INTRINSIC_COPYSIGNI:
|
|
return expand_intrinsic_copysign (callexp);
|
|
|
|
case INTRINSIC_POW:
|
|
return expand_intrinsic_pow (callexp);
|
|
|
|
case INTRINSIC_TOPREC:
|
|
case INTRINSIC_TOPRECF:
|
|
case INTRINSIC_TOPRECL:
|
|
return expand_intrinsic_toprec (callexp);
|
|
|
|
case INTRINSIC_VA_ARG:
|
|
case INTRINSIC_C_VA_ARG:
|
|
return expand_intrinsic_vaarg (callexp);
|
|
|
|
case INTRINSIC_VASTART:
|
|
return expand_intrinsic_vastart (callexp);
|
|
|
|
case INTRINSIC_ADDS:
|
|
case INTRINSIC_ADDSL:
|
|
case INTRINSIC_ADDU:
|
|
case INTRINSIC_ADDUL:
|
|
case INTRINSIC_SUBS:
|
|
case INTRINSIC_SUBSL:
|
|
case INTRINSIC_SUBU:
|
|
case INTRINSIC_SUBUL:
|
|
case INTRINSIC_MULS:
|
|
case INTRINSIC_MULSL:
|
|
case INTRINSIC_MULU:
|
|
case INTRINSIC_MULUI:
|
|
case INTRINSIC_MULUL:
|
|
case INTRINSIC_NEGS:
|
|
case INTRINSIC_NEGSL:
|
|
return expand_intrinsic_checkedint (intrinsic, callexp);
|
|
|
|
case INTRINSIC_VLOAD8:
|
|
case INTRINSIC_VLOAD16:
|
|
case INTRINSIC_VLOAD32:
|
|
case INTRINSIC_VLOAD64:
|
|
return expand_volatile_load (callexp);
|
|
|
|
case INTRINSIC_VSTORE8:
|
|
case INTRINSIC_VSTORE16:
|
|
case INTRINSIC_VSTORE32:
|
|
case INTRINSIC_VSTORE64:
|
|
return expand_volatile_store (callexp);
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|