From: Enes Cevik <[email protected]>

This patch implements the bswap intrinsic for integer types. It maps to the 
respective GCC built-ins for 16, 32, 64 and 128-bit integers.

gcc/rust/ChangeLog:

        * backend/rust-compile-intrinsic.cc: Add bswap_handler.
        * backend/rust-intrinsic-handlers.cc (bswap_handler): New function.
        * backend/rust-intrinsic-handlers.h (bswap_handler): New declaration.

gcc/testsuite/ChangeLog:

        * rust/compile/bswap.rs: New test.
        * rust/execute/bswap.rs: New test.

Signed-off-by: Enes Cevik <[email protected]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: 
https://github.com/Rust-GCC/gccrs/commit/7aa3e7792737ab5ce818fdd240a37c9a80cc919d

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4452

 gcc/rust/backend/rust-compile-intrinsic.cc  |   3 +-
 gcc/rust/backend/rust-intrinsic-handlers.cc | 160 ++++++++++++
 gcc/rust/backend/rust-intrinsic-handlers.h  |   1 +
 gcc/testsuite/rust/compile/bswap.rs         |  17 ++
 gcc/testsuite/rust/execute/bswap.rs         | 263 ++++++++++++++++++++
 5 files changed, 443 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/rust/compile/bswap.rs
 create mode 100644 gcc/testsuite/rust/execute/bswap.rs

diff --git a/gcc/rust/backend/rust-compile-intrinsic.cc 
b/gcc/rust/backend/rust-compile-intrinsic.cc
index f46341966..a4924542d 100644
--- a/gcc/rust/backend/rust-compile-intrinsic.cc
+++ b/gcc/rust/backend/rust-compile-intrinsic.cc
@@ -67,7 +67,8 @@ static const std::map<std::string, handlers::HandlerBuilder> 
generic_intrinsics
      {IValue::TRY, handlers::try_handler (false)},
      {IValue::CATCH_UNWIND, handlers::try_handler (true)},
      {IValue::DISCRIMINANT_VALUE, handlers::discriminant_value},
-     {IValue::VARIANT_COUNT, handlers::variant_count}};
+     {IValue::VARIANT_COUNT, handlers::variant_count},
+     {IValue::BSWAP, handlers::bswap_handler}};
 
 Intrinsics::Intrinsics (Context *ctx) : ctx (ctx) {}
 
diff --git a/gcc/rust/backend/rust-intrinsic-handlers.cc 
b/gcc/rust/backend/rust-intrinsic-handlers.cc
index 076ab06e0..5c7854ce8 100644
--- a/gcc/rust/backend/rust-intrinsic-handlers.cc
+++ b/gcc/rust/backend/rust-intrinsic-handlers.cc
@@ -1370,6 +1370,166 @@ offset (Context *ctx, TyTy::FnType *fntype)
   return fndecl;
 }
 
+/**
+ * pub const fn bswap<T: Copy>(x: T) -> T;
+ */
+tree
+bswap_handler (Context *ctx, TyTy::FnType *fntype)
+{
+  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);
+
+  auto locus = fntype->get_locus ();
+
+  std::vector<Bvariable *> param_vars;
+  compile_fn_params (ctx, fntype, fndecl, &param_vars);
+
+  auto &x_param = param_vars.at (0);
+  rust_assert (param_vars.size () == 1);
+  if (!Backend::function_set_parameters (fndecl, param_vars))
+    return error_mark_node;
+
+  auto *monomorphized_type
+    = fntype->get_substs ().at (0).get_param_ty ()->resolve ();
+
+  check_for_basic_integer_type ("bswap", fntype->get_locus (),
+                               monomorphized_type);
+
+  tree template_parameter_type
+    = TyTyResolveCompile::compile (ctx, monomorphized_type);
+
+  tree size_expr = TYPE_SIZE_UNIT (template_parameter_type);
+  unsigned HOST_WIDE_INT size = TREE_INT_CST_LOW (size_expr);
+
+  enter_intrinsic_block (ctx, fndecl);
+
+  // BUILTIN bswap FN BODY BEGIN
+
+  auto expr_x = Backend::var_expression (x_param, locus);
+  tree result = NULL_TREE;
+
+  if (size == 1)
+    {
+      result = expr_x;
+    }
+  else
+    {
+      tree target_type = NULL_TREE;
+      const char *builtin_name = nullptr;
+      switch (size)
+       {
+       case 2:
+         builtin_name = "__builtin_bswap16";
+         target_type = uint16_type_node;
+         break;
+       case 4:
+         builtin_name = "__builtin_bswap32";
+         target_type = uint32_type_node;
+         break;
+       case 8:
+         builtin_name = "__builtin_bswap64";
+         target_type = uint64_type_node;
+         break;
+       case 16:
+         builtin_name = "__builtin_bswap128";
+         target_type = uint128_type_node;
+         break;
+       default:
+         return error_mark_node;
+       }
+
+      tree bswap_raw = nullptr;
+      auto ok = BuiltinsContext::get ().lookup_simple_builtin (builtin_name,
+                                                              &bswap_raw);
+
+      if (ok)
+       {
+         tree bswap_fn = build_fold_addr_expr_loc (locus, bswap_raw);
+
+         auto bswap_x = build1 (CONVERT_EXPR, target_type, expr_x);
+
+         auto bswap_call
+           = Backend::call_expression (bswap_fn, {bswap_x}, NULL_TREE, locus);
+
+         result = build1 (CONVERT_EXPR, template_parameter_type, bswap_call);
+       }
+      else
+       {
+         auto ok2 = BuiltinsContext::get ().lookup_simple_builtin (
+           "__builtin_bswap64", &bswap_raw);
+         rust_assert (ok2);
+
+         tree bswap_fn = build_fold_addr_expr_loc (locus, bswap_raw);
+
+         tree tmp_in_stmt = error_mark_node;
+         Bvariable *in_var
+           = Backend::temporary_variable (fndecl, NULL_TREE,
+                                          template_parameter_type, expr_x,
+                                          true, locus, &tmp_in_stmt);
+         ctx->add_statement (tmp_in_stmt);
+
+         tree addr_x
+           = build_fold_addr_expr_loc (locus, in_var->get_tree (locus));
+         tree u64_ptr_type = build_pointer_type (uint64_type_node);
+
+         tree low_ptr = fold_convert (u64_ptr_type, addr_x);
+         tree low = build_fold_indirect_ref_loc (locus, low_ptr);
+
+         tree high_ptr = fold_build2 (POINTER_PLUS_EXPR, u64_ptr_type, low_ptr,
+                                      size_int (8));
+         tree high = build_fold_indirect_ref_loc (locus, high_ptr);
+
+         auto new_high
+           = Backend::call_expression (bswap_fn, {low}, NULL_TREE, locus);
+         auto new_low
+           = Backend::call_expression (bswap_fn, {high}, NULL_TREE, locus);
+
+         tree tmp_stmt = error_mark_node;
+         Bvariable *result_var
+           = Backend::temporary_variable (fndecl, NULL_TREE,
+                                          template_parameter_type, NULL_TREE,
+                                          true, locus, &tmp_stmt);
+         ctx->add_statement (tmp_stmt);
+
+         tree addr_res
+           = build_fold_addr_expr_loc (locus, result_var->get_tree (locus));
+         tree res_ptr = fold_convert (u64_ptr_type, addr_res);
+
+         tree store_low
+           = build2 (MODIFY_EXPR, void_type_node,
+                     build_fold_indirect_ref_loc (locus, res_ptr), new_low);
+         ctx->add_statement (store_low);
+
+         tree res_high_ptr = fold_build2 (POINTER_PLUS_EXPR, u64_ptr_type,
+                                          res_ptr, size_int (8));
+         tree store_high
+           = build2 (MODIFY_EXPR, void_type_node,
+                     build_fold_indirect_ref_loc (locus, res_high_ptr),
+                     new_high);
+         ctx->add_statement (store_high);
+
+         result = result_var->get_tree (locus);
+       }
+    }
+
+  auto return_statement = Backend::return_statement (fndecl, result, locus);
+
+  TREE_READONLY (result) = 1;
+
+  ctx->add_statement (return_statement);
+
+  // BUILTIN bswap FN BODY END
+
+  finalize_intrinsic_block (ctx, fndecl);
+
+  return fndecl;
+}
+
 } // namespace handlers
 } // namespace Compile
 } // namespace Rust
diff --git a/gcc/rust/backend/rust-intrinsic-handlers.h 
b/gcc/rust/backend/rust-intrinsic-handlers.h
index 1111f34f0..06174a535 100644
--- a/gcc/rust/backend/rust-intrinsic-handlers.h
+++ b/gcc/rust/backend/rust-intrinsic-handlers.h
@@ -64,6 +64,7 @@ tree move_val_init (Context *ctx, TyTy::FnType *fntype);
 tree assume (Context *ctx, TyTy::FnType *fntype);
 tree discriminant_value (Context *ctx, TyTy::FnType *fntype);
 tree variant_count (Context *ctx, TyTy::FnType *fntype);
+tree bswap_handler (Context *ctx, TyTy::FnType *fntype);
 
 tree prefetch_data (Context *ctx, TyTy::FnType *fntype, Prefetch kind);
 
diff --git a/gcc/testsuite/rust/compile/bswap.rs 
b/gcc/testsuite/rust/compile/bswap.rs
new file mode 100644
index 000000000..a41607efc
--- /dev/null
+++ b/gcc/testsuite/rust/compile/bswap.rs
@@ -0,0 +1,17 @@
+// { dg-do compile }
+#![feature(intrinsics, lang_items, no_core)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+#[lang = "copy"]
+pub trait Copy {}
+
+extern "rust-intrinsic" {
+    pub fn bswap<T>(x: T) -> T; // { dg-error "bswap intrinsics can only be 
used with basic integer types .got 'bool'." }
+}
+
+fn main() {
+    let _ = bswap(true);
+}
diff --git a/gcc/testsuite/rust/execute/bswap.rs 
b/gcc/testsuite/rust/execute/bswap.rs
new file mode 100644
index 000000000..b64256508
--- /dev/null
+++ b/gcc/testsuite/rust/execute/bswap.rs
@@ -0,0 +1,263 @@
+// { dg-do run }
+// { dg-options "-O2" }
+
+/*
+ * Note on test design:
+ * This test uses a union-based type punning approach rather than standard
+ * integer literals (e.g., 0x..._u128) or bitwise shifts for 64-bit and 
128-bit types.
+ * We do this for a few practical reasons:
+ *  gccrs doesn't support 128-bit integer literals yet.
+ *  Doing manual bitwise shifts on 32-bit targets currently triggers ICEs in 
the frontend.
+ *  Casting via raw pointers gets optimized away by GCC under -O2 due to 
strict aliasing rules.
+ * Using a #[repr(C)] union gives us a safe workaround.
+ *  As a bonus, by asserting against the physical memory layout ([u8; N]) 
instead
+ * of the logical integer value, this test is inherently endian-agnostic.
+ */
+
+#![feature(intrinsics, lang_items, no_core)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+#[lang = "copy"]
+pub trait Copy {}
+
+extern "rust-intrinsic" {
+    pub fn bswap<T>(x: T) -> T;
+    pub fn abort() -> !;
+}
+
+#[repr(C)]
+union U64Pun {
+    bytes: [u8; 8],
+    val: u64,
+}
+#[repr(C)]
+union I64Pun {
+    bytes: [u8; 8],
+    val: i64,
+}
+#[repr(C)]
+union U128Pun {
+    bytes: [u8; 16],
+    val: u128,
+}
+#[repr(C)]
+union I128Pun {
+    bytes: [u8; 16],
+    val: i128,
+}
+
+fn test_bswap_16_and_32() {
+    if bswap(0x0102_u16) != 0x0201_u16 {
+        abort();
+    }
+    if bswap(0x0102_i16) != 0x0201_i16 {
+        abort();
+    }
+
+    if bswap(0x01020304_u32) != 0x04030201_u32 {
+        abort();
+    }
+    if bswap(0x01020304_i32) != 0x04030201_i32 {
+        abort();
+    }
+}
+
+fn test_bswap_u64() {
+    let in_pun = U64Pun {
+        bytes: [1, 2, 3, 4, 5, 6, 7, 8],
+    };
+    unsafe {
+        let out_pun = U64Pun {
+            val: bswap(in_pun.val),
+        };
+        if out_pun.bytes[0] != 8 {
+            abort();
+        }
+        if out_pun.bytes[1] != 7 {
+            abort();
+        }
+        if out_pun.bytes[2] != 6 {
+            abort();
+        }
+        if out_pun.bytes[3] != 5 {
+            abort();
+        }
+        if out_pun.bytes[4] != 4 {
+            abort();
+        }
+        if out_pun.bytes[5] != 3 {
+            abort();
+        }
+        if out_pun.bytes[6] != 2 {
+            abort();
+        }
+        if out_pun.bytes[7] != 1 {
+            abort();
+        }
+    }
+}
+
+fn test_bswap_i64() {
+    let in_pun = I64Pun {
+        bytes: [1, 2, 3, 4, 5, 6, 7, 8],
+    };
+    unsafe {
+        let out_pun = I64Pun {
+            val: bswap(in_pun.val),
+        };
+        if out_pun.bytes[0] != 8 {
+            abort();
+        }
+        if out_pun.bytes[1] != 7 {
+            abort();
+        }
+        if out_pun.bytes[2] != 6 {
+            abort();
+        }
+        if out_pun.bytes[3] != 5 {
+            abort();
+        }
+        if out_pun.bytes[4] != 4 {
+            abort();
+        }
+        if out_pun.bytes[5] != 3 {
+            abort();
+        }
+        if out_pun.bytes[6] != 2 {
+            abort();
+        }
+        if out_pun.bytes[7] != 1 {
+            abort();
+        }
+    }
+}
+
+fn test_bswap_u128() {
+    let in_pun = U128Pun {
+        bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
+    };
+    unsafe {
+        let out_pun = U128Pun {
+            val: bswap(in_pun.val),
+        };
+        if out_pun.bytes[0] != 16 {
+            abort();
+        }
+        if out_pun.bytes[1] != 15 {
+            abort();
+        }
+        if out_pun.bytes[2] != 14 {
+            abort();
+        }
+        if out_pun.bytes[3] != 13 {
+            abort();
+        }
+        if out_pun.bytes[4] != 12 {
+            abort();
+        }
+        if out_pun.bytes[5] != 11 {
+            abort();
+        }
+        if out_pun.bytes[6] != 10 {
+            abort();
+        }
+        if out_pun.bytes[7] != 9 {
+            abort();
+        }
+        if out_pun.bytes[8] != 8 {
+            abort();
+        }
+        if out_pun.bytes[9] != 7 {
+            abort();
+        }
+        if out_pun.bytes[10] != 6 {
+            abort();
+        }
+        if out_pun.bytes[11] != 5 {
+            abort();
+        }
+        if out_pun.bytes[12] != 4 {
+            abort();
+        }
+        if out_pun.bytes[13] != 3 {
+            abort();
+        }
+        if out_pun.bytes[14] != 2 {
+            abort();
+        }
+        if out_pun.bytes[15] != 1 {
+            abort();
+        }
+    }
+}
+
+fn test_bswap_i128() {
+    let in_pun = I128Pun {
+        bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
+    };
+    unsafe {
+        let out_pun = I128Pun {
+            val: bswap(in_pun.val),
+        };
+        if out_pun.bytes[0] != 16 {
+            abort();
+        }
+        if out_pun.bytes[1] != 15 {
+            abort();
+        }
+        if out_pun.bytes[2] != 14 {
+            abort();
+        }
+        if out_pun.bytes[3] != 13 {
+            abort();
+        }
+        if out_pun.bytes[4] != 12 {
+            abort();
+        }
+        if out_pun.bytes[5] != 11 {
+            abort();
+        }
+        if out_pun.bytes[6] != 10 {
+            abort();
+        }
+        if out_pun.bytes[7] != 9 {
+            abort();
+        }
+        if out_pun.bytes[8] != 8 {
+            abort();
+        }
+        if out_pun.bytes[9] != 7 {
+            abort();
+        }
+        if out_pun.bytes[10] != 6 {
+            abort();
+        }
+        if out_pun.bytes[11] != 5 {
+            abort();
+        }
+        if out_pun.bytes[12] != 4 {
+            abort();
+        }
+        if out_pun.bytes[13] != 3 {
+            abort();
+        }
+        if out_pun.bytes[14] != 2 {
+            abort();
+        }
+        if out_pun.bytes[15] != 1 {
+            abort();
+        }
+    }
+}
+
+fn main() -> i32 {
+    test_bswap_16_and_32();
+    test_bswap_u64();
+    test_bswap_i64();
+    test_bswap_u128();
+    test_bswap_i128();
+    0
+}

base-commit: 9b1e540f3e5d1a3486e58c9df3c18907294c33d7
-- 
2.53.0

Reply via email to