// 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 "rust-compile-context.h" #include "rust-compile-type.h" #include "rust-compile-expr.h" #include "rust-compile-fnparam.h" #include "rust-builtins.h" #include "rust-diagnostics.h" #include "rust-location.h" #include "rust-constexpr.h" #include "rust-tree.h" #include "tree-core.h" #include "rust-gcc.h" #include "print-tree.h" #include "fold-const.h" #include "langhooks.h" #include "rust-gcc.h" #include "rust-constexpr.h" #include "print-tree.h" // declaration taken from "stringpool.h" // the get_identifier macro causes compilation issues extern tree get_identifier (const char *); namespace Rust { namespace Compile { static bool is_basic_integer_type (TyTy::BaseType *type) { switch (type->get_kind ()) { case TyTy::INT: case TyTy::UINT: case TyTy::USIZE: case TyTy::ISIZE: return true; default: return false; break; } } static bool check_for_basic_integer_type (const std::string &intrinsic_str, location_t locus, TyTy::BaseType *type) { auto is_basic_integer = is_basic_integer_type (type); if (!is_basic_integer) { rust_error_at ( locus, "%s intrinsics can only be used with basic integer types (got %qs)", intrinsic_str.c_str (), type->get_name ().c_str ()); } return is_basic_integer; } static tree offset_handler (Context *ctx, TyTy::FnType *fntype); static tree sizeof_handler (Context *ctx, TyTy::FnType *fntype); static tree transmute_handler (Context *ctx, TyTy::FnType *fntype); static tree rotate_handler (Context *ctx, TyTy::FnType *fntype, tree_code op); static tree wrapping_op_handler_inner (Context *ctx, TyTy::FnType *fntype, tree_code op); static tree op_with_overflow_inner (Context *ctx, TyTy::FnType *fntype, tree_code op); static tree uninit_handler (Context *ctx, TyTy::FnType *fntype); static tree move_val_init_handler (Context *ctx, TyTy::FnType *fntype); static tree assume_handler (Context *ctx, TyTy::FnType *fntype); enum class Prefetch { Read, Write }; static tree prefetch_data_handler (Context *ctx, TyTy::FnType *fntype, Prefetch kind); static inline tree rotate_left_handler (Context *ctx, TyTy::FnType *fntype) { return rotate_handler (ctx, fntype, LROTATE_EXPR); } static inline tree rotate_right_handler (Context *ctx, TyTy::FnType *fntype) { return rotate_handler (ctx, fntype, RROTATE_EXPR); } const static std::function wrapping_op_handler (tree_code op) { return [op] (Context *ctx, TyTy::FnType *fntype) { return wrapping_op_handler_inner (ctx, fntype, op); }; } const static std::function op_with_overflow (tree_code op) { return [op] (Context *ctx, TyTy::FnType *fntype) { return op_with_overflow_inner (ctx, fntype, op); }; } static inline tree prefetch_read_data (Context *ctx, TyTy::FnType *fntype) { return prefetch_data_handler (ctx, fntype, Prefetch::Read); } static inline tree prefetch_write_data (Context *ctx, TyTy::FnType *fntype) { return prefetch_data_handler (ctx, fntype, Prefetch::Write); } static tree atomic_store_handler_inner (Context *ctx, TyTy::FnType *fntype, int ordering); static tree atomic_load_handler_inner (Context *ctx, TyTy::FnType *fntype, int ordering); static inline std::function atomic_store_handler (int ordering) { return [ordering] (Context *ctx, TyTy::FnType *fntype) { return atomic_store_handler_inner (ctx, fntype, ordering); }; } static inline std::function atomic_load_handler (int ordering) { return [ordering] (Context *ctx, TyTy::FnType *fntype) { return atomic_load_handler_inner (ctx, fntype, ordering); }; } static inline tree unchecked_op_inner (Context *ctx, TyTy::FnType *fntype, tree_code op); const static std::function unchecked_op_handler (tree_code op) { return [op] (Context *ctx, TyTy::FnType *fntype) { return unchecked_op_inner (ctx, fntype, op); }; } static inline tree copy_handler_inner (Context *ctx, TyTy::FnType *fntype, bool overlaps); const static std::function copy_handler (bool overlaps) { return [overlaps] (Context *ctx, TyTy::FnType *fntype) { return copy_handler_inner (ctx, fntype, overlaps); }; } static inline tree expect_handler_inner (Context *ctx, TyTy::FnType *fntype, bool likely); const static std::function expect_handler (bool likely) { return [likely] (Context *ctx, TyTy::FnType *fntype) { return expect_handler_inner (ctx, fntype, likely); }; } inline tree sorry_handler (Context *ctx, TyTy::FnType *fntype) { rust_sorry_at (fntype->get_locus (), "intrinsic %qs is not yet implemented", fntype->get_identifier ().c_str ()); return error_mark_node; } static const std::map> generic_intrinsics = { {"offset", offset_handler}, {"size_of", sizeof_handler}, {"transmute", transmute_handler}, {"rotate_left", rotate_left_handler}, {"rotate_right", rotate_right_handler}, {"wrapping_add", wrapping_op_handler (PLUS_EXPR)}, {"wrapping_sub", wrapping_op_handler (MINUS_EXPR)}, {"wrapping_mul", wrapping_op_handler (MULT_EXPR)}, {"add_with_overflow", op_with_overflow (PLUS_EXPR)}, {"sub_with_overflow", op_with_overflow (MINUS_EXPR)}, {"mul_with_overflow", op_with_overflow (MULT_EXPR)}, {"copy", copy_handler (true)}, {"copy_nonoverlapping", copy_handler (false)}, {"prefetch_read_data", prefetch_read_data}, {"prefetch_write_data", prefetch_write_data}, {"atomic_store_seqcst", atomic_store_handler (__ATOMIC_SEQ_CST)}, {"atomic_store_release", atomic_store_handler (__ATOMIC_RELEASE)}, {"atomic_store_relaxed", atomic_store_handler (__ATOMIC_RELAXED)}, {"atomic_store_unordered", atomic_store_handler (__ATOMIC_RELAXED)}, {"atomic_load_seqcst", atomic_load_handler (__ATOMIC_SEQ_CST)}, {"atomic_load_acquire", atomic_load_handler (__ATOMIC_ACQUIRE)}, {"atomic_load_relaxed", atomic_load_handler (__ATOMIC_RELAXED)}, {"atomic_load_unordered", atomic_load_handler (__ATOMIC_RELAXED)}, {"unchecked_add", unchecked_op_handler (PLUS_EXPR)}, {"unchecked_sub", unchecked_op_handler (MINUS_EXPR)}, {"unchecked_mul", unchecked_op_handler (MULT_EXPR)}, {"unchecked_div", unchecked_op_handler (TRUNC_DIV_EXPR)}, {"unchecked_rem", unchecked_op_handler (TRUNC_MOD_EXPR)}, {"unchecked_shl", unchecked_op_handler (LSHIFT_EXPR)}, {"unchecked_shr", unchecked_op_handler (RSHIFT_EXPR)}, {"uninit", uninit_handler}, {"move_val_init", move_val_init_handler}, {"likely", expect_handler (true)}, {"unlikely", expect_handler (false)}, {"assume", assume_handler}, }; Intrinsics::Intrinsics (Context *ctx) : ctx (ctx) {} /** * Returns a FUNC_DECL corresponding to the intrinsic function FNTYPE. If a * corresponding builtin exists, returns it. If not, search in the generic * intrinsics declared and delegate the return to the corresponding handler. * * @param fntype The Rust function type that should be implemented by the * compiler */ tree Intrinsics::compile (TyTy::FnType *fntype) { rust_assert (fntype->get_abi () == ABI::INTRINSIC); tree builtin = error_mark_node; BuiltinsContext &builtin_ctx = BuiltinsContext::get (); if (builtin_ctx.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_t locus = ctx->get_mappings ()->lookup_location (fntype->get_ref ()); rust_error_at (locus, ErrorCode::E0093, "unrecognized intrinsic function: %<%s%>", fntype->get_identifier ().c_str ()); return error_mark_node; } /** * 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. */ static bool check_for_cached_intrinsic (Context *ctx, TyTy::FnType *fntype, tree *lookup) { const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path; std::string asm_name = ctx->mangle_item (fntype, canonical_path); if (ctx->lookup_function_decl (fntype->get_ty_ref (), lookup, fntype->get_id (), fntype, asm_name)) { return true; } return false; } /** * Maybe override the Hir Lookups for the substituions in this context */ static void maybe_override_ctx (TyTy::FnType *fntype) { if (fntype->has_substitutions_defined ()) fntype->override_context (); } /** * Compile and setup a function's parameters */ static void compile_fn_params (Context *ctx, TyTy::FnType *fntype, tree fndecl, std::vector *compiled_param_variables, std::vector *compiled_param_types = nullptr) { 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_t param_locus = referenced_param->get_locus (); Bvariable *compiled_param_var = CompileFnParam::compile (ctx, fndecl, referenced_param, compiled_param_type, param_locus); compiled_param_variables->push_back (compiled_param_var); if (compiled_param_types) compiled_param_types->push_back (compiled_param_type); } } static tree compile_intrinsic_function (Context *ctx, TyTy::FnType *fntype) { maybe_override_ctx (fntype); const Resolver::CanonicalPath &canonical_path = fntype->get_ident ().path; 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 = 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; return fndecl; } static void enter_intrinsic_block (Context *ctx, tree fndecl, const std::vector &vars = {}) { tree enclosing_scope = NULL_TREE; location_t start_location = UNDEF_LOCATION; location_t end_location = UNDEF_LOCATION; auto block = Backend::block (fndecl, enclosing_scope, vars, start_location, end_location); ctx->push_block (block); } static void finalize_intrinsic_block (Context *ctx, tree fndecl) { tree bind_tree = ctx->pop_block (); gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR); DECL_SAVED_TREE (fndecl) = bind_tree; ctx->push_function (fndecl); DECL_DECLARED_CONSTEXPR_P (fndecl) = 1; maybe_save_constexpr_fundef (fndecl); } static tree offset_handler (Context *ctx, TyTy::FnType *fntype) { // offset intrinsic has two params dst pointer and offset isize rust_assert (fntype->get_params ().size () == 2); auto fndecl = compile_intrinsic_function (ctx, fntype); std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); auto &dst_param = param_vars.at (0); auto &size_param = param_vars.at (1); rust_assert (param_vars.size () == 2); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN offset FN BODY BEGIN tree dst = Backend::var_expression (dst_param, UNDEF_LOCATION); tree size = Backend::var_expression (size_param, UNDEF_LOCATION); tree pointer_offset_expr = pointer_offset_expression (dst, size, BUILTINS_LOCATION); auto return_statement = Backend::return_statement (fndecl, pointer_offset_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN offset FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree sizeof_handler (Context *ctx, TyTy::FnType *fntype) { // size_of has _zero_ parameters its parameter is the generic one rust_assert (fntype->get_params ().size () == 0); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // 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); enter_intrinsic_block (ctx, fndecl); // BUILTIN size_of FN BODY BEGIN tree size_expr = TYPE_SIZE_UNIT (template_parameter_type); auto return_statement = Backend::return_statement (fndecl, size_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN size_of FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree transmute_handler (Context *ctx, TyTy::FnType *fntype) { // transmute intrinsic has one parameter rust_assert (fntype->get_params ().size () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); std::vector param_vars; std::vector compiled_types; compile_fn_params (ctx, fntype, fndecl, ¶m_vars, &compiled_types); if (!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 = Backend::var_expression (convert_me_param, UNDEF_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); } enter_intrinsic_block (ctx, fndecl); // BUILTIN transmute FN BODY BEGIN // Return *((orig_type*)&decl) */ tree t = build_fold_addr_expr_loc (UNKNOWN_LOCATION, convert_me_expr); t = fold_build1_loc (UNKNOWN_LOCATION, NOP_EXPR, build_pointer_type (target_type_expr), t); tree result_expr = build_fold_indirect_ref_loc (UNKNOWN_LOCATION, t); auto return_statement = Backend::return_statement (fndecl, result_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN transmute FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree rotate_handler (Context *ctx, TyTy::FnType *fntype, tree_code op) { // rotate intrinsic has two parameter rust_assert (fntype->get_params ().size () == 2); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); auto &x_param = param_vars.at (0); auto &y_param = param_vars.at (1); rust_assert (param_vars.size () == 2); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN rotate FN BODY BEGIN tree x = Backend::var_expression (x_param, UNDEF_LOCATION); tree y = Backend::var_expression (y_param, UNDEF_LOCATION); tree rotate_expr = fold_build2_loc (BUILTINS_LOCATION, op, TREE_TYPE (x), x, y); auto return_statement = Backend::return_statement (fndecl, rotate_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN rotate FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } /** * pub fn wrapping_{add, sub, mul}(lhs: T, rhs: T) -> T; */ static tree wrapping_op_handler_inner (Context *ctx, TyTy::FnType *fntype, tree_code op) { // wrapping_ intrinsics have two parameter rust_assert (fntype->get_params ().size () == 2); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); auto &lhs_param = param_vars.at (0); auto &rhs_param = param_vars.at (1); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN wrapping_ FN BODY BEGIN auto lhs = Backend::var_expression (lhs_param, UNDEF_LOCATION); auto rhs = Backend::var_expression (rhs_param, UNDEF_LOCATION); // Operations are always wrapping in Rust, as we have -fwrapv enabled by // default. The difference between a wrapping_{add, sub, mul} and a regular // arithmetic operation is that these intrinsics do not panic - they always // carry over. auto wrap_expr = build2 (op, TREE_TYPE (lhs), lhs, rhs); auto return_statement = Backend::return_statement (fndecl, wrap_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN wrapping_ FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } /** * pub fn add_with_overflow(x: T, y: T) -> (T, bool); */ static tree op_with_overflow_inner (Context *ctx, TyTy::FnType *fntype, tree_code op) { // wrapping_ intrinsics have two parameter rust_assert (fntype->get_params ().size () == 2); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); auto &x_param = param_vars.at (0); auto &y_param = param_vars.at (1); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; 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); // this should match y as well or we can take it from the TyTy structure tree tmp_stmt = error_mark_node; Bvariable *result_variable = Backend::temporary_variable (fndecl, NULL_TREE, template_parameter_type, NULL_TREE, true /*address_is_taken*/, UNDEF_LOCATION, &tmp_stmt); Bvariable *bool_variable = Backend::temporary_variable (fndecl, NULL_TREE, boolean_type_node, NULL_TREE, true /*address_is_taken*/, UNDEF_LOCATION, &tmp_stmt); enter_intrinsic_block (ctx, fndecl, {result_variable, bool_variable}); // BUILTIN op_with_overflow FN BODY BEGIN auto x = Backend::var_expression (x_param, UNDEF_LOCATION); auto y = Backend::var_expression (y_param, UNDEF_LOCATION); tree overflow_builtin = error_mark_node; switch (op) { case PLUS_EXPR: BuiltinsContext::get ().lookup_simple_builtin ("__builtin_add_overflow", &overflow_builtin); break; case MINUS_EXPR: BuiltinsContext::get ().lookup_simple_builtin ("__builtin_sub_overflow", &overflow_builtin); break; case MULT_EXPR: BuiltinsContext::get ().lookup_simple_builtin ("__builtin_mul_overflow", &overflow_builtin); break; default: rust_unreachable (); break; } rust_assert (overflow_builtin != error_mark_node); tree bool_decl = bool_variable->get_tree (BUILTINS_LOCATION); tree result_decl = result_variable->get_tree (BUILTINS_LOCATION); tree result_ref = build_fold_addr_expr_loc (BUILTINS_LOCATION, result_decl); tree builtin_call = build_call_expr_loc (BUILTINS_LOCATION, overflow_builtin, 3, x, y, result_ref); tree overflow_assignment = Backend::assignment_statement (bool_decl, builtin_call, BUILTINS_LOCATION); ctx->add_statement (overflow_assignment); std::vector vals = {result_decl, bool_decl}; tree tuple_type = TREE_TYPE (DECL_RESULT (fndecl)); tree result_expr = Backend::constructor_expression (tuple_type, false, vals, -1, UNDEF_LOCATION); auto return_statement = Backend::return_statement (fndecl, result_expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN wrapping_ FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } /** * fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); * fn copy(src: *const T, dst: *mut T, count: usize); */ static tree copy_handler_inner (Context *ctx, TyTy::FnType *fntype, bool overlaps) { rust_assert (fntype->get_params ().size () == 3); rust_assert (fntype->get_num_substitutions () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // Most intrinsic functions are pure - not `copy_nonoverlapping` and `copy` TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN copy_nonoverlapping BODY BEGIN auto src = Backend::var_expression (param_vars[0], UNDEF_LOCATION); auto dst = Backend::var_expression (param_vars[1], UNDEF_LOCATION); auto count = Backend::var_expression (param_vars[2], UNDEF_LOCATION); // We want to create the following statement // memcpy(dst, src, size_of::()); // so // memcpy(dst, src, size_expr); auto *resolved_ty = fntype->get_substs ().at (0).get_param_ty ()->resolve (); auto param_type = TyTyResolveCompile::compile (ctx, resolved_ty); tree size_expr = build2 (MULT_EXPR, size_type_node, TYPE_SIZE_UNIT (param_type), count); tree memcpy_raw = nullptr; BuiltinsContext::get ().lookup_simple_builtin (overlaps ? "__builtin_memmove" : "__builtin_memcpy", &memcpy_raw); rust_assert (memcpy_raw); auto memcpy = build_fold_addr_expr_loc (UNKNOWN_LOCATION, memcpy_raw); auto copy_call = Backend::call_expression (memcpy, {dst, src, size_expr}, nullptr, UNDEF_LOCATION); ctx->add_statement (copy_call); // BUILTIN copy_nonoverlapping BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree make_unsigned_long_tree (unsigned long value) { return build_int_cst (integer_type_node, value); } static tree prefetch_data_handler (Context *ctx, TyTy::FnType *fntype, Prefetch kind) { rust_assert (fntype->get_params ().size () == 2); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // prefetching isn't pure and shouldn't be discarded after GIMPLE TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; std::vector args; compile_fn_params (ctx, fntype, fndecl, &args); if (!Backend::function_set_parameters (fndecl, args)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); auto addr = Backend::var_expression (args[0], UNDEF_LOCATION); // The core library technically allows you to pass any i32 value as a // locality, but LLVM will then complain if the value cannot be constant // evaluated. For now, we ignore the locality argument and instead always // pass `3` (the most restrictive value). This allows us to still have // prefetch behavior, just not as granular as expected. In future Rust // versions, we hope that prefetch intrinsics will be split up according to // locality, similarly to atomic intrinsics. // The solution is to try and perform constant folding for the locality // argument, or instead of creating a new function definition, modify the call // site directly This has the bad side-effect of creating warnings about // `unused name - locality`, which we hack away here: // TODO: Take care of handling locality properly Backend::var_expression (args[1], UNDEF_LOCATION); auto rw_flag = make_unsigned_long_tree (kind == Prefetch::Write ? 1 : 0); auto prefetch_raw = NULL_TREE; auto ok = BuiltinsContext::get ().lookup_simple_builtin ("__builtin_prefetch", &prefetch_raw); rust_assert (ok); auto prefetch = build_fold_addr_expr_loc (UNKNOWN_LOCATION, prefetch_raw); auto prefetch_call = Backend::call_expression (prefetch, {addr, rw_flag, // locality arg make_unsigned_long_tree (3)}, nullptr, UNDEF_LOCATION); TREE_READONLY (prefetch_call) = 0; TREE_SIDE_EFFECTS (prefetch_call) = 1; ctx->add_statement (prefetch_call); finalize_intrinsic_block (ctx, fndecl); return fndecl; } static std::string build_atomic_builtin_name (const std::string &prefix, location_t locus, TyTy::BaseType *operand_type) { static const std::map allowed_types = { {"i8", "1"}, {"i16", "2"}, {"i32", "4"}, {"i64", "8"}, {"i128", "16"}, {"isize", "8"}, {"u8", "1"}, {"u16", "2"}, {"u32", "4"}, {"u64", "8"}, {"u128", "16"}, {"usize", "8"}, }; // TODO: Can we maybe get the generic version (atomic_store_n) to work... This // would be so much better std::string result = "__" + prefix; // + "n"; auto type_name = operand_type->get_name (); if (type_name == "usize" || type_name == "isize") { rust_sorry_at ( locus, "atomics are not yet available for size types (usize, isize)"); return ""; } if (type_name.at (0) == 'i') { rust_sorry_at (locus, "atomics are not yet supported for signed " "integer types (i8, i16, i32, i64, i128)"); return ""; } auto type_size_str = allowed_types.find (type_name); if (!check_for_basic_integer_type ("atomic", locus, operand_type)) return ""; result += type_size_str->second; return result; } static tree atomic_store_handler_inner (Context *ctx, TyTy::FnType *fntype, int ordering) { rust_assert (fntype->get_params ().size () == 2); rust_assert (fntype->get_num_substitutions () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // Most intrinsic functions are pure but not the atomic ones TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; // setup the params std::vector param_vars; std::vector types; compile_fn_params (ctx, fntype, fndecl, ¶m_vars, &types); auto ok = Backend::function_set_parameters (fndecl, param_vars); rust_assert (ok); enter_intrinsic_block (ctx, fndecl); auto dst = Backend::var_expression (param_vars[0], UNDEF_LOCATION); TREE_READONLY (dst) = 0; auto value = Backend::var_expression (param_vars[1], UNDEF_LOCATION); auto memorder = make_unsigned_long_tree (ordering); auto monomorphized_type = fntype->get_substs ()[0].get_param_ty ()->resolve (); auto builtin_name = build_atomic_builtin_name ("atomic_store_", fntype->get_locus (), monomorphized_type); if (builtin_name.empty ()) return error_mark_node; tree atomic_store_raw = nullptr; BuiltinsContext::get ().lookup_simple_builtin (builtin_name, &atomic_store_raw); rust_assert (atomic_store_raw); auto atomic_store = build_fold_addr_expr_loc (UNKNOWN_LOCATION, atomic_store_raw); auto store_call = Backend::call_expression (atomic_store, {dst, value, memorder}, nullptr, UNDEF_LOCATION); TREE_READONLY (store_call) = 0; TREE_SIDE_EFFECTS (store_call) = 1; ctx->add_statement (store_call); finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree atomic_load_handler_inner (Context *ctx, TyTy::FnType *fntype, int ordering) { rust_assert (fntype->get_params ().size () == 1); rust_assert (fntype->get_num_substitutions () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // Most intrinsic functions are pure but not the atomic ones // FIXME: Is atomic_load_* pure? Feels like it shouldn't so TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; // setup the params std::vector param_vars; std::vector types; compile_fn_params (ctx, fntype, fndecl, ¶m_vars, &types); auto ok = Backend::function_set_parameters (fndecl, param_vars); rust_assert (ok); enter_intrinsic_block (ctx, fndecl); auto src = Backend::var_expression (param_vars[0], UNDEF_LOCATION); auto memorder = make_unsigned_long_tree (ordering); auto monomorphized_type = fntype->get_substs ()[0].get_param_ty ()->resolve (); auto builtin_name = build_atomic_builtin_name ("atomic_load_", fntype->get_locus (), monomorphized_type); if (builtin_name.empty ()) return error_mark_node; tree atomic_load_raw = nullptr; BuiltinsContext::get ().lookup_simple_builtin (builtin_name, &atomic_load_raw); rust_assert (atomic_load_raw); auto atomic_load = build_fold_addr_expr_loc (UNKNOWN_LOCATION, atomic_load_raw); auto load_call = Backend::call_expression (atomic_load, {src, memorder}, nullptr, UNDEF_LOCATION); auto return_statement = Backend::return_statement (fndecl, load_call, UNDEF_LOCATION); TREE_READONLY (load_call) = 0; TREE_SIDE_EFFECTS (load_call) = 1; ctx->add_statement (return_statement); finalize_intrinsic_block (ctx, fndecl); return fndecl; } static inline tree unchecked_op_inner (Context *ctx, TyTy::FnType *fntype, tree_code op) { rust_assert (fntype->get_params ().size () == 2); rust_assert (fntype->get_num_substitutions () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN unchecked_ BODY BEGIN auto x = Backend::var_expression (param_vars[0], UNDEF_LOCATION); auto y = Backend::var_expression (param_vars[1], UNDEF_LOCATION); auto *monomorphized_type = fntype->get_substs ().at (0).get_param_ty ()->resolve (); check_for_basic_integer_type ("unchecked operation", fntype->get_locus (), monomorphized_type); auto expr = build2 (op, TREE_TYPE (x), x, y); auto return_statement = Backend::return_statement (fndecl, expr, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN unchecked_ BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree uninit_handler (Context *ctx, TyTy::FnType *fntype) { // uninit has _zero_ parameters its parameter is the generic one rust_assert (fntype->get_params ().size () == 0); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // Most intrinsic functions are pure - not `uninit_handler` TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; // get the template parameter type tree fn uninit(); 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); // result temporary tree dst_type = TREE_TYPE (DECL_RESULT (fndecl)); rust_assert (TYPE_SIZE_UNIT (template_parameter_type) == TYPE_SIZE_UNIT (dst_type)); tree tmp_stmt = error_mark_node; Bvariable *bvar = Backend::temporary_variable (fndecl, NULL_TREE, dst_type, NULL_TREE, true /*address_is_taken*/, UNDEF_LOCATION, &tmp_stmt); enter_intrinsic_block (ctx, fndecl, {bvar}); // BUILTIN size_of FN BODY BEGIN tree memset_builtin = error_mark_node; BuiltinsContext::get ().lookup_simple_builtin ("__builtin_memset", &memset_builtin); rust_assert (memset_builtin != error_mark_node); // call memset with 0x01 and size of the thing see // https://github.com/Rust-GCC/gccrs/issues/1899 tree dst = bvar->get_tree (BUILTINS_LOCATION); tree dst_addr = build_fold_addr_expr_loc (BUILTINS_LOCATION, dst); tree constant_byte = build_int_cst (integer_type_node, 0x01); tree size_expr = TYPE_SIZE_UNIT (template_parameter_type); tree memset_call = build_call_expr_loc (BUILTINS_LOCATION, memset_builtin, 3, dst_addr, constant_byte, size_expr); ctx->add_statement (memset_call); auto return_statement = Backend::return_statement (fndecl, dst, UNDEF_LOCATION); ctx->add_statement (return_statement); // BUILTIN size_of FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree move_val_init_handler (Context *ctx, TyTy::FnType *fntype) { rust_assert (fntype->get_params ().size () == 2); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // Most intrinsic functions are pure - not `move_val_init` TREE_READONLY (fndecl) = 0; TREE_SIDE_EFFECTS (fndecl) = 1; // 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); std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN size_of FN BODY BEGIN tree dst = Backend::var_expression (param_vars[0], UNDEF_LOCATION); tree src = Backend::var_expression (param_vars[1], UNDEF_LOCATION); tree size = TYPE_SIZE_UNIT (template_parameter_type); tree memcpy_builtin = error_mark_node; BuiltinsContext::get ().lookup_simple_builtin ("__builtin_memcpy", &memcpy_builtin); rust_assert (memcpy_builtin != error_mark_node); src = build_fold_addr_expr_loc (BUILTINS_LOCATION, src); tree memset_call = build_call_expr_loc (BUILTINS_LOCATION, memcpy_builtin, 3, dst, src, size); ctx->add_statement (memset_call); // BUILTIN size_of FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static inline tree expect_handler_inner (Context *ctx, TyTy::FnType *fntype, bool likely) { rust_assert (fntype->get_params ().size () == 1); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); enter_intrinsic_block (ctx, fndecl); // BUILTIN expect_handler_inner FN BODY BEGIN // setup the params std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); tree expr = Backend::var_expression (param_vars[0], UNDEF_LOCATION); tree expect_fn_raw = nullptr; BuiltinsContext::get ().lookup_simple_builtin ("__builtin_expect", &expect_fn_raw); rust_assert (expect_fn_raw); auto expect_fn = build_fold_addr_expr_loc (BUILTINS_LOCATION, expect_fn_raw); // we need to convert the expression return type to long to match the expected // parameter type of __builtin_expect auto expect_src = build1 (CONVERT_EXPR, long_integer_type_node, expr); auto expect_value = make_unsigned_long_tree (static_cast (likely)); auto expect_call = Backend::call_expression (expect_fn, {expect_src, expect_value}, nullptr, BUILTINS_LOCATION); // the return value also needs to be casted (to bool) auto expect_call_bool = build1 (CONVERT_EXPR, boolean_type_node, expect_call); auto return_statement = Backend::return_statement (fndecl, expect_call_bool, BUILTINS_LOCATION); ctx->add_statement (return_statement); // BUILTIN expect_handler_inner FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } static tree assume_handler (Context *ctx, TyTy::FnType *fntype) { // TODO: make sure this is actually helping the compiler optimize rust_assert (fntype->get_params ().size () == 1); rust_assert (fntype->param_at (0).second->get_kind () == TyTy::TypeKind::BOOL); tree lookup = NULL_TREE; if (check_for_cached_intrinsic (ctx, fntype, &lookup)) return lookup; auto fndecl = compile_intrinsic_function (ctx, fntype); // TODO: make sure these are necessary TREE_READONLY (fndecl) = 0; DECL_DISREGARD_INLINE_LIMITS (fndecl) = 1; DECL_ATTRIBUTES (fndecl) = tree_cons (get_identifier ("always_inline"), NULL_TREE, DECL_ATTRIBUTES (fndecl)); std::vector param_vars; compile_fn_params (ctx, fntype, fndecl, ¶m_vars); if (!Backend::function_set_parameters (fndecl, param_vars)) return error_mark_node; enter_intrinsic_block (ctx, fndecl); // BUILTIN assume FN BODY BEGIN tree val = Backend::var_expression (param_vars[0], UNDEF_LOCATION); tree assume_expr = build_call_expr_internal_loc (UNDEF_LOCATION, IFN_ASSUME, void_type_node, 1, val); TREE_SIDE_EFFECTS (assume_expr) = 1; ctx->add_statement (assume_expr); // BUILTIN size_of FN BODY END finalize_intrinsic_block (ctx, fndecl); return fndecl; } } // namespace Compile } // namespace Rust