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 ¶m_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