https://gcc.gnu.org/g:9df7a490b1e5909d7c4e340a387878311c912128
commit r17-5712-g9df7a490b1e5909d7c4e340a387878311c912128 Author: Arthur Cohen <[email protected]> Date: Wed Oct 21 17:50:53 2026 +0200 gccrs: intrinsics: Add basic support for assert_uninit_valid<T> 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. Diff: --- gcc/rust/backend/rust-compile-intrinsic.cc | 5 +- gcc/rust/backend/rust-intrinsic-handlers.cc | 227 ++++++++++++--------- gcc/rust/backend/rust-intrinsic-handlers.h | 13 +- .../typecheck/rust-hir-type-check-intrinsic.cc | 2 + gcc/rust/util/rust-intrinsic-values.h | 1 + .../rust/execute/torture/assert_uninit_valid1.rs | 40 ++++ .../rust/execute/torture/assert_zero_valid1.rs | 40 ++++ 7 files changed, 231 insertions(+), 97 deletions(-) diff --git a/gcc/rust/backend/rust-compile-intrinsic.cc b/gcc/rust/backend/rust-compile-intrinsic.cc index 7b56056ae65e..131ad42dd455 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 82bb5b4d30fe..58f3f345ea3b 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 7ab3e8a701fa..d0aad88ee6ab 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 258f85faa0da..728b5fdd0904 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 5f498d16b215..e154f60b5e4b 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 000000000000..b541fa3ce341 --- /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 000000000000..67cf6edd767c --- /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 +}
