diff --git a/gcc/rust/backend/rust-builtins.cc b/gcc/rust/backend/rust-builtins.cc index 881561fdde3..12ae27e20e1 100644 --- a/gcc/rust/backend/rust-builtins.cc +++ b/gcc/rust/backend/rust-builtins.cc @@ -29,6 +29,10 @@ BuiltinsContext & BuiltinsContext::get () { static BuiltinsContext instance; + + if (instance.setup_state == SetupState::UNINITIALIZED) + instance.setup (); + return instance; } @@ -44,7 +48,26 @@ BuiltinsContext::lookup_simple_builtin (const std::string &name, tree *builtin) return lookup_gcc_builtin (*to_search, builtin); } -BuiltinsContext::BuiltinsContext () { setup (); } +LlvmBuiltinMappingResult +BuiltinsContext::map_llvm_to_gcc_builtin (const std::string &name, + tree *resolved, + LlvmBuiltinAdapter *adapter) +{ + rust_assert (resolved != nullptr); + + auto mapping = llvm_to_gcc_builtin.find (name); + if (mapping == llvm_to_gcc_builtin.end ()) + return LlvmBuiltinMappingResult::NOT_MAPPED; + + *resolved = NULL_TREE; + if (!lookup_gcc_builtin (mapping->second.gcc_name, resolved)) + return LlvmBuiltinMappingResult::TARGET_UNAVAILABLE; + + *adapter = mapping->second.adapter; + return LlvmBuiltinMappingResult::RESOLVED; +} + +BuiltinsContext::BuiltinsContext () : setup_state (SetupState::UNINITIALIZED) {} /** * Define a function type according to `builtin-types.def` @@ -355,14 +378,83 @@ BuiltinsContext::register_rust_mappings () }; } +void +BuiltinsContext::register_llvm_to_gcc_builtin () +{ + llvm_to_gcc_builtin = { + {"llvm.x86.rdrand.16", + {"__builtin_ia32_rdrand16_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + {"llvm.x86.rdrand.32", + {"__builtin_ia32_rdrand32_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + {"llvm.x86.rdrand.64", + {"__builtin_ia32_rdrand64_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + {"llvm.x86.rdseed.16", + {"__builtin_ia32_rdseed_hi_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + {"llvm.x86.rdseed.32", + {"__builtin_ia32_rdseed_si_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + {"llvm.x86.rdseed.64", + {"__builtin_ia32_rdseed_di_step", + LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS}}, + + {"llvm.x86.addcarry.32", + {"__builtin_ia32_addcarryx_u32", + LlvmBuiltinAdapter::OUTPUT_POINTER_STATUS_VALUE}}, + {"llvm.x86.addcarryx.u32", + {"__builtin_ia32_addcarryx_u32", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + {"llvm.x86.subborrow.32", + {"__builtin_ia32_sbb_u32", + LlvmBuiltinAdapter::OUTPUT_POINTER_STATUS_VALUE}}, + {"llvm.x86.addcarry.64", + {"__builtin_ia32_addcarryx_u64", + LlvmBuiltinAdapter::OUTPUT_POINTER_STATUS_VALUE}}, + {"llvm.x86.addcarryx.u64", + {"__builtin_ia32_addcarryx_u64", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + {"llvm.x86.subborrow.64", + {"__builtin_ia32_sbb_u64", + LlvmBuiltinAdapter::OUTPUT_POINTER_STATUS_VALUE}}, + + {"llvm.x86.vcvtph2ps.128", + {"__builtin_ia32_vcvtph2ps", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + {"llvm.x86.vcvtph2ps.256", + {"__builtin_ia32_vcvtph2ps256", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + {"llvm.x86.vcvtps2ph.128", + {"__builtin_ia32_vcvtps2ph", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + {"llvm.x86.vcvtps2ph.256", + {"__builtin_ia32_vcvtps2ph256", LlvmBuiltinAdapter::FORWARD_ARGUMENTS}}, + }; +} + +void +BuiltinsContext::register_builtin (tree decl) +{ + rust_assert (TREE_CODE (decl) == FUNCTION_DECL); + rust_assert (DECL_NAME (decl)); + + const char *name = IDENTIFIER_POINTER (DECL_NAME (decl)); + builtin_functions.insert ({std::string (name), decl}); +} + void BuiltinsContext::setup () { + rust_assert (setup_state == SetupState::UNINITIALIZED); + setup_state = SetupState::INITIALIZING; + define_builtin_types (); define_builtin_attributes (); define_builtins (); + targetm.init_builtins (); + register_rust_mappings (); + register_llvm_to_gcc_builtin (); + + setup_state = SetupState::READY; } bool diff --git a/gcc/rust/backend/rust-builtins.h b/gcc/rust/backend/rust-builtins.h index 22250fa8905..e2c7f9816f9 100644 --- a/gcc/rust/backend/rust-builtins.h +++ b/gcc/rust/backend/rust-builtins.h @@ -79,6 +79,26 @@ namespace Compile { // }; // Some(cx.get_intrinsic(&llvm_name)) +enum class LlvmBuiltinMappingResult +{ + NOT_MAPPED, + TARGET_UNAVAILABLE, + RESOLVED, +}; + +enum class LlvmBuiltinAdapter +{ + OUTPUT_POINTER_VALUE_STATUS, + OUTPUT_POINTER_STATUS_VALUE, + FORWARD_ARGUMENTS, +}; + +struct LlvmBuiltinMapping +{ + std::string gcc_name; + LlvmBuiltinAdapter adapter; +}; + class BuiltinsContext { public: @@ -86,6 +106,12 @@ class BuiltinsContext bool lookup_simple_builtin (const std::string &name, tree *builtin); + LlvmBuiltinMappingResult + map_llvm_to_gcc_builtin (const std::string &name, tree *resolved, + LlvmBuiltinAdapter *adapter); + + void register_builtin (tree decl); + private: enum Type { @@ -166,6 +192,15 @@ class BuiltinsContext ATTR_LAST, }; + enum class SetupState + { + UNINITIALIZED, + INITIALIZING, + READY, + }; + + SetupState setup_state; + /** * All builtin types, as defined in `builtin-types.def` * @@ -190,6 +225,7 @@ class BuiltinsContext void define_builtins (); void register_rust_mappings (); + void register_llvm_to_gcc_builtin (); BuiltinsContext (); @@ -204,6 +240,7 @@ class BuiltinsContext // A mapping of the GCC built-ins exposed to GCC Rust. std::map builtin_functions; std::map rust_intrinsic_to_gcc_builtin; + std::map llvm_to_gcc_builtin; }; } // namespace Compile diff --git a/gcc/rust/backend/rust-compile-extern.h b/gcc/rust/backend/rust-compile-extern.h index 4eabf0ddb85..b2237b77c24 100644 --- a/gcc/rust/backend/rust-compile-extern.h +++ b/gcc/rust/backend/rust-compile-extern.h @@ -26,6 +26,9 @@ #include "rust-hir-full-decls.h" #include "rust-attributes.h" #include "rust-attribute-values.h" +#include "rust-builtins.h" +#include "rust-compile-fnparam.h" +#include "fold-const.h" namespace Rust { namespace Compile { @@ -127,6 +130,67 @@ class CompileExternItem : public HIRCompileBase, std::string ir_symbol_name = function.get_item_name ().as_string (); GGC::Ident asm_name = get_link_name (function); + if (fntype->get_abi () == ABI::UNADJUSTED) + { + tree resolved; + LlvmBuiltinAdapter adapter; + auto mapping_res = BuiltinsContext::get ().map_llvm_to_gcc_builtin ( + asm_name.as_string (), &resolved, &adapter); + + switch (mapping_res) + { + case LlvmBuiltinMappingResult::NOT_MAPPED: + rust_error_at (function.get_locus (), + "LLVM intrinsic %qs is not supported at the moment", + asm_name.c_str ()); + reference = error_mark_node; + return; + case LlvmBuiltinMappingResult::TARGET_UNAVAILABLE: + rust_error_at ( + function.get_locus (), + "LLVM intrinsic %qs is not available for this target", + asm_name.c_str ()); + reference = error_mark_node; + return; + case LlvmBuiltinMappingResult::RESOLVED: + break; + } + + tree adapter_tree = error_mark_node; + + switch (adapter) + { + case LlvmBuiltinAdapter::OUTPUT_POINTER_VALUE_STATUS: + adapter_tree = compile_x86_output_pointer_adapter ( + ctx, fntype, resolved, OutputTupleOrder::VALUE_STATUS, + function.get_locus ()); + break; + case LlvmBuiltinAdapter::OUTPUT_POINTER_STATUS_VALUE: + adapter_tree = compile_x86_output_pointer_adapter ( + ctx, fntype, resolved, OutputTupleOrder::STATUS_VALUE, + function.get_locus ()); + break; + case LlvmBuiltinAdapter::FORWARD_ARGUMENTS: + adapter_tree + = compile_x86_forwarding_adapter (ctx, fntype, resolved, + function.get_locus ()); + break; + } + + if (adapter_tree == error_mark_node) + { + rust_error_at (function.get_locus (), + "Invalid signature for LLVM intrinsic %qs", + asm_name.c_str ()); + reference = error_mark_node; + return; + } + + ctx->insert_function_decl (fntype, adapter_tree); + reference = address_expression (adapter_tree, ref_locus); + return; + } + const unsigned int flags = Backend::function_is_declaration; tree fndecl = Backend::function (compiled_fn_type, ir_symbol_name, asm_name, flags, function.get_locus ()); @@ -144,6 +208,12 @@ class CompileExternItem : public HIRCompileBase, } private: + enum class OutputTupleOrder + { + VALUE_STATUS, + STATUS_VALUE, + }; + CompileExternItem (Context *ctx, TyTy::BaseType *concrete, location_t ref_locus) : HIRCompileBase (ctx), concrete (concrete), reference (error_mark_node), @@ -180,6 +250,227 @@ class CompileExternItem : public HIRCompileBase, return obj.get_item_name (); } + /** + * Compiles a wrapper that wraps around GCC built-ins for compatibility + * with LLVM built-ins function signatures returning a tuple with value + + * status. + * + * @param ctx + * @param fntype the LLVM built-in function type + * @param gcc_builtin the GCC built-in function + * @param order whether the LLVM built-in returns (value, status) or (status, + * value) + * @param locus + * @return tree the resultant wrapper function + */ + static tree compile_x86_output_pointer_adapter (Context *ctx, + TyTy::FnType *fntype, + tree gcc_builtin, + OutputTupleOrder order, + location_t locus) + { + // expect to be a 2-field tuple return type + TyTy::TupleType *tuple + = fntype->get_return_type ()->try_as (); + + if (tuple == nullptr || tuple->num_fields () != 2) + // TODO probably add error here + return error_mark_node; + + tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype); + + const auto &path = fntype->get_ident ().path; + std::string ir_name = path.get () + fntype->subst_as_string (); + std::string asm_name = ctx->mangle_item (fntype, path); + + // start building the wrapper function + tree fndecl + = Backend::function (compiled_fn_type, ir_name, asm_name, 0, locus); + + TREE_PUBLIC (fndecl) = 0; + DECL_ARTIFICIAL (fndecl) = 1; + DECL_EXTERNAL (fndecl) = 0; + DECL_DECLARED_INLINE_P (fndecl) = 1; + + // compile params for the rust wrapper + std::vector param_vars; + param_vars.reserve (fntype->get_params ().size ()); + for (auto ¶m : fntype->get_params ()) + { + auto &pattern = param.get_pattern (); + tree type = TyTyResolveCompile::compile (ctx, param.get_type ()); + Bvariable *variable + = CompileFnParam::compile (ctx, fndecl, pattern, type, + pattern.get_locus ()); + param_vars.emplace_back (variable); + } + + if (!Backend::function_set_parameters (fndecl, param_vars)) + return error_mark_node; + + // forward the rust params, convert each into the corresponding gcc + // built-in param type + std::vector call_arguments; + // +1 here because gcc built-in expects output pointer as param + call_arguments.reserve (param_vars.size () + 1); + tree gcc_argument_types = TYPE_ARG_TYPES (TREE_TYPE (gcc_builtin)); + for (Bvariable *param : param_vars) + { + tree argument = param->get_tree (locus); + tree expected_type = TREE_VALUE (gcc_argument_types); + argument = Backend::convert_expression (expected_type, argument, locus); + call_arguments.emplace_back (argument); + gcc_argument_types = TREE_CHAIN (gcc_argument_types); + } + + // the order of llvm built-ins' tuple return types' fields is not + // consistent, some are (value, status) and others are (status, value), so + // we need to reorder it... + tree tuple_type = TREE_TYPE (DECL_RESULT (fndecl)); + tree first_field = TYPE_FIELDS (tuple_type); + tree second_field = DECL_CHAIN (first_field); + + tree value_field + = order == OutputTupleOrder::VALUE_STATUS ? first_field : second_field; + tree status_field + = order == OutputTupleOrder::VALUE_STATUS ? second_field : first_field; + + tree value_type = TREE_TYPE (value_field); + tree status_type = TREE_TYPE (status_field); + + tree temporary_stmt = NULL_TREE; + Bvariable *value + = Backend::temporary_variable (fndecl, NULL_TREE, value_type, NULL_TREE, + true, locus, &temporary_stmt); + Bvariable *status + = Backend::temporary_variable (fndecl, NULL_TREE, status_type, NULL_TREE, + false, locus, &temporary_stmt); + + // compile the final gcc built-in param which is the output value pointer, + // which llvm returns as part of the tuple return type + tree gcc_pointer_type = TREE_VALUE (gcc_argument_types); + tree block + = Backend::block (fndecl, NULL_TREE, {value, status}, locus, locus); + ctx->push_block (block); + + tree value_decl = value->get_tree (locus); + tree status_decl = status->get_tree (locus); + + tree value_address = build_fold_addr_expr_loc (locus, value_decl); + value_address + = Backend::convert_expression (gcc_pointer_type, value_address, locus); + call_arguments.emplace_back (value_address); + + // call the gcc built-in with the params that were built and assign the + // returned val to the status temp var + tree builtin_call + = build_call_expr_loc_array (locus, gcc_builtin, + static_cast (call_arguments.size ()), + call_arguments.data ()); + builtin_call + = Backend::convert_expression (status_type, builtin_call, locus); + ctx->add_statement ( + Backend::assignment_statement (status_decl, builtin_call, locus)); + + // finally compile and add the return statement + std::vector tuple_values; + if (order == OutputTupleOrder::VALUE_STATUS) + tuple_values = {value_decl, status_decl}; + else + tuple_values = {status_decl, value_decl}; + + tree tuple_result + = Backend::constructor_expression (tuple_type, false, tuple_values, -1, + locus); + ctx->add_statement ( + Backend::return_statement (fndecl, tuple_result, locus)); + + tree body = ctx->pop_block (); + DECL_SAVED_TREE (fndecl) = body; + + ctx->push_function (fndecl); + return fndecl; + } + + /** + * Compiles a 1-to-1 wrapper for LLVM built-ins that wraps around GCC + * built-ins. + * + * @param ctx + * @param fntype the LLVM built-in function type + * @param gcc_builtin the GCC built-in function + * @param locus + * @return tree the resultant wrapper function + */ + static tree compile_x86_forwarding_adapter (Context *ctx, + TyTy::FnType *fntype, + tree gcc_builtin, + location_t locus) + { + tree compiled_fn_type = TyTyResolveCompile::compile (ctx, fntype); + + const auto &path = fntype->get_ident ().path; + std::string ir_name = path.get () + fntype->subst_as_string (); + std::string asm_name = ctx->mangle_item (fntype, path); + + // start building the wrapper function + tree fndecl + = Backend::function (compiled_fn_type, ir_name, asm_name, 0, locus); + + TREE_PUBLIC (fndecl) = 0; + DECL_ARTIFICIAL (fndecl) = 1; + DECL_EXTERNAL (fndecl) = 0; + DECL_DECLARED_INLINE_P (fndecl) = 1; + + // compile params for the rust wrapper + std::vector param_vars; + param_vars.reserve (fntype->get_params ().size ()); + for (auto ¶m : fntype->get_params ()) + { + auto &pattern = param.get_pattern (); + tree type = TyTyResolveCompile::compile (ctx, param.get_type ()); + Bvariable *variable + = CompileFnParam::compile (ctx, fndecl, pattern, type, + pattern.get_locus ()); + param_vars.emplace_back (variable); + } + + if (!Backend::function_set_parameters (fndecl, param_vars)) + return error_mark_node; + + // forward the rust params, convert each into the corresponding gcc + // built-in param type + std::vector call_arguments; + call_arguments.reserve (param_vars.size ()); + tree gcc_argument_types = TYPE_ARG_TYPES (TREE_TYPE (gcc_builtin)); + for (Bvariable *param : param_vars) + { + tree argument = param->get_tree (locus); + tree expected_type = TREE_VALUE (gcc_argument_types); + argument = Backend::convert_expression (expected_type, argument, locus); + call_arguments.emplace_back (argument); + gcc_argument_types = TREE_CHAIN (gcc_argument_types); + } + + tree builtin_call + = build_call_expr_loc_array (locus, gcc_builtin, + static_cast (call_arguments.size ()), + call_arguments.data ()); + tree wrapper_ret_type = TREE_TYPE (DECL_RESULT (fndecl)); + + builtin_call + = Backend::convert_expression (wrapper_ret_type, builtin_call, locus); + tree block = Backend::block (fndecl, NULL_TREE, {}, locus, locus); + ctx->push_block (block); + ctx->add_statement ( + Backend::return_statement (fndecl, builtin_call, locus)); + tree body = ctx->pop_block (); + DECL_SAVED_TREE (fndecl) = body; + + ctx->push_function (fndecl); + return fndecl; + } + TyTy::BaseType *concrete; tree reference; location_t ref_locus; diff --git a/gcc/rust/rust-gcc.cc b/gcc/rust/rust-gcc.cc index ada66a2c7c6..c1f6002e816 100644 --- a/gcc/rust/rust-gcc.cc +++ b/gcc/rust/rust-gcc.cc @@ -333,6 +333,9 @@ init () // this->define_builtin (BUILT_IN_ATOMIC_FETCH_OR_1, "__atomic_fetch_or_1", // NULL, // t, 0); + + // eagerly initialize BuiltinsContext + (void) Rust::Compile::BuiltinsContext::get (); } void diff --git a/gcc/rust/rust-lang.cc b/gcc/rust/rust-lang.cc index dc39bc053d0..52f6e5a5b12 100644 --- a/gcc/rust/rust-lang.cc +++ b/gcc/rust/rust-lang.cc @@ -38,6 +38,7 @@ #include "optional.h" #include "rust-unicode.h" #include "rust-punycode.h" +#include "rust-builtins.h" #include // note: header files must be in this order or else forward declarations don't @@ -226,10 +227,11 @@ grs_langhook_type_for_mode (machine_mode mode, int unsignedp) return NULL; } -// Record a builtin function. We just ignore builtin functions. +// Record a builtin function. static tree -grs_langhook_builtin_function (tree decl ATTRIBUTE_UNUSED) +grs_langhook_builtin_function (tree decl) { + Rust::Compile::BuiltinsContext::get ().register_builtin (decl); return decl; } diff --git a/gcc/testsuite/rust/compile/llvm_builtins.rs b/gcc/testsuite/rust/compile/llvm_builtins.rs new file mode 100644 index 00000000000..066c9d1a718 --- /dev/null +++ b/gcc/testsuite/rust/compile/llvm_builtins.rs @@ -0,0 +1,62 @@ +// { dg-do compile { target { { i?86-*-* x86_64-*-* } && ia32 } } } +// { dg-options "-mrdrnd -mrdseed -fdump-tree-gimple" } +#![feature(no_core, abi_unadjusted)] +#![no_core] + +#[allow(improper_ctypes)] +extern "unadjusted" { + #[link_name = "llvm.x86.rdrand.16"] + fn x86_rdrand16_step() -> (u16, i32); + #[link_name = "llvm.x86.rdrand.32"] + fn x86_rdrand32_step() -> (u32, i32); + #[link_name = "llvm.x86.rdseed.16"] + fn x86_rdseed16_step() -> (u16, i32); + #[link_name = "llvm.x86.rdseed.32"] + fn x86_rdseed32_step() -> (u32, i32); + + #[link_name = "llvm.x86.addcarry.32"] + fn llvm_addcarry_u32(a: u8, b: u32, c: u32) -> (u8, u32); + #[link_name = "llvm.x86.addcarryx.u32"] + fn llvm_addcarryx_u32(a: u8, b: u32, c: u32, d: *mut u8) -> u8; + #[link_name = "llvm.x86.subborrow.32"] + fn llvm_subborrow_u32(a: u8, b: u32, c: u32) -> (u8, u32); + + // TODO implement the SIMD types (i16x8, f32x4, f32x8) before + // enabling these tests + // #[link_name = "llvm.x86.vcvtph2ps.128"] + // fn llvm_vcvtph2ps_128(a: i16x8) -> f32x4; + // #[link_name = "llvm.x86.vcvtph2ps.256"] + // fn llvm_vcvtph2ps_256(a: i16x8) -> f32x8; + // #[link_name = "llvm.x86.vcvtps2ph.128"] + // fn llvm_vcvtps2ph_128(a: f32x4, rounding: i32) -> i16x8; + // #[link_name = "llvm.x86.vcvtps2ph.256"] + // fn llvm_vcvtps2ph_256(a: f32x8, rounding: i32) -> i16x8; +} + +fn main() { + let (_v1, _s1) = unsafe { + x86_rdrand16_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdrand16_step} 1 gimple } } + }; + let (_v2, _s2) = unsafe { + x86_rdrand32_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdrand32_step} 1 gimple } } + }; + let (_v3, _s3) = unsafe { + x86_rdseed16_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdseed_hi_step} 1 gimple } } + }; + let (_v4, _s4) = unsafe { + x86_rdseed32_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdseed_si_step} 1 gimple } } + }; + + // { dg-final { scan-tree-dump-times {__builtin_ia32_addcarryx_u32} 2 gimple } } + let (_v5, _s5) = unsafe { + llvm_addcarry_u32(0, 1, 2) + }; + let _s6 = unsafe { + let _temp_ptr: *mut u8; + llvm_addcarryx_u32(0, 1, 2, _temp_ptr) + }; + + let (_v7, _s7) = unsafe { + llvm_subborrow_u32(0, 1, 2) // { dg-final { scan-tree-dump-times {__builtin_ia32_sbb_u32} 1 gimple } } + }; +} diff --git a/gcc/testsuite/rust/compile/llvm_builtins_64_bit.rs b/gcc/testsuite/rust/compile/llvm_builtins_64_bit.rs new file mode 100644 index 00000000000..c3ba93b069c --- /dev/null +++ b/gcc/testsuite/rust/compile/llvm_builtins_64_bit.rs @@ -0,0 +1,42 @@ +// { dg-do compile { target { { i?86-*-* x86_64-*-* } && { ! ia32 } } } } +// { dg-options "-mrdrnd -m64 -mrdseed -fdump-tree-gimple" } +#![feature(no_core, abi_unadjusted)] +#![no_core] + +#[allow(improper_ctypes)] +extern "unadjusted" { + #[link_name = "llvm.x86.rdrand.64"] + fn x86_rdrand64_step() -> (u64, i32); + #[link_name = "llvm.x86.rdseed.64"] + fn x86_rdseed64_step() -> (u64, i32); + + #[link_name = "llvm.x86.addcarry.64"] + fn llvm_addcarry_u64(a: u8, b: u64, c: u64) -> (u8, u64); + #[link_name = "llvm.x86.addcarryx.u64"] + fn llvm_addcarryx_u64(a: u8, b: u64, c: u64, d: *mut u8) -> u8; + #[link_name = "llvm.x86.subborrow.64"] + fn llvm_subborrow_u64(a: u8, b: u64, c: u64) -> (u8, u64); +} + +fn main() { + let (_v1, _s1) = unsafe { + x86_rdrand64_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdrand64_step} 1 gimple } } + }; + let (_v2, _s2) = unsafe { + x86_rdseed64_step() // { dg-final { scan-tree-dump-times {__builtin_ia32_rdseed_di_step} 1 gimple } } + }; + + // { dg-final { scan-tree-dump-times {__builtin_ia32_addcarryx_u64} 2 gimple } } + let (_s3, _v3) = unsafe { + llvm_addcarry_u64(0, 1, 2) + }; + let _s4 = unsafe { + let _temp_ptr: *mut u8; + llvm_addcarryx_u64(0, 1, 2, _temp_ptr) + }; + + // { dg-final { scan-tree-dump-times {__builtin_ia32_sbb_u64} 1 gimple } } + let (_s5, _v5) = unsafe { + llvm_subborrow_u64(0, 1, 2) + }; +}