From: Arthur Cohen <[email protected]>

This commit also reworks assert_zero_valid, assert_uninit_valid and 
assert_inhabited to all
live within the same handler, as this is how rustc implements them and a lot of 
the
verification needs to be repeated.

gcc/rust/ChangeLog:

        * backend/rust-compile-intrinsic.cc: Add new handler.
        * backend/rust-intrinsic-handlers.cc (build_rust_string): Move in the 
file.
        (is_uninhabited): New function.
        (type_assert): New inner handler and associated handler.
        (assert_zero_valid_handler): Remove.
        (assert_inhabited): Remove.
        * backend/rust-intrinsic-handlers.h (enum class TyAssert): New.
        (assert_inhabited): Remove.
        (type_assert): New declaration.
        * typecheck/rust-hir-type-check-intrinsic.cc: New entry for 
assert_uninit_valid.
        * util/rust-intrinsic-values.h: Likewise.

gcc/testsuite/ChangeLog:

        * rust/execute/torture/assert_uninit_valid1.rs: New test.
        * rust/execute/torture/assert_zero_valid1.rs: New test.
---
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/8204ba7c099852a9e03a5ab705f7e96b5a7cac2f

The commit has NOT been mentioned in any issue.

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

 gcc/rust/backend/rust-compile-intrinsic.cc    |   5 +-
 gcc/rust/backend/rust-intrinsic-handlers.cc   | 227 ++++++++++--------
 gcc/rust/backend/rust-intrinsic-handlers.h    |  13 +-
 .../rust-hir-type-check-intrinsic.cc          |   2 +
 gcc/rust/util/rust-intrinsic-values.h         |   1 +
 .../execute/torture/assert_uninit_valid1.rs   |  40 +++
 .../execute/torture/assert_zero_valid1.rs     |  40 +++
 7 files changed, 231 insertions(+), 97 deletions(-)
 create mode 100644 gcc/testsuite/rust/execute/torture/assert_uninit_valid1.rs
 create mode 100644 gcc/testsuite/rust/execute/torture/assert_zero_valid1.rs

diff --git a/gcc/rust/backend/rust-compile-intrinsic.cc 
b/gcc/rust/backend/rust-compile-intrinsic.cc
index 7b56056ae..131ad42dd 100644
--- a/gcc/rust/backend/rust-compile-intrinsic.cc
+++ b/gcc/rust/backend/rust-compile-intrinsic.cc
@@ -31,7 +31,6 @@ static const std::map<std::string, Intrinsic::CompileFn> 
intrinsics = {
   {IValue::OFFSET, handlers::offset},
   {IValue::ARITH_OFFSET, handlers::arith_offset_handler},
   {IValue::WRITE_BYTES, handlers::write_bytes_handler},
-  {IValue::ASSERT_ZERO_VALID, handlers::assert_zero_valid_handler},
   {IValue::SIZE_OF, handlers::sizeof_handler},
   {IValue::SIZE_OF_VAL, handlers::size_of_val_handler},
   {IValue::MIN_ALIGN_OF, handlers::min_align_of_handler},
@@ -92,7 +91,9 @@ static const std::map<std::string, Intrinsic::CompileFn> 
intrinsics = {
    handlers::fop_fast (ArithmeticOrLogicalOperator::MODULUS)},
   {IValue::FLOAT_TO_INT_UNCHECKED, handlers::float_to_int_unchecked},
   {IValue::EXACT_DIV, handlers::exact_div_handler},
-  {IValue::ASSERT_INHABITED, handlers::assert_inhabited},
+  {IValue::ASSERT_ZERO_VALID, handlers::type_assert (TyAssert::ZeroValid)},
+  {IValue::ASSERT_INHABITED, handlers::type_assert (TyAssert::Inhabited)},
+  {IValue::ASSERT_UNINIT_VALID, handlers::type_assert (TyAssert::UninitValid)},
 };
 
 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 82bb5b4d3..58f3f345e 100644
--- a/gcc/rust/backend/rust-intrinsic-handlers.cc
+++ b/gcc/rust/backend/rust-intrinsic-handlers.cc
@@ -889,6 +889,132 @@ fop_fast (IntrinsicCtx &ctx, ArithmeticOrLogicalOperator 
op)
   ctx.add_statement (return_statement);
 }
 
+static tree
+build_rust_string (IntrinsicCtx &ctx, const std::string &content)
+{
+  TyTy::BaseType *str = nullptr;
+  TyTy::BaseType *usize = nullptr;
+  rust_assert (ctx.get_tyctx ()->lookup_builtin ("str", &str));
+  rust_assert (ctx.get_tyctx ()->lookup_builtin ("usize", &usize));
+
+  tree meta_pointer = TyTyResolveCompile::compile (ctx.inner (), str);
+  tree usize_ty = TyTyResolveCompile::compile (ctx.inner (), usize);
+
+  auto base = Backend::string_constant_expression (content);
+  tree data = build_fold_addr_expr (base);
+
+  tree size = build_int_cstu (usize_ty, content.size ());
+
+  return Backend::constructor_expression (meta_pointer, false, {data, size}, 
-1,
+                                         ctx.loc);
+}
+
+/**
+ * Check if a type is uninhabited - either the Never type or a variant-less 
enum
+ */
+static bool
+is_uninhabited (TyTy::BaseType *t)
+{
+  rust_assert (t);
+
+  if (auto adt = t->try_as<TyTy::ADTType> ())
+    if (adt->is_enum () && adt->number_of_variants () == 0)
+      return true;
+
+  if (t->is<TyTy::NeverType> ())
+    return true;
+
+  return false;
+}
+
+static void
+type_assert (IntrinsicCtx &ctx, TyAssert assert)
+{
+  /**
+   * These functions check whether a type respects a predicate *statically* - 
if
+   * the given generic is invalid, then panic at runtime, otherwise do nothing.
+   */
+
+  /**
+   * TODO: Since gccrs currently lacks comprehensive layout engine support for
+   * validity ranges (such as `rustc_layout_scalar_valid_range_start`), we
+   * cannot accurately determine if a type is safely zeroable or can be left
+   * uninitialized. Therefore, this is implemented as a temporary no-op stub
+   * except for uninhabited types. We emit a warning regarding this limitation.
+   */
+
+  auto t_param = ctx.fntype.get_substs ().at (0);
+  auto t_ty = t_param.get_param_ty ();
+  auto t = t_ty->resolve ();
+  auto t_name = t->get_name ();
+  auto valid = true;
+
+  // In all cases, invalid types are... invalid. So single out never and
+  // variant-less enums and return early
+  if (is_uninhabited (t))
+    valid = false;
+
+  /**
+   * TODO: Since gccrs currently lacks comprehensive layout engine support for
+   * validity ranges (such as `rustc_layout_scalar_valid_range_start`), we
+   * cannot accurately determine if a type is safely zeroable or can be left
+   * uninitialized. Therefore, this is implemented as a temporary no-op stub
+   * except for uninhabited types
+   */
+  auto always_warn = false;
+  auto fn_name = "";
+
+  // FIXME: Once we have proper verification for the layout of the given type,
+  // we need to separate our panic strings based on the operation we're doing -
+  // asserting that a type is inhabited, zero valid, or uninit valid
+  std::string panic_str;
+
+  switch (assert)
+    {
+    case TyAssert::Inhabited:
+      panic_str = "attempted to instantiate uninhabited type `" + t_name + "`";
+      break;
+    case TyAssert::ZeroValid:
+      panic_str = "attempted to zero-initialize type `" + t_name
+                 + "`, which is invalid";
+
+      // FIXME: Remove eventually
+      always_warn = true;
+      fn_name = "assert_zero_valid<T>";
+      break;
+    case TyAssert::UninitValid:
+      panic_str = "attempted to leave type `" + t_name
+                 + "` uninitialized, which is invalid";
+
+      // FIXME: Remove eventually
+      always_warn = true;
+      fn_name = "assert_uninit_valid<T>";
+      break;
+    }
+
+  if (always_warn)
+    {
+      rust_warning_at (ctx.loc, 0,
+                      "%qs was emitted as a no-op and "
+                      "will not trigger - this may be invalid",
+                      fn_name);
+    }
+
+  if (!valid)
+    {
+      auto arg = build_rust_string (ctx, panic_str);
+
+      auto panic_call = lang_item_call (ctx, LangItem::Kind::PANIC, {arg});
+
+      ctx.add_statement (panic_call);
+
+      // buh :(
+      TREE_READONLY (ctx.fn) = 0;
+      TREE_USED (ctx.fn) = 1;
+      TREE_SIDE_EFFECTS (ctx.fn) = 1;
+    }
+}
+
 } // namespace inner
 
 Intrinsic::CompileFn
@@ -903,6 +1029,13 @@ fop_fast (ArithmeticOrLogicalOperator op)
   return [op] (IntrinsicCtx &ctx) { return inner::fop_fast (ctx, op); };
 }
 
+Intrinsic::CompileFn
+type_assert (TyAssert assert)
+{
+  return
+    [assert] (IntrinsicCtx &ctx) { return inner::type_assert (ctx, assert); };
+}
+
 void
 rotate_left (IntrinsicCtx &ctx)
 {
@@ -1722,41 +1855,6 @@ arith_offset_handler (IntrinsicCtx &ctx)
   ctx.add_statement (return_statement);
 }
 
-/**
- * pub fn assert_zero_valid<T>();
- *
- * TODO: Since gccrs currently lacks comprehensive layout engine support for
- * validity ranges (such as `rustc_layout_scalar_valid_range_start`), we cannot
- * accurately determine if a type is safely zeroable. Therefore, this is
- * implemented as a temporary no-op stub that always returns void (unit) and
- * succeeds for all types.
- */
-void
-assert_zero_valid_handler (IntrinsicCtx &ctx)
-{
-  auto &param_mapping = ctx.fntype.get_substs ().at (0);
-  const auto param_tyty = param_mapping.get_param_ty ();
-  auto resolved_tyty = param_tyty->resolve ();
-
-  // Safely resolve the template parameter type to ensure monomorphization
-  // works.
-  // Suppress unused-variable warning since layout verification is a FIXME.
-  [[gnu::unused]] tree template_parameter_type
-    = TyTyResolveCompile::compile (ctx.inner (), resolved_tyty);
-
-  // TODO: Implement layout verification via template_parameter_type once
-  // niche-filling and valid scalar ranges are supported in the layout engine.
-  // If invalid, this should emit a panic.
-  rust_warning_at (ctx.loc, 0,
-                  "%<assert_zero_valid<T>%> is currently always emitted as a "
-                  "no-op and will not trigger");
-
-  // we always return unit for now.
-  auto return_statement
-    = Backend::return_statement (ctx.fn, void_node, UNDEF_LOCATION);
-  ctx.add_statement (return_statement);
-}
-
 void
 exact_div_handler (IntrinsicCtx &ctx)
 {
@@ -1798,65 +1896,6 @@ float_to_int_unchecked (IntrinsicCtx &ctx)
   ctx.add_statement (return_statement);
 }
 
-static tree
-build_rust_string (IntrinsicCtx &ctx, const std::string &content)
-{
-  TyTy::BaseType *str = nullptr;
-  TyTy::BaseType *usize = nullptr;
-  rust_assert (ctx.get_tyctx ()->lookup_builtin ("str", &str));
-  rust_assert (ctx.get_tyctx ()->lookup_builtin ("usize", &usize));
-
-  tree meta_pointer = TyTyResolveCompile::compile (ctx.inner (), str);
-  tree usize_ty = TyTyResolveCompile::compile (ctx.inner (), usize);
-
-  auto base = Backend::string_constant_expression (content);
-  tree data = build_fold_addr_expr (base);
-
-  tree size = build_int_cstu (usize_ty, content.size ());
-
-  return Backend::constructor_expression (meta_pointer, false, {data, size}, 
-1,
-                                         ctx.loc);
-}
-
-void
-assert_inhabited (IntrinsicCtx &ctx)
-{
-  /**
-   * This function checks whether a type is uninhabited *statically* - if the
-   * given generic is never or an empty enum, then panic at runtime, otherwise
-   * do nothing
-   */
-
-  auto t_param = ctx.fntype.get_substs ().at (0);
-  auto t_ty = t_param.get_param_ty ();
-  auto t = t_ty->resolve ();
-  auto t_name = t->get_name ();
-
-  auto inhabited = true;
-
-  if (auto adt = t->try_as<TyTy::ADTType> ())
-    if (adt->is_enum () && adt->number_of_variants () == 0)
-      inhabited = false;
-
-  if (t->is<TyTy::NeverType> ())
-    inhabited = false;
-
-  if (!inhabited)
-    {
-      auto fmt = "attempted to instantiate uninhabited type `" + t_name + "`";
-      auto arg = build_rust_string (ctx, fmt);
-
-      auto panic_call = lang_item_call (ctx, LangItem::Kind::PANIC, {arg});
-
-      ctx.add_statement (panic_call);
-    }
-
-  // buh :(
-  TREE_READONLY (ctx.fn) = 0;
-  TREE_USED (ctx.fn) = 1;
-  TREE_SIDE_EFFECTS (ctx.fn) = 1;
-}
-
 } // 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 7ab3e8a70..d0aad88ee 100644
--- a/gcc/rust/backend/rust-intrinsic-handlers.h
+++ b/gcc/rust/backend/rust-intrinsic-handlers.h
@@ -121,6 +121,13 @@ enum class Prefetch
   Write
 };
 
+enum class TyAssert
+{
+  ZeroValid,
+  Inhabited,
+  UninitValid,
+};
+
 namespace handlers {
 
 namespace inner {
@@ -195,7 +202,11 @@ Intrinsic::CompileFn try_handler (bool is_new_api);
  */
 Intrinsic::CompileFn fop_fast (ArithmeticOrLogicalOperator op);
 
-void assert_inhabited (IntrinsicCtx &ctx);
+/**
+ * For assert_zero_valid, assert_inhabited and assert_maybe_uninit.
+ * This is how rustc does it as well since a lot of the code is reused.
+ */
+Intrinsic::CompileFn type_assert (TyAssert assert);
 
 } // namespace handlers
 
diff --git a/gcc/rust/typecheck/rust-hir-type-check-intrinsic.cc 
b/gcc/rust/typecheck/rust-hir-type-check-intrinsic.cc
index 258f85faa..728b5fdd0 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-intrinsic.cc
+++ b/gcc/rust/typecheck/rust-hir-type-check-intrinsic.cc
@@ -316,6 +316,8 @@ const std::unordered_map<std::string, IntrinsicRules>
     {IValue::ASSERT_ZERO_VALID, {1, {}, IRT::Unit}},
     // fn assert_inhabited<T>();
     {IValue::ASSERT_INHABITED, {1, {}, IRT::Unit}},
+    // fn assert_maybe_uninit<T>();
+    {IValue::ASSERT_UNINIT_VALID, {1, {}, IRT::Unit}},
 
     // pub fn fadd_fast<T>(a: T, b: T) -> T
     // same for fsub_fast, fmul_fast, fdiv_fast and frem_fast
diff --git a/gcc/rust/util/rust-intrinsic-values.h 
b/gcc/rust/util/rust-intrinsic-values.h
index 5f498d16b..e154f60b5 100644
--- a/gcc/rust/util/rust-intrinsic-values.h
+++ b/gcc/rust/util/rust-intrinsic-values.h
@@ -177,6 +177,7 @@ public:
 
   static constexpr auto &ASSERT_ZERO_VALID = "assert_zero_valid";
   static constexpr auto &ASSERT_INHABITED = "assert_inhabited";
+  static constexpr auto &ASSERT_UNINIT_VALID = "assert_uninit_valid";
 
   static constexpr auto &FADD_FAST = "fadd_fast";
   static constexpr auto &FSUB_FAST = "fsub_fast";
diff --git a/gcc/testsuite/rust/execute/torture/assert_uninit_valid1.rs 
b/gcc/testsuite/rust/execute/torture/assert_uninit_valid1.rs
new file mode 100644
index 000000000..b541fa3ce
--- /dev/null
+++ b/gcc/testsuite/rust/execute/torture/assert_uninit_valid1.rs
@@ -0,0 +1,40 @@
+#![feature(no_core)]
+#![feature(intrinsics)]
+#![feature(lang_items)]
+#![no_core]
+
+// { dg-output "attempted to leave type `!` uninitialized, which is 
invalid\r*\nattempted to leave type `Nothing` uninitialized, which is 
invalid\r*\n" }
+
+#[lang = "sized"]
+trait Sized {}
+
+#[lang = "panic"]
+pub fn panic(s: &'static str) -> ! {
+    unsafe {
+        puts(s as *const str as *const i8);
+    }
+}
+
+extern "C" {
+    fn puts(s: *const i8);
+}
+
+extern "rust-intrinsic" {
+    fn assert_inhabited<T>();
+    fn assert_zero_valid<T>();
+    fn assert_uninit_valid<T>();
+}
+
+enum Nothing {}
+
+fn foo() {
+    unsafe { assert_uninit_valid::<!>() }; // { dg-warning "no-op" }
+    unsafe { assert_uninit_valid::<Nothing>() }; // { dg-warning "no-op" }
+    unsafe { assert_uninit_valid::<i32>() }; // { dg-warning "no-op" }
+}
+
+fn main() -> i32 {
+    foo();
+
+    0
+}
diff --git a/gcc/testsuite/rust/execute/torture/assert_zero_valid1.rs 
b/gcc/testsuite/rust/execute/torture/assert_zero_valid1.rs
new file mode 100644
index 000000000..67cf6edd7
--- /dev/null
+++ b/gcc/testsuite/rust/execute/torture/assert_zero_valid1.rs
@@ -0,0 +1,40 @@
+#![feature(no_core)]
+#![feature(intrinsics)]
+#![feature(lang_items)]
+#![no_core]
+
+// { dg-output "attempted to zero-initialize type `!`, which is 
invalid\r*\nattempted to zero-initialize type `Nothing`, which is invalid\r*\n" 
}
+
+#[lang = "sized"]
+trait Sized {}
+
+#[lang = "panic"]
+pub fn panic(s: &'static str) -> ! {
+    unsafe {
+        puts(s as *const str as *const i8);
+    }
+}
+
+extern "C" {
+    fn puts(s: *const i8);
+}
+
+extern "rust-intrinsic" {
+    fn assert_inhabited<T>();
+    fn assert_zero_valid<T>();
+    fn assert_uninit_valid<T>();
+}
+
+enum Nothing {}
+
+fn foo() {
+    unsafe { assert_zero_valid::<!>() }; // { dg-warning "no-op" }
+    unsafe { assert_zero_valid::<Nothing>() }; // { dg-warning "no-op" }
+    unsafe { assert_zero_valid::<i32>() }; // { dg-warning "no-op" }
+}
+
+fn main() -> i32 {
+    foo();
+
+    0
+}
-- 
2.55.0

Reply via email to