diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index c52d372d3..3d9a5291a 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(subspace PUBLIC "assertions/unreachable.h" "boxed/box.h" "boxed/dyn.h" + "boxed/macros.h" "choice/__private/all_values_are_unique.h" "choice/__private/index_of_value.h" "choice/__private/index_type.h" @@ -35,6 +36,7 @@ target_sources(subspace PUBLIC "choice/__private/type_list.h" "choice/choice.h" "choice/choice_types.h" + "choice/macros.h" "cmp/__private/void_concepts.h" "cmp/cmp.h" "cmp/eq.h" @@ -151,7 +153,9 @@ target_sources(subspace PUBLIC "mem/copy.h" "mem/forward.h" "mem/move.h" + "mem/never_value_macros.h" "mem/never_value.h" + "mem/relocate_macros.h" "mem/relocate.h" "mem/remove_rvalue_reference.h" "mem/replace.h" @@ -366,4 +370,3 @@ if(${SUBSPACE_BUILD_TESTS}) ) gtest_discover_tests(subspace_overflow_unittests) endif() - diff --git a/sus/boxed/dyn.h b/sus/boxed/dyn.h index d430271cb..bc3adbae6 100644 --- a/sus/boxed/dyn.h +++ b/sus/boxed/dyn.h @@ -18,6 +18,7 @@ #include #include "sus/boxed/boxed.h" // namespace docs. +#include "sus/boxed/macros.h" #include "sus/macros/lifetimebound.h" #include "sus/mem/forward.h" #include "sus/mem/move.h" @@ -437,54 +438,3 @@ constexpr Dyn dyn( namespace sus { using sus::boxed::dyn; } - -/// Macro to help implement `DynC` for a concept `C`. The macro is placed in the -/// body of the `DynC` class. -/// -/// Here `DynC` is used as a placeholder name to refer to the virtual class -/// that type-erases for the concept `C`. The type erasure class is typically -/// named to match the concept, with a "Dyn" prefix. The type-aware subclass -/// of the type erasure class is typically named to match the concept with a -/// "Dyn" prefix and a "Typed" suffix. -/// -/// The `Concept` parameter is the concept `C` for which types are being -/// type-erased. -/// -/// The `DynConcept` parameter is the name of the type-erasure -/// class `DynC` which the macro is written within, and which has a pure virtual -/// interface matching the concept's requirements. -/// -/// The `DynConceptTyped` -/// parameter is the type-aware subclass of `DynC` which contains the -/// `sus_dyn_concept_typed` macro in its body, and the -/// implementation of the virtual interface that forwards calls through to the -/// concrete type. -/// -/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of -/// concept-satisfying types, and [DynConcept examples]( -/// $sus::boxed::DynConcept#examples) for examples of using the macro. -#define sus_dyn_concept(Concept, DynConcept, DynConceptTyped) \ - public: \ - template \ - static constexpr bool SatisfiesConcept = Concept; \ - template \ - using DynTyped = DynConceptTyped; \ - \ - DynConcept() = default; \ - virtual ~DynConcept() = default; \ - DynConcept(DynConcept&&) = delete; \ - DynConcept& operator=(DynConcept&&) = delete - -/// Macro to help implement `DynCTyped` for a concept `C`. The macro is placed -/// in the body of the `DynCTyped` class. -/// -/// See the TODO: link [`sus_dyn_concept`] macro for more, and -/// [DynConcept examples]($sus::boxed::DynConcept#examples) for examples -/// of using the macro. -#define sus_dyn_concept_typed(Concept, DynConcept, DynConceptTyped, VarName) \ - public: \ - static_assert(Concept); \ - constexpr DynConceptTyped(Store&& c) : VarName(::sus::forward(c)) {} \ - \ - private: \ - Store VarName; diff --git a/sus/boxed/macros.h b/sus/boxed/macros.h new file mode 100644 index 000000000..39f08a77a --- /dev/null +++ b/sus/boxed/macros.h @@ -0,0 +1,66 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/// Macro to help implement `DynC` for a concept `C`. The macro is placed in the +/// body of the `DynC` class. +/// +/// Here `DynC` is used as a placeholder name to refer to the virtual class +/// that type-erases for the concept `C`. The type erasure class is typically +/// named to match the concept, with a "Dyn" prefix. The type-aware subclass +/// of the type erasure class is typically named to match the concept with a +/// "Dyn" prefix and a "Typed" suffix. +/// +/// The `Concept` parameter is the concept `C` for which types are being +/// type-erased. +/// +/// The `DynConcept` parameter is the name of the type-erasure +/// class `DynC` which the macro is written within, and which has a pure virtual +/// interface matching the concept's requirements. +/// +/// The `DynConceptTyped` +/// parameter is the type-aware subclass of `DynC` which contains the +/// `sus_dyn_concept_typed` macro in its body, and the +/// implementation of the virtual interface that forwards calls through to the +/// concrete type. +/// +/// See [`DynConcept`]($sus::boxed::DynConcept) for more on type erasure of +/// concept-satisfying types, and [DynConcept examples]( +/// $sus::boxed::DynConcept#examples) for examples of using the macro. +#define sus_dyn_concept(Concept, DynConcept, DynConceptTyped) \ + public: \ + template \ + static constexpr bool SatisfiesConcept = Concept; \ + template \ + using DynTyped = DynConceptTyped; \ + \ + DynConcept() = default; \ + virtual ~DynConcept() = default; \ + DynConcept(DynConcept&&) = delete; \ + DynConcept& operator=(DynConcept&&) = delete + +/// Macro to help implement `DynCTyped` for a concept `C`. The macro is placed +/// in the body of the `DynCTyped` class. +/// +/// See the TODO: link [`sus_dyn_concept`] macro for more, and +/// [DynConcept examples]($sus::boxed::DynConcept#examples) for examples +/// of using the macro. +#define sus_dyn_concept_typed(Concept, DynConcept, DynConceptTyped, VarName) \ + public: \ + static_assert(Concept); \ + constexpr DynConceptTyped(Store&& c) : VarName(::sus::forward(c)) {} \ + \ + private: \ + Store VarName; diff --git a/sus/choice/choice.h b/sus/choice/choice.h index cb45dbda6..82c30e031 100644 --- a/sus/choice/choice.h +++ b/sus/choice/choice.h @@ -29,6 +29,7 @@ #include "sus/choice/__private/storage.h" #include "sus/choice/__private/type_list.h" #include "sus/choice/choice_types.h" +#include "sus/choice/macros.h" #include "sus/cmp/eq.h" #include "sus/cmp/ord.h" #include "sus/lib/__private/forward_decl.h" diff --git a/sus/choice/choice_types.h b/sus/choice/choice_types.h index 677762ff8..45f9901b3 100644 --- a/sus/choice/choice_types.h +++ b/sus/choice/choice_types.h @@ -18,58 +18,5 @@ #include "sus/choice/__private/storage.h" #include "sus/choice/__private/type_list.h" -#include "sus/macros/for_each.h" -#include "sus/macros/remove_parens.h" +#include "sus/choice/macros.h" #include "sus/tuple/tuple.h" - -/// A macro used to declare the value-type pairings in a [`Choice`]( -/// $sus::choice_type::Choice). See the [`Choice`]( -/// $sus::choice_type::Choice) type for examples of its use. -/// -/// Constructs a set of associated value and types pairings. The type of the -/// values need have no relationship to the specified types. -/// -/// # Details -/// The input takes the format: `(Value1, Type1A, Type1B), (Value2, Type2), ...` -/// The output is the sequence `TypeList, Tuple, -/// ...>, Value1, Value2, ...`. -/// Use `sus::macros::value_types::TypeAt>` to extract each tuple -/// type from the returned set of types. -/// -/// The number of values that follow will always be the same as the number of -/// types in the set. This is the primary value of the `sus_choice_types()` -/// construct. -/// -/// # Example -/// ``` -/// template -/// class Example { -/// using first_type = v<0, Types>; -/// static constexpr auto first_value = FirstValue; -/// }; -/// -/// using E = Example; -/// // `E::first_value` will be `'h'` of type `char`. -/// // `E::first_type` will be `Tuple`. -/// ``` -// -// clang-format off -#define sus_choice_types(...) \ - sus::choice_type::__private::TypeList< \ - _sus_for_each(_sus__make_union_storage_type, _sus_for_each_sep_comma, \ - _sus_for_each(_sus__value_types_types, _sus_for_each_sep_comma, \ - __VA_ARGS__))>, \ - _sus_for_each(_sus__value_types_value, _sus_for_each_sep_comma, \ - __VA_ARGS__) - -// clang-format on - -#define _sus__make_union_storage_type(types) \ - ::sus::choice_type::__private::MakeStorageType<_sus_remove_parens(types)>::type - -#define _sus__first(a, ...) a -#define _sus__second_plus(a, ...) __VA_ARGS__ - -#define _sus__value_types_types(x) \ - (_sus_remove_parens_and_eval(_sus__second_plus, x)) -#define _sus__value_types_value(x) _sus_remove_parens_and_eval(_sus__first, x) diff --git a/sus/choice/macros.h b/sus/choice/macros.h new file mode 100644 index 000000000..f050dc776 --- /dev/null +++ b/sus/choice/macros.h @@ -0,0 +1,72 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IWYU pragma: private, include "sus/choice/choice.h" +// IWYU pragma: friend "sus/.*" +#pragma once + +#include "sus/macros/for_each.h" +#include "sus/macros/remove_parens.h" + +/// A macro used to declare the value-type pairings in a [`Choice`]( +/// $sus::choice_type::Choice). See the [`Choice`]( +/// $sus::choice_type::Choice) type for examples of its use. +/// +/// Constructs a set of associated value and types pairings. The type of the +/// values need have no relationship to the specified types. +/// +/// # Details +/// The input takes the format: `(Value1, Type1A, Type1B), (Value2, Type2), ...` +/// The output is the sequence `TypeList, Tuple, +/// ...>, Value1, Value2, ...`. +/// Use `sus::macros::value_types::TypeAt>` to extract each tuple +/// type from the returned set of types. +/// +/// The number of values that follow will always be the same as the number of +/// types in the set. This is the primary value of the `sus_choice_types()` +/// construct. +/// +/// # Example +/// ``` +/// template +/// class Example { +/// using first_type = v<0, Types>; +/// static constexpr auto first_value = FirstValue; +/// }; +/// +/// using E = Example; +/// // `E::first_value` will be `'h'` of type `char`. +/// // `E::first_type` will be `Tuple`. +/// ``` +// +// clang-format off +#define sus_choice_types(...) \ + sus::choice_type::__private::TypeList< \ + _sus_for_each(_sus__make_union_storage_type, _sus_for_each_sep_comma, \ + _sus_for_each(_sus__value_types_types, _sus_for_each_sep_comma, \ + __VA_ARGS__))>, \ + _sus_for_each(_sus__value_types_value, _sus_for_each_sep_comma, \ + __VA_ARGS__) + +// clang-format on + +#define _sus__make_union_storage_type(types) \ + ::sus::choice_type::__private::MakeStorageType<_sus_remove_parens(types)>::type + +#define _sus__first(a, ...) a +#define _sus__second_plus(a, ...) __VA_ARGS__ + +#define _sus__value_types_types(x) \ + (_sus_remove_parens_and_eval(_sus__second_plus, x)) +#define _sus__value_types_value(x) _sus_remove_parens_and_eval(_sus__first, x) diff --git a/sus/mem/never_value.h b/sus/mem/never_value.h index 80611584f..777045947 100644 --- a/sus/mem/never_value.h +++ b/sus/mem/never_value.h @@ -24,6 +24,7 @@ #include "sus/macros/pure.h" #include "sus/marker/unsafe.h" #include "sus/mem/forward.h" +#include "sus/mem/never_value_macros.h" #include "sus/mem/relocate.h" namespace sus::mem { @@ -134,42 +135,3 @@ template concept NeverValueField = __private::NeverValueChecker::has_field; } // namespace sus::mem - -/// Mark a class field as never being a specific value, often a zero, after a -/// constructor has run and before the destructor has completed. This allows -/// querying if a class is constructed in a memory location, since the class is -/// constructed iff the value of the field is not the never-value. -/// -/// The named field can be compared to the `never_value` to determine if the -/// object is constructed. The field must be set to the `destroy_value` just -/// prior to destruction. The latter is meant to help the destructor be a no-op -/// when the type is in a never-value state, if the never-value would be read in -/// the destructor. -/// -/// The macro includes `private:` which changes the class definition visibility -/// to private. -#define sus_class_never_value_field(unsafe_fn, T, field_name, never_value, \ - destroy_value) \ - private: \ - static_assert( \ - std::same_as); \ - \ - template \ - friend struct ::sus::mem::__private::NeverValueAccess; \ - template \ - friend struct ::sus::mem::__private::NeverValueChecker; \ - \ - _sus_pure constexpr bool _sus_Unsafe_NeverValueIsConstructed( \ - ::sus::marker::UnsafeFnMarker) const noexcept { \ - static_assert( \ - std::is_assignable_v, \ - "The `never_value` must be able to be assigned to the named field."); \ - return !(field_name == never_value); \ - } \ - constexpr void _sus_Unsafe_NeverValueSetDestroyValue( \ - ::sus::marker::UnsafeFnMarker) noexcept { \ - static_assert(::sus::cmp::Eq, \ - "The `never_value` must be comparable to the named field."); \ - field_name = destroy_value; \ - } \ - static_assert(true) diff --git a/sus/mem/never_value_macros.h b/sus/mem/never_value_macros.h new file mode 100644 index 000000000..49bb6db82 --- /dev/null +++ b/sus/mem/never_value_macros.h @@ -0,0 +1,54 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/// Mark a class field as never being a specific value, often a zero, after a +/// constructor has run and before the destructor has completed. This allows +/// querying if a class is constructed in a memory location, since the class is +/// constructed iff the value of the field is not the never-value. +/// +/// The named field can be compared to the `never_value` to determine if the +/// object is constructed. The field must be set to the `destroy_value` just +/// prior to destruction. The latter is meant to help the destructor be a no-op +/// when the type is in a never-value state, if the never-value would be read in +/// the destructor. +/// +/// The macro includes `private:` which changes the class definition visibility +/// to private. +#define sus_class_never_value_field(unsafe_fn, T, field_name, never_value, \ + destroy_value) \ + private: \ + static_assert( \ + std::same_as); \ + \ + template \ + friend struct ::sus::mem::__private::NeverValueAccess; \ + template \ + friend struct ::sus::mem::__private::NeverValueChecker; \ + \ + _sus_pure constexpr bool _sus_Unsafe_NeverValueIsConstructed( \ + ::sus::marker::UnsafeFnMarker) const noexcept { \ + static_assert( \ + std::is_assignable_v, \ + "The `never_value` must be able to be assigned to the named field."); \ + return !(field_name == never_value); \ + } \ + constexpr void _sus_Unsafe_NeverValueSetDestroyValue( \ + ::sus::marker::UnsafeFnMarker) noexcept { \ + static_assert(::sus::cmp::Eq, \ + "The `never_value` must be comparable to the named field."); \ + field_name = destroy_value; \ + } \ + static_assert(true) diff --git a/sus/mem/relocate.h b/sus/mem/relocate.h index 10fefbb22..e97a1ef2e 100644 --- a/sus/mem/relocate.h +++ b/sus/mem/relocate.h @@ -20,6 +20,7 @@ #include "sus/macros/builtin.h" #include "sus/macros/compiler.h" #include "sus/marker/unsafe.h" +#include "sus/mem/relocate_macros.h" #include "sus/mem/size_of.h" namespace sus::mem { @@ -127,176 +128,3 @@ template concept TriviallyRelocatable = (... && __private::TriviallyRelocatable_impl); } // namespace sus::mem - -/// An attribute to allow a class to be passed in registers. -/// -/// This should only be used when the class is also marked as unconditionally -/// relocatable with `sus_class_trivially_relocatable()` or -/// `sus_class_trivially_relocatable_unchecked()`. -/// -/// This also enables trivial relocation in libc++ if compiled with clang. -#define _sus_trivial_abi sus_if_clang(clang::trivial_abi) - -/// Mark a class as unconditionally trivially relocatable while also asserting -/// that all of the types passed as arguments are also marked as such. -/// -/// Typically all field types in the class should be passed to the macro as -/// its arguments. -/// -/// To additionally allow the class to be passed in registers, the class can be -/// marked with the `[[clang::trivial_abi]]` attribute. -/// -/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to -/// determine if a type is trivially relocatable, and to test with static_assert -/// that types are matching what you are expecting. This allows containers to -/// optimize their implementations when relocating the type in memory. -/// -/// | Macro | Style | -/// | ----- | ----- | -/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | -/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | -/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | -/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | -/// -/// # Example -/// ``` -/// struct S { -/// Thing thing; -/// i32 more; -/// -/// sus_class_trivially_relocatable( -/// unsafe_fn, -/// decltype(thing), -/// decltype(more)); -/// }; -/// ``` -#define sus_class_trivially_relocatable(unsafe_fn, ...) \ - static_assert(std::is_same_v); \ - sus_class_trivially_relocatable_if_types(unsafe_fn, __VA_ARGS__); \ - static_assert(SusUnsafeTrivialRelocate, "Type is not trivially relocatable") - -/// Mark a class as trivially relocatable if the types passed as arguments are -/// all trivially relocatable. -/// -/// This macro is most useful in templates where the template parameter types -/// are unknown and can be passed to the macro to determine if they are -/// trivially relocatable. -/// -/// Avoid marking the class with the `[[clang::trivial_abi]]` attribute, as when -/// the class is not trivially relocatable (since a subtype is not trivially -/// relocatable), it can cause memory safety bugs and Undefined Behaviour. -/// -/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to -/// determine if a type is trivially relocatable, and to test with static_assert -/// that types are matching what you are expecting. This allows containers to -/// optimize their implementations when relocating the type in memory. -/// -/// | Macro | Style | -/// | ----- | ----- | -/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | -/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | -/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | -/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | -/// -/// # Example -/// ``` -/// template -/// struct S { -/// Thing thing; -/// i32 more; -/// -/// sus_class_trivially_relocatable_if_types( -/// unsafe_fn, -/// decltype(thing), -/// decltype(more)); -/// }; -/// ``` -#define sus_class_trivially_relocatable_if_types(unsafe_fn, ...) \ - static_assert(std::is_same_v); \ - template \ - friend struct ::sus::mem::__private::RelocatableTag; \ - /** #[doc.hidden] */ \ - static constexpr bool SusUnsafeTrivialRelocate = \ - ::sus::mem::TriviallyRelocatable<__VA_ARGS__> - -/// Mark a class as trivially relocatable based on a compile-time condition. -/// -/// This macro is most useful in templates where the condition is based on the -/// template parameters. -/// -/// Avoid marking the class with the `[[clang::trivial_abi]]` attribute, as when -/// the class is not trivially relocatable (the value is false), it can cause -/// memory safety bugs and Undefined Behaviour. -/// -/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to -/// determine if a type is trivially relocatable, and to test with static_assert -/// that types are matching what you are expecting. This allows containers to -/// optimize their implementations when relocating the type in memory. -/// -/// | Macro | Style | -/// | ----- | ----- | -/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | -/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | -/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | -/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | -/// -/// # Example -/// ``` -/// template -/// struct S { -/// Thing thing; -/// i32 more; -/// -/// sus_class_trivially_relocatable_if( -/// unsafe_fn, -/// SomeCondition> -/// && sus::mem::TriviallyRelocatable -/// && sus::mem::TriviallyRelocatable); -/// }; -/// ``` -#define sus_class_trivially_relocatable_if(unsafe_fn, is_trivially_reloc) \ - static_assert(std::is_same_v); \ - static_assert( \ - std::is_same_v, bool>); \ - template \ - friend struct ::sus::mem::__private::RelocatableTag; \ - static constexpr bool SusUnsafeTrivialRelocate = is_trivially_reloc - -/// Mark a class as unconditionally trivially relocatable, without any -/// additional assertion to help verify correctness. -/// -/// Generally, prefer to use sus_class_trivially_relocatable() with -/// all field types passed to the macro. -/// To additionally allow the class to be passed in registers, the class can be -/// marked with the `[[clang::trivial_abi]]` attribute. -/// -/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to -/// determine if a type is trivially relocatable, and to test with static_assert -/// that types are matching what you are expecting. This allows containers to -/// optimize their implementations when relocating the type in memory. -/// -/// | Macro | Style | -/// | ----- | ----- | -/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | -/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | -/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | -/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | -/// -/// # Example -/// ``` -/// struct S { -/// Thing thing; -/// i32 more; -/// -/// sus_class_trivially_relocatable_unchecked(unsafe_fn); -/// }; -/// ``` -#define sus_class_trivially_relocatable_unchecked(unsafe_fn) \ - static_assert(std::is_same_v); \ - template \ - friend struct ::sus::mem::__private::RelocatableTag; \ - static constexpr bool SusUnsafeTrivialRelocate = true diff --git a/sus/mem/relocate_macros.h b/sus/mem/relocate_macros.h new file mode 100644 index 000000000..6c92b9771 --- /dev/null +++ b/sus/mem/relocate_macros.h @@ -0,0 +1,188 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/// An attribute to allow a class to be passed in registers. +/// +/// This should only be used when the class is also marked as unconditionally +/// relocatable with `sus_class_trivially_relocatable()` or +/// `sus_class_trivially_relocatable_unchecked()`. +/// +/// This also enables trivial relocation in libc++ if compiled with clang. +#define _sus_trivial_abi sus_if_clang(clang::trivial_abi) + +/// Mark a class as unconditionally trivially relocatable while also asserting +/// that all of the types passed as arguments are also marked as such. +/// +/// Typically all field types in the class should be passed to the macro as +/// its arguments. +/// +/// To additionally allow the class to be passed in registers, the class can be +/// marked with the `[[clang::trivial_abi]]` attribute. +/// +/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to +/// determine if a type is trivially relocatable, and to test with static_assert +/// that types are matching what you are expecting. This allows containers to +/// optimize their implementations when relocating the type in memory. +/// +/// | Macro | Style | +/// | ----- | ----- | +/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | +/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | +/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | +/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | +/// +/// # Example +/// ``` +/// struct S { +/// Thing thing; +/// i32 more; +/// +/// sus_class_trivially_relocatable( +/// unsafe_fn, +/// decltype(thing), +/// decltype(more)); +/// }; +/// ``` +#define sus_class_trivially_relocatable(unsafe_fn, ...) \ + static_assert(std::is_same_v); \ + sus_class_trivially_relocatable_if_types(unsafe_fn, __VA_ARGS__); \ + static_assert(SusUnsafeTrivialRelocate, "Type is not trivially relocatable") + +/// Mark a class as trivially relocatable if the types passed as arguments are +/// all trivially relocatable. +/// +/// This macro is most useful in templates where the template parameter types +/// are unknown and can be passed to the macro to determine if they are +/// trivially relocatable. +/// +/// Avoid marking the class with the `[[clang::trivial_abi]]` attribute, as when +/// the class is not trivially relocatable (since a subtype is not trivially +/// relocatable), it can cause memory safety bugs and Undefined Behaviour. +/// +/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to +/// determine if a type is trivially relocatable, and to test with static_assert +/// that types are matching what you are expecting. This allows containers to +/// optimize their implementations when relocating the type in memory. +/// +/// | Macro | Style | +/// | ----- | ----- | +/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | +/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | +/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | +/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | +/// +/// # Example +/// ``` +/// template +/// struct S { +/// Thing thing; +/// i32 more; +/// +/// sus_class_trivially_relocatable_if_types( +/// unsafe_fn, +/// decltype(thing), +/// decltype(more)); +/// }; +/// ``` +#define sus_class_trivially_relocatable_if_types(unsafe_fn, ...) \ + static_assert(std::is_same_v); \ + template \ + friend struct ::sus::mem::__private::RelocatableTag; \ + /** #[doc.hidden] */ \ + static constexpr bool SusUnsafeTrivialRelocate = \ + ::sus::mem::TriviallyRelocatable<__VA_ARGS__> + +/// Mark a class as trivially relocatable based on a compile-time condition. +/// +/// This macro is most useful in templates where the condition is based on the +/// template parameters. +/// +/// Avoid marking the class with the `[[clang::trivial_abi]]` attribute, as when +/// the class is not trivially relocatable (the value is false), it can cause +/// memory safety bugs and Undefined Behaviour. +/// +/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to +/// determine if a type is trivially relocatable, and to test with static_assert +/// that types are matching what you are expecting. This allows containers to +/// optimize their implementations when relocating the type in memory. +/// +/// | Macro | Style | +/// | ----- | ----- | +/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | +/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | +/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | +/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | +/// +/// # Example +/// ``` +/// template +/// struct S { +/// Thing thing; +/// i32 more; +/// +/// sus_class_trivially_relocatable_if( +/// unsafe_fn, +/// SomeCondition> +/// && sus::mem::TriviallyRelocatable +/// && sus::mem::TriviallyRelocatable); +/// }; +/// ``` +#define sus_class_trivially_relocatable_if(unsafe_fn, is_trivially_reloc) \ + static_assert(std::is_same_v); \ + static_assert( \ + std::is_same_v, bool>); \ + template \ + friend struct ::sus::mem::__private::RelocatableTag; \ + static constexpr bool SusUnsafeTrivialRelocate = is_trivially_reloc + +/// Mark a class as unconditionally trivially relocatable, without any +/// additional assertion to help verify correctness. +/// +/// Generally, prefer to use sus_class_trivially_relocatable() with +/// all field types passed to the macro. +/// To additionally allow the class to be passed in registers, the class can be +/// marked with the `[[clang::trivial_abi]]` attribute. +/// +/// Use the [`TriviallyRelocatable`]($sus::mem::TriviallyRelocatable) concept to +/// determine if a type is trivially relocatable, and to test with static_assert +/// that types are matching what you are expecting. This allows containers to +/// optimize their implementations when relocating the type in memory. +/// +/// | Macro | Style | +/// | ----- | ----- | +/// | [`sus_class_trivially_relocatable`]($sus_class_trivially_relocatable) | **asserts** all param types are trivially relocatable | +/// | [`sus_class_trivially_relocatable_if_types`]($sus_class_trivially_relocatable_if_types) | is **conditionally** trivially relocatable if all param types are | +/// | [`sus_class_trivially_relocatable_if`]($sus_class_trivially_relocatable_if) | is **conditionally** trivially relocatable if the condition is true | +/// | [`sus_class_trivially_relocatable_unchecked`]($sus_class_trivially_relocatable_unchecked) | is trivially relocatable without any condition or assertion | +/// +/// # Example +/// ``` +/// struct S { +/// Thing thing; +/// i32 more; +/// +/// sus_class_trivially_relocatable_unchecked(unsafe_fn); +/// }; +/// ``` +#define sus_class_trivially_relocatable_unchecked(unsafe_fn) \ + static_assert(std::is_same_v); \ + template \ + friend struct ::sus::mem::__private::RelocatableTag; \ + static constexpr bool SusUnsafeTrivialRelocate = true