https://gcc.gnu.org/g:1ed514a029dc071a60713e480cc0d980c0293faf
commit r17-4309-g1ed514a029dc071a60713e480cc0d980c0293faf Author: Yap Zhi Heng <[email protected]> Date: Sun Aug 23 15:46:45 2026 +0800 gccrs: Initial implementation of LLVM builtin to GCC adapter Only works for rdseed, rdrand, addcarry and subborrow for now. Compiled 006t.original output from compiling llvm_builtins.rs: ``` struct (u16, i32) x86_rdrand16_step () { u16 RUSTTMP.0; i32 RUSTTMP.1; RUSTTMP.1 = __builtin_ia32_rdrand16_step (&RUSTTMP.0); return {.__0=RUSTTMP.0, .__1=RUSTTMP.1}; } struct (u32, i32) x86_rdrand32_step () { u32 RUSTTMP.2; i32 RUSTTMP.3; RUSTTMP.3 = __builtin_ia32_rdrand32_step (&RUSTTMP.2); return {.__0=RUSTTMP.2, .__1=RUSTTMP.3}; } struct (u64, i32) x86_rdrand64_step () { u64 RUSTTMP.4; i32 RUSTTMP.5; RUSTTMP.5 = __builtin_ia32_rdrand64_step ((unsigned long *) &RUSTTMP.4); return {.__0=RUSTTMP.4, .__1=RUSTTMP.5}; } struct (u16, i32) x86_rdseed16_step () { u16 RUSTTMP.6; i32 RUSTTMP.7; RUSTTMP.7 = __builtin_ia32_rdseed_hi_step (&RUSTTMP.6); return {.__0=RUSTTMP.6, .__1=RUSTTMP.7}; } struct (u32, i32) x86_rdseed32_step () { u32 RUSTTMP.8; i32 RUSTTMP.9; RUSTTMP.9 = __builtin_ia32_rdseed_si_step (&RUSTTMP.8); return {.__0=RUSTTMP.8, .__1=RUSTTMP.9}; } struct (u64, i32) x86_rdseed64_step () { u64 RUSTTMP.10; i32 RUSTTMP.11; RUSTTMP.11 = __builtin_ia32_rdseed_di_step ((unsigned long *) &RUSTTMP.10); return {.__0=RUSTTMP.10, .__1=RUSTTMP.11}; } struct (u8, u32) llvm_addcarry_u32 (const u8 a, const u32 b, const u32 c) { u32 RUSTTMP.12; u8 RUSTTMP.13; RUSTTMP.13 = (u8) __builtin_ia32_addcarryx_u32 ((unsigned char) a, (u32) b, (u32) c, &RUSTTMP.12); return {.__0=RUSTTMP.13, .__1=RUSTTMP.12}; } struct (u8, u32) llvm_subborrow_u32 (const u8 a, const u32 b, const u32 c) { u32 RUSTTMP.14; u8 RUSTTMP.15; RUSTTMP.15 = (u8) __builtin_ia32_sbb_u32 ((unsigned char) a, (u32) b, (u32) c, &RUSTTMP.14); return {.__0=RUSTTMP.15, .__1=RUSTTMP.14}; } struct (u8, u64) llvm_addcarry_u64 (const u8 a, const u64 b, const u64 c) { u64 RUSTTMP.16; u8 RUSTTMP.17; RUSTTMP.17 = (u8) __builtin_ia32_addcarryx_u64 ((unsigned char) a, (unsigned long) b, (unsigned long) c, (unsigned long *) &RUSTTMP.16); return {.__0=RUSTTMP.17, .__1=RUSTTMP.16}; } struct (u8, u64) llvm_subborrow_u64 (const u8 a, const u64 b, const u64 c) { u64 RUSTTMP.18; u8 RUSTTMP.19; RUSTTMP.19 = (u8) __builtin_ia32_sbb_u64 ((unsigned char) a, (unsigned long) b, (unsigned long) c, (unsigned long *) &RUSTTMP.18); return {.__0=RUSTTMP.19, .__1=RUSTTMP.18}; } ``` gcc/rust/ChangeLog: * backend/rust-builtins.h (LlvmBuiltinMappingResult): New enum. (LlvmBuiltinAdapter): New enum. (LlvmBuiltinMapping): New enum. (BuiltinsContext::map_llvm_to_gcc_builtin): New function for LLVM built-in mapping. (BuiltinsContext::register_builtin): New function for registering built-ins. (BuiltinsContext::register_llvm_to_gcc_builtin): New function. (BuiltinsContext::llvm_to_gcc_builtin): New map to translate function names. * backend/rust-builtins.cc (BuiltinsContext::map_llvm_to_gcc_builtin): Implementation. (BuiltinsContext::register_llvm_to_gcc_builtin): Implementation. (BuiltinsContext::register_builtin): Implementation. (BuiltinsContext::setup): Call register_llvm_to_gcc_builtin during setup. * rust-lang.cc (grs_langhook_builtin_function): Register GCC built-in functions. * backend/rust-compile-extern.h (CompileExternItem::OutputTupleOrder): New enum to denote the tuple field order of the LLVM built-in's return type. (CompileExternItem::compile_x86_output_pointer_adapter): New function to adapt LLVM's rdseed, rdrand, addcarry, subborrow function signatures to GCC's equivalents. (CompileExternItem::visit (HIR::ExternalFunctionItem)): Handle UNADJUSTED ABI case by properly resolving LLVM intrinsic function names. Signed-off-by: Yap Zhi Heng <[email protected]> Diff: --- gcc/rust/backend/rust-builtins.cc | 71 ++++++++++ gcc/rust/backend/rust-builtins.h | 28 ++++ gcc/rust/backend/rust-compile-extern.h | 213 ++++++++++++++++++++++++++++ gcc/rust/rust-lang.cc | 6 +- gcc/testsuite/rust/compile/llvm_builtins.rs | 50 +++++++ 5 files changed, 366 insertions(+), 2 deletions(-) diff --git a/gcc/rust/backend/rust-builtins.cc b/gcc/rust/backend/rust-builtins.cc index 9914b6badbb5..569c42576e03 100644 --- a/gcc/rust/backend/rust-builtins.cc +++ b/gcc/rust/backend/rust-builtins.cc @@ -48,6 +48,25 @@ BuiltinsContext::lookup_simple_builtin (const std::string &name, tree *builtin) return lookup_gcc_builtin (*to_search, builtin); } +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) {} /** @@ -359,6 +378,57 @@ 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}}}; +} + +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 () { @@ -372,6 +442,7 @@ BuiltinsContext::setup () targetm.init_builtins (); register_rust_mappings (); + register_llvm_to_gcc_builtin (); setup_state = SetupState::READY; } diff --git a/gcc/rust/backend/rust-builtins.h b/gcc/rust/backend/rust-builtins.h index 7819a7c03491..e2c7f9816f9a 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 @@ public: 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 { @@ -199,6 +225,7 @@ private: void define_builtins (); void register_rust_mappings (); + void register_llvm_to_gcc_builtin (); BuiltinsContext (); @@ -213,6 +240,7 @@ private: // A mapping of the GCC built-ins exposed to GCC Rust. std::map<std::string, tree> builtin_functions; std::map<std::string, std::string> rust_intrinsic_to_gcc_builtin; + std::map<std::string, LlvmBuiltinMapping> 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 4eabf0ddb853..f9aecef5b873 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,68 @@ public: 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: + // TODO placeholder + adapter_tree = compile_x86_output_pointer_adapter ( + ctx, fntype, resolved, OutputTupleOrder::STATUS_VALUE, + 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 +209,12 @@ public: } 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 +251,148 @@ private: 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 + */ + 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<TyTy::TupleType> (); + + 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<Bvariable *> 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<tree> 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<int> (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<tree> 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; + } + TyTy::BaseType *concrete; tree reference; location_t ref_locus; diff --git a/gcc/rust/rust-lang.cc b/gcc/rust/rust-lang.cc index dc39bc053d0e..52f6e5a5b12a 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 <mpfr.h> // 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 000000000000..6503c0be2bde --- /dev/null +++ b/gcc/testsuite/rust/compile/llvm_builtins.rs @@ -0,0 +1,50 @@ +// { dg-options "-mrdrnd" } +#![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.rdrand.64"] + fn x86_rdrand64_step() -> (u64, 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.rdseed.64"] + fn x86_rdseed64_step() -> (u64, 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); + #[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); + + // #[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 (_status2, _v1) = unsafe { + llvm_addcarry_u32(0, 1, 2) + }; + let (_v2, _status2) = unsafe { + x86_rdrand16_step() + }; +}
