// 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
// .
#include "rust-compile-intrinsic.h"
#include "fold-const.h"
#include "langhooks.h"
#include "rust-compile-context.h"
#include "rust-compile-type.h"
#include "rust-compile-fnparam.h"
#include "rust-diagnostics.h"
#include "rust-location.h"
#include "rust-tree.h"
#include "tree-core.h"
namespace Rust {
namespace Compile {
// https://github.com/rust-lang/rust/blob/master/library/core/src/intrinsics.rs
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_codegen_llvm/src/intrinsic.rs
// https://github.com/Rust-GCC/gccrs/issues/658
//
// let llvm_name = match name {
// sym::sqrtf32 => "llvm.sqrt.f32",
// sym::sqrtf64 => "llvm.sqrt.f64",
// sym::powif32 => "llvm.powi.f32",
// sym::powif64 => "llvm.powi.f64",
// sym::sinf32 => "llvm.sin.f32",
// sym::sinf64 => "llvm.sin.f64",
// sym::cosf32 => "llvm.cos.f32",
// sym::cosf64 => "llvm.cos.f64",
// sym::powf32 => "llvm.pow.f32",
// sym::powf64 => "llvm.pow.f64",
// sym::expf32 => "llvm.exp.f32",
// sym::expf64 => "llvm.exp.f64",
// sym::exp2f32 => "llvm.exp2.f32",
// sym::exp2f64 => "llvm.exp2.f64",
// sym::logf32 => "llvm.log.f32",
// sym::logf64 => "llvm.log.f64",
// sym::log10f32 => "llvm.log10.f32",
// sym::log10f64 => "llvm.log10.f64",
// sym::log2f32 => "llvm.log2.f32",
// sym::log2f64 => "llvm.log2.f64",
// sym::fmaf32 => "llvm.fma.f32",
// sym::fmaf64 => "llvm.fma.f64",
// sym::fabsf32 => "llvm.fabs.f32",
// sym::fabsf64 => "llvm.fabs.f64",
// sym::minnumf32 => "llvm.minnum.f32",
// sym::minnumf64 => "llvm.minnum.f64",
// sym::maxnumf32 => "llvm.maxnum.f32",
// sym::maxnumf64 => "llvm.maxnum.f64",
// sym::copysignf32 => "llvm.copysign.f32",
// sym::copysignf64 => "llvm.copysign.f64",
// sym::floorf32 => "llvm.floor.f32",
// sym::floorf64 => "llvm.floor.f64",
// sym::ceilf32 => "llvm.ceil.f32",
// sym::ceilf64 => "llvm.ceil.f64",
// sym::truncf32 => "llvm.trunc.f32",
// sym::truncf64 => "llvm.trunc.f64",
// sym::rintf32 => "llvm.rint.f32",
// sym::rintf64 => "llvm.rint.f64",
// sym::nearbyintf32 => "llvm.nearbyint.f32",
// sym::nearbyintf64 => "llvm.nearbyint.f64",
// sym::roundf32 => "llvm.round.f32",
// sym::roundf64 => "llvm.round.f64",
// _ => return None,
// };
// Some(cx.get_intrinsic(&llvm_name))
class SimpleIntrinsics
{
public:
static SimpleIntrinsics &get ()
{
static SimpleIntrinsics instance;
return instance;
}
bool lookup_simple_builtin (const std::string &name, tree *builtin)
{
auto it = rust_intrinsic_to_gcc_builtin.find (name);
if (it == rust_intrinsic_to_gcc_builtin.end ())
return false;
return lookup_gcc_builtin (it->second, builtin);
}
private:
static const int builtin_const = 1 << 0;
static const int builtin_noreturn = 1 << 1;
static const int builtin_novops = 1 << 2;
SimpleIntrinsics () { setup (); }
void setup ()
{
tree math_function_type_f32
= build_function_type_list (float_type_node, float_type_node, NULL_TREE);
define_builtin ("sinf32", BUILT_IN_SINF, "__builtin_sinf", "sinf",
math_function_type_f32, builtin_const);
define_builtin ("sqrtf32", BUILT_IN_SQRTF, "__builtin_sqrtf", "sqrtf",
math_function_type_f32, builtin_const);
define_builtin ("unreachable", BUILT_IN_UNREACHABLE,
"__builtin_unreachable", NULL,
build_function_type (void_type_node, void_list_node),
builtin_const | builtin_noreturn);
define_builtin ("abort", BUILT_IN_ABORT, "__builtin_abort", "abort",
build_function_type (void_type_node, void_list_node),
builtin_const | builtin_noreturn);
define_builtin ("breakpoint", BUILT_IN_TRAP, "__builtin_trap", "breakpoint",
build_function_type (void_type_node, void_list_node),
builtin_const | builtin_noreturn);
}
// Define a builtin function. BCODE is the builtin function code
// defined by builtins.def. NAME is the name of the builtin function.
// LIBNAME is the name of the corresponding library function, and is
// NULL if there isn't one. FNTYPE is the type of the function.
// CONST_P is true if the function has the const attribute.
// NORETURN_P is true if the function has the noreturn attribute.
void define_builtin (const std::string rust_name, built_in_function bcode,
const char *name, const char *libname, tree fntype,
int flags)
{
tree decl = add_builtin_function (name, fntype, bcode, BUILT_IN_NORMAL,
libname, NULL_TREE);
if ((flags & builtin_const) != 0)
TREE_READONLY (decl) = 1;
if ((flags & builtin_noreturn) != 0)
TREE_THIS_VOLATILE (decl) = 1;
if ((flags & builtin_novops) != 0)
DECL_IS_NOVOPS (decl) = 1;
set_builtin_decl (bcode, decl, true);
this->builtin_functions_[name] = decl;
if (libname != NULL)
{
decl = add_builtin_function (libname, fntype, bcode, BUILT_IN_NORMAL,
NULL, NULL_TREE);
if ((flags & builtin_const) != 0)
TREE_READONLY (decl) = 1;
if ((flags & builtin_noreturn) != 0)
TREE_THIS_VOLATILE (decl) = 1;
if ((flags & builtin_novops) != 0)
DECL_IS_NOVOPS (decl) = 1;
this->builtin_functions_[libname] = decl;
}
rust_intrinsic_to_gcc_builtin[rust_name] = name;
}
bool lookup_gcc_builtin (const std::string &name, tree *builtin)
{
auto it = builtin_functions_.find (name);
if (it == builtin_functions_.end ())
return false;
*builtin = it->second;
return true;
}
// A mapping of the GCC built-ins exposed to GCC Rust.
std::map builtin_functions_;
std::map rust_intrinsic_to_gcc_builtin;
};
static tree
offset_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype);
static tree
sizeof_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype);
static tree
transmute_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype);
static tree
rotate_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype, tree_code op);
static inline tree
rotate_left_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype)
{
return rotate_intrinsic_handler (ctx, fntype, LROTATE_EXPR);
}
static inline tree
rotate_right_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype)
{
return rotate_intrinsic_handler (ctx, fntype, RROTATE_EXPR);
}
static const std::map>
generic_intrinsics = {{"offset", &offset_intrinsic_handler},
{"size_of", &sizeof_intrinsic_handler},
{"transmute", &transmute_intrinsic_handler},
{"rotate_left", &rotate_left_intrinsic_handler},
{"rotate_right", &rotate_right_intrinsic_handler}};
Intrinsics::Intrinsics (Context *ctx) : ctx (ctx) {}
tree
Intrinsics::compile (TyTy::FnType *fntype)
{
rust_assert (fntype->get_abi () == ABI::INTRINSIC);
tree builtin = error_mark_node;
SimpleIntrinsics &simple_intrinsics = SimpleIntrinsics::get ();
if (simple_intrinsics.lookup_simple_builtin (fntype->get_identifier (),
&builtin))
return builtin;
// is it an generic builtin?
auto it = generic_intrinsics.find (fntype->get_identifier ());
if (it != generic_intrinsics.end ())
return it->second (ctx, fntype);
Location locus = ctx->get_mappings ()->lookup_location (fntype->get_ref ());
rust_error_at (locus, "unknown builtin intrinsic: %s",
fntype->get_identifier ().c_str ());
return error_mark_node;
}
static tree
offset_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype_tyty)
{
rust_assert (fntype_tyty->get_kind () == TyTy::TypeKind::FNDEF);
TyTy::FnType *fntype = static_cast (fntype_tyty);
const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path;
// items can be forward compiled which means we may not need to invoke this
// code. We might also have already compiled this generic function as well.
tree lookup = NULL_TREE;
if (ctx->lookup_function_decl (fntype->get_ty_ref (), &lookup,
fntype->get_id (), fntype))
{
// has this been added to the list then it must be finished
if (ctx->function_completed (lookup))
{
tree dummy = NULL_TREE;
if (!ctx->lookup_function_decl (fntype->get_ty_ref (), &dummy))
{
ctx->insert_function_decl (fntype, lookup);
}
return lookup;
}
}
if (fntype->has_subsititions_defined ())
{
// override the Hir Lookups for the substituions in this context
fntype->override_context ();
}
// offset intrinsic has two params dst pointer and offset isize
if (fntype->get_params ().size () != 2)
{
rust_error_at (fntype->get_ident ().locus,
"invalid number of parameters for offset intrinsic");
return error_mark_node;
}
tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype);
std::string ir_symbol_name
= canonical_path.get () + fntype->subst_as_string ();
std::string asm_name = ctx->mangle_item (fntype, canonical_path);
unsigned int flags = 0;
tree fndecl
= ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name,
flags, fntype->get_ident ().locus);
TREE_PUBLIC (fndecl) = 0;
TREE_READONLY (fndecl) = 1;
DECL_ARTIFICIAL (fndecl) = 1;
DECL_EXTERNAL (fndecl) = 0;
DECL_DECLARED_INLINE_P (fndecl) = 1;
// setup the params
std::vector param_vars;
for (auto &parm : fntype->get_params ())
{
auto &referenced_param = parm.first;
auto ¶m_tyty = parm.second;
auto compiled_param_type = TyTyResolveCompile::compile (ctx, param_tyty);
Location param_locus = referenced_param->get_locus ();
Bvariable *compiled_param_var
= CompileFnParam::compile (ctx, fndecl, referenced_param,
compiled_param_type, param_locus);
param_vars.push_back (compiled_param_var);
}
auto &dst_param = param_vars.at (0);
auto &size_param = param_vars.at (1);
rust_assert (param_vars.size () == 2);
if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars))
return error_mark_node;
tree enclosing_scope = NULL_TREE;
Location start_location = Location ();
Location end_location = Location ();
tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, {},
start_location, end_location);
ctx->push_block (code_block);
// BUILTIN offset FN BODY BEGIN
tree dst = ctx->get_backend ()->var_expression (dst_param, Location ());
tree size = ctx->get_backend ()->var_expression (size_param, Location ());
tree pointer_offset_expr
= pointer_offset_expression (dst, size, BUILTINS_LOCATION);
auto return_statement
= ctx->get_backend ()->return_statement (fndecl, {pointer_offset_expr},
Location ());
ctx->add_statement (return_statement);
// BUILTIN offset FN BODY END
tree bind_tree = ctx->pop_block ();
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
DECL_SAVED_TREE (fndecl) = bind_tree;
ctx->push_function (fndecl);
return fndecl;
}
static tree
sizeof_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype_tyty)
{
rust_assert (fntype_tyty->get_kind () == TyTy::TypeKind::FNDEF);
TyTy::FnType *fntype = static_cast (fntype_tyty);
const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path;
// items can be forward compiled which means we may not need to invoke this
// code. We might also have already compiled this generic function as well.
tree lookup = NULL_TREE;
if (ctx->lookup_function_decl (fntype->get_ty_ref (), &lookup,
fntype->get_id (), fntype))
{
// has this been added to the list then it must be finished
if (ctx->function_completed (lookup))
{
tree dummy = NULL_TREE;
if (!ctx->lookup_function_decl (fntype->get_ty_ref (), &dummy))
{
ctx->insert_function_decl (fntype, lookup);
}
return lookup;
}
}
if (fntype->has_subsititions_defined ())
{
// override the Hir Lookups for the substituions in this context
fntype->override_context ();
}
// size_of has _zero_ parameters its parameter is the generic one
if (fntype->get_params ().size () != 0)
{
rust_error_at (fntype->get_ident ().locus,
"invalid number of parameters for size of intrinsic");
return error_mark_node;
}
// get the template parameter type tree fn size_of();
rust_assert (fntype->get_num_substitutions () == 1);
auto ¶m_mapping = fntype->get_substs ().at (0);
const TyTy::ParamType *param_tyty = param_mapping.get_param_ty ();
TyTy::BaseType *resolved_tyty = param_tyty->resolve ();
tree template_parameter_type
= TyTyResolveCompile::compile (ctx, resolved_tyty);
// build the intrinsic function
tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype);
std::string ir_symbol_name
= canonical_path.get () + fntype->subst_as_string ();
std::string asm_name = ctx->mangle_item (fntype, canonical_path);
unsigned int flags = 0;
tree fndecl
= ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name,
flags, fntype->get_ident ().locus);
TREE_PUBLIC (fndecl) = 0;
TREE_READONLY (fndecl) = 1;
DECL_ARTIFICIAL (fndecl) = 1;
DECL_EXTERNAL (fndecl) = 0;
DECL_DECLARED_INLINE_P (fndecl) = 1;
tree enclosing_scope = NULL_TREE;
Location start_location = Location ();
Location end_location = Location ();
tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, {},
start_location, end_location);
ctx->push_block (code_block);
// BUILTIN size_of FN BODY BEGIN
tree size_expr = TYPE_SIZE_UNIT (template_parameter_type);
auto return_statement
= ctx->get_backend ()->return_statement (fndecl, {size_expr}, Location ());
ctx->add_statement (return_statement);
// BUILTIN size_of FN BODY END
tree bind_tree = ctx->pop_block ();
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
DECL_SAVED_TREE (fndecl) = bind_tree;
ctx->push_function (fndecl);
return fndecl;
}
static tree
transmute_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype_tyty)
{
rust_assert (fntype_tyty->get_kind () == TyTy::TypeKind::FNDEF);
TyTy::FnType *fntype = static_cast (fntype_tyty);
const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path;
// items can be forward compiled which means we may not need to invoke this
// code. We might also have already compiled this generic function as well.
tree lookup = NULL_TREE;
if (ctx->lookup_function_decl (fntype->get_ty_ref (), &lookup,
fntype->get_id (), fntype))
{
// has this been added to the list then it must be finished
if (ctx->function_completed (lookup))
{
tree dummy = NULL_TREE;
if (!ctx->lookup_function_decl (fntype->get_ty_ref (), &dummy))
{
ctx->insert_function_decl (fntype, lookup);
}
return lookup;
}
}
if (fntype->has_subsititions_defined ())
{
// override the Hir Lookups for the substituions in this context
fntype->override_context ();
}
// transmute intrinsic has one parameter
if (fntype->get_params ().size () != 1)
{
rust_error_at (fntype->get_ident ().locus,
"invalid number of parameters for transmute intrinsic");
return error_mark_node;
}
// build the intrinsic function
tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype);
std::string ir_symbol_name
= canonical_path.get () + fntype->subst_as_string ();
std::string asm_name = ctx->mangle_item (fntype, canonical_path);
unsigned int flags = 0;
tree fndecl
= ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name,
flags, fntype->get_ident ().locus);
TREE_PUBLIC (fndecl) = 0;
TREE_READONLY (fndecl) = 1;
DECL_ARTIFICIAL (fndecl) = 1;
DECL_EXTERNAL (fndecl) = 0;
DECL_DECLARED_INLINE_P (fndecl) = 1;
// setup the params
std::vector param_vars;
std::vector compiled_types;
for (auto &parm : fntype->get_params ())
{
auto &referenced_param = parm.first;
auto ¶m_tyty = parm.second;
auto compiled_param_type = TyTyResolveCompile::compile (ctx, param_tyty);
Location param_locus = referenced_param->get_locus ();
Bvariable *compiled_param_var
= CompileFnParam::compile (ctx, fndecl, referenced_param,
compiled_param_type, param_locus);
param_vars.push_back (compiled_param_var);
compiled_types.push_back (compiled_param_type);
}
rust_assert (param_vars.size () == 1);
if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars))
return error_mark_node;
// param to convert
Bvariable *convert_me_param = param_vars.at (0);
tree convert_me_expr
= ctx->get_backend ()->var_expression (convert_me_param, Location ());
// check for transmute pre-conditions
tree target_type_expr = TREE_TYPE (DECL_RESULT (fndecl));
tree source_type_expr = compiled_types.at (0);
tree target_size_expr = TYPE_SIZE (target_type_expr);
tree source_size_expr = TYPE_SIZE (source_type_expr);
// for some reason, unit types and other zero-sized types return NULL for the
// size expressions
unsigned HOST_WIDE_INT target_size
= target_size_expr ? TREE_INT_CST_LOW (target_size_expr) : 0;
unsigned HOST_WIDE_INT source_size
= source_size_expr ? TREE_INT_CST_LOW (source_size_expr) : 0;
// size check for concrete types
// TODO(liushuyu): check alignment for pointers; check for dependently-sized
// types
if (target_size != source_size)
{
rust_error_at (fntype->get_locus (),
"cannot transmute between types of different sizes, or "
"dependently-sized types");
rust_inform (fntype->get_ident ().locus, "source type: %qs (%lu bits)",
fntype->get_params ().at (0).second->as_string ().c_str (),
(unsigned long) source_size);
rust_inform (fntype->get_ident ().locus, "target type: %qs (%lu bits)",
fntype->get_return_type ()->as_string ().c_str (),
(unsigned long) target_size);
}
tree enclosing_scope = NULL_TREE;
Location start_location = Location ();
Location end_location = Location ();
tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, {},
start_location, end_location);
ctx->push_block (code_block);
// BUILTIN transmute FN BODY BEGIN
tree result_type_tree = TREE_TYPE (DECL_RESULT (fndecl));
tree result_expr = error_mark_node;
if (AGGREGATE_TYPE_P (TREE_TYPE (convert_me_expr)))
{
result_expr = fold_build1_loc (Location ().gcc_location (), CONVERT_EXPR,
result_type_tree, convert_me_expr);
}
else
{
result_expr = ctx->get_backend ()->convert_expression (result_type_tree,
convert_me_expr,
Location ());
}
auto return_statement
= ctx->get_backend ()->return_statement (fndecl, {result_expr},
Location ());
ctx->add_statement (return_statement);
// BUILTIN transmute FN BODY END
tree bind_tree = ctx->pop_block ();
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
DECL_SAVED_TREE (fndecl) = bind_tree;
ctx->push_function (fndecl);
return fndecl;
}
static tree
rotate_intrinsic_handler (Context *ctx, TyTy::BaseType *fntype_tyty,
tree_code op)
{
rust_assert (fntype_tyty->get_kind () == TyTy::TypeKind::FNDEF);
TyTy::FnType *fntype = static_cast (fntype_tyty);
const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path;
// items can be forward compiled which means we may not need to invoke this
// code. We might also have already compiled this generic function as well.
tree lookup = NULL_TREE;
if (ctx->lookup_function_decl (fntype->get_ty_ref (), &lookup,
fntype->get_id (), fntype))
{
// has this been added to the list then it must be finished
if (ctx->function_completed (lookup))
{
tree dummy = NULL_TREE;
if (!ctx->lookup_function_decl (fntype->get_ty_ref (), &dummy))
{
ctx->insert_function_decl (fntype, lookup);
}
return lookup;
}
}
if (fntype->has_subsititions_defined ())
{
// override the Hir Lookups for the substitutions in this context
fntype->override_context ();
}
// rotate intrinsic has two parameter
if (fntype->get_params ().size () != 2)
{
rust_error_at (fntype->get_ident ().locus,
"invalid number of parameters for rotate intrinsic");
return error_mark_node;
}
// build the intrinsic function
tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype);
std::string ir_symbol_name
= canonical_path.get () + fntype->subst_as_string ();
std::string asm_name = ctx->mangle_item (fntype, canonical_path);
unsigned int flags = 0;
tree fndecl
= ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name,
flags, fntype->get_ident ().locus);
TREE_PUBLIC (fndecl) = 0;
TREE_READONLY (fndecl) = 1;
DECL_ARTIFICIAL (fndecl) = 1;
DECL_EXTERNAL (fndecl) = 0;
DECL_DECLARED_INLINE_P (fndecl) = 1;
// setup the params
std::vector param_vars;
for (auto &parm : fntype->get_params ())
{
auto &referenced_param = parm.first;
auto ¶m_tyty = parm.second;
auto compiled_param_type = TyTyResolveCompile::compile (ctx, param_tyty);
Location param_locus = referenced_param->get_locus ();
Bvariable *compiled_param_var
= CompileFnParam::compile (ctx, fndecl, referenced_param,
compiled_param_type, param_locus);
param_vars.push_back (compiled_param_var);
}
auto &x_param = param_vars.at (0);
auto &y_param = param_vars.at (1);
rust_assert (param_vars.size () == 2);
if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars))
return error_mark_node;
tree enclosing_scope = NULL_TREE;
Location start_location = Location ();
Location end_location = Location ();
tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, {},
start_location, end_location);
ctx->push_block (code_block);
// BUILTIN rotate FN BODY BEGIN
tree x = ctx->get_backend ()->var_expression (x_param, Location ());
tree y = ctx->get_backend ()->var_expression (y_param, Location ());
tree rotate_expr
= fold_build2_loc (BUILTINS_LOCATION, op, TREE_TYPE (x), x, y);
auto return_statement
= ctx->get_backend ()->return_statement (fndecl, {rotate_expr},
Location ());
ctx->add_statement (return_statement);
// BUILTIN rotate FN BODY END
tree bind_tree = ctx->pop_block ();
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
DECL_SAVED_TREE (fndecl) = bind_tree;
ctx->push_function (fndecl);
return fndecl;
}
} // namespace Compile
} // namespace Rust