Hello, First attempt at contributing to GCC. My GCC knowledge is limited so any assistance would be appreciated.
I have tried to implement a `trivial_abi` attribute for GCC because I ran into an issue interacting with clang built code where `trivial_abi` was in use. Without implementing the same attribute the same way, we risk having nuanced calling convention mismatches. There also has been a long standing feature request https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107187 I have attempted to get the patch accepted by the formatter. However, I didn't fix the one remaining "There should be exactly one space between function name and parenthesis." following the code surrounding my change. Yuxuan
From a14881a229ed51a570958bf8d1af3cb53a5a1277 Mon Sep 17 00:00:00 2001 From: Yuxuan Chen <[email protected]> Date: Tue, 7 Oct 2025 14:17:35 -0700 Subject: [PATCH] implement trivial_abi --- gcc/cp/class.cc | 4 +- gcc/cp/cp-tree.h | 10 +- gcc/cp/module.cc | 2 + gcc/cp/pt.cc | 2 + gcc/cp/tree.cc | 159 ++++++++++++++++++ .../g++.dg/cpp0x/attr-trivial_abi1.C | 35 ++++ .../g++.dg/cpp0x/attr-trivial_abi2.C | 49 ++++++ .../g++.dg/cpp0x/attr-trivial_abi3.C | 51 ++++++ .../g++.dg/cpp0x/attr-trivial_abi4.C | 69 ++++++++ .../g++.dg/cpp0x/attr-trivial_abi5.C | 72 ++++++++ 10 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi1.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi2.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi3.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi4.C create mode 100644 gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi5.C diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index b56cc518a11..549a4b16630 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -2415,8 +2415,8 @@ finish_struct_bits (tree t) mode to be BLKmode, and force its TREE_ADDRESSABLE bit to be nonzero. This will cause it to be passed by invisible reference and prevent it from being returned in a register. */ - if (type_has_nontrivial_copy_init (t) - || TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)) + if (!trivial_for_calls_p (t) && (type_has_nontrivial_copy_init (t) + || TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t))) { SET_DECL_MODE (TYPE_MAIN_DECL (t), BLKmode); SET_TYPE_MODE (t, BLKmode); diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 6ee945f3ebf..a35696c8a36 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -2527,6 +2527,8 @@ struct GTY(()) lang_type { bool replaceable : 1; bool replaceable_computed : 1; + bool has_trivial_abi : 1; + /* When adding a flag here, consider whether or not it ought to apply to a template instance if it applies to the template. If so, make sure to copy it in instantiate_class_template! @@ -2536,7 +2538,7 @@ struct GTY(()) lang_type { /* There are some bits left to fill out a 32-bit word. Keep track of this by updating the size of this bitfield whenever you add or remove a flag. */ - unsigned dummy : 30; + unsigned dummy : 29; tree primary_base; vec<tree_pair_s, va_gc> *vcall_indices; @@ -2906,7 +2908,12 @@ struct GTY(()) lang_type { already. */ #define CLASSTYPE_REPLACEABLE_COMPUTED(NODE) \ (LANG_TYPE_CLASS_CHECK (NODE)->replaceable_computed) + +/* True if the class is trivial for the purpose of calls. */ +#define CLASSTYPE_HAS_TRIVIAL_ABI(NODE) \ + (LANG_TYPE_CLASS_CHECK (NODE)->has_trivial_abi) + /* Additional macros for inheritance information. */ /* Nonzero means that this class is on a path leading to a new vtable. */ @@ -8371,6 +8378,7 @@ extern bool trivial_type_p (const_tree); extern bool trivially_relocatable_type_p (tree); extern bool replaceable_type_p (tree); extern bool trivially_copyable_p (const_tree); +extern bool trivial_for_calls_p (const_tree); extern bool type_has_unique_obj_representations (const_tree); extern bool scalarish_type_p (const_tree); extern bool structural_type_p (tree, bool = false); diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index be873c19295..0404be19435 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -6209,6 +6209,7 @@ trees_out::lang_type_bools (tree t, bits_out& bits) WB (lang->replaceable); WB (lang->replaceable_computed); + WB (lang->has_trivial_abi); #undef WB } @@ -6287,6 +6288,7 @@ trees_in::lang_type_bools (tree t, bits_in& bits) RB (lang->replaceable); RB (lang->replaceable_computed); + RB (lang->has_trivial_abi); #undef RB return !get_overrun (); } diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index bd60d515653..9597f2aacb8 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -12727,6 +12727,8 @@ instantiate_class_template (tree type) = CLASSTYPE_REPLACEABLE_BIT (pattern); CLASSTYPE_REPLACEABLE_COMPUTED (type) = CLASSTYPE_REPLACEABLE_COMPUTED (pattern); + CLASSTYPE_HAS_TRIVIAL_ABI (type) + = CLASSTYPE_HAS_TRIVIAL_ABI (pattern); } pbinfo = TYPE_BINFO (pattern); diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index 03b58ff6005..929746fc95a 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -48,6 +48,7 @@ static tree handle_init_priority_attribute (tree *, tree, tree, int, bool *); static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *); static tree handle_contract_attribute (tree *, tree, tree, int, bool *); static tree handle_no_dangling_attribute (tree *, tree, tree, int, bool *); +static tree handle_trivial_abi_attribute (tree *, tree, tree, int, bool *); /* If REF is an lvalue, returns the kind of lvalue that REF is. Otherwise, returns clk_none. */ @@ -4796,6 +4797,21 @@ trivial_type_p (const_tree t) return scalarish_type_p (t); } +/* Returns 1 iff type T is trivial for the purpose of calls. + This includes types that are otherwise trivial, or types + that have the trivial_abi attribute. */ + +bool +trivial_for_calls_p (const_tree t) +{ + t = strip_array_types (CONST_CAST_TREE (t)); + + if (trivial_type_p (t)) + return true; + + return CLASSTYPE_HAS_TRIVIAL_ABI (t); +} + /* Returns 1 iff type T is a default-movable type, as defined in [class.prop]. */ @@ -4923,6 +4939,14 @@ trivially_relocatable_type_p (tree t) if (!COMPLETE_TYPE_P (t)) return false; + /* trivial_abi attribute makes the class trivially relocatable. */ + if (CLASSTYPE_HAS_TRIVIAL_ABI (t)) + { + CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t) = 1; + CLASSTYPE_TRIVIALLY_RELOCATABLE_COMPUTED (t) = 1; + return true; + } + if (!CLASSTYPE_TRIVIALLY_RELOCATABLE_BIT (t) && !union_with_no_declared_special_member_fns (t) && !default_movable_type_p (t)) @@ -5602,6 +5626,8 @@ static const attribute_spec cxx_gnu_attributes[] = handle_abi_tag_attribute, NULL }, { "no_dangling", 0, 1, false, true, false, false, handle_no_dangling_attribute, NULL }, + { "trivial_abi", 0, 0, false, true, false, true, + handle_trivial_abi_attribute, NULL }, }; const scoped_attribute_specs cxx_gnu_attribute_table = @@ -5933,6 +5959,139 @@ handle_no_dangling_attribute (tree *node, tree name, tree args, int, return NULL_TREE; } +/* Handle a "trivial_abi" attribute. */ + +static tree +handle_trivial_abi_attribute (tree *node, tree name, tree, int + bool *no_add_attrs) +{ + tree type = *node; + + /* Only allow on class types (struct, class, union) */ + if (TREE_CODE (type) != RECORD_TYPE + && TREE_CODE (type) != UNION_TYPE) + { + warning (OPT_Wattributes, + "%qE attribute ignored on non-class type", name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* For complete types, perform full validation. + For incomplete types, just allow the attribute - validation will + happen when the type is used or becomes complete. */ + if (COMPLETE_TYPE_P (type)) + { + /* Check for virtual bases. */ + if (TYPE_BINFO (type) && BINFO_N_BASE_BINFOS (TYPE_BINFO (type)) > 0) + { + for (unsigned int i = 0; + i < BINFO_N_BASE_BINFOS (TYPE_BINFO (type)); ++i) + { + tree base_binfo = BINFO_BASE_BINFO (TYPE_BINFO (type), i); + if (BINFO_VIRTUAL_P (base_binfo)) + { + warning (OPT_Wattributes, + "%qE attribute ignored on type with virtual " + "base classes", name); + *no_add_attrs = true; + return NULL_TREE; + } + } + } + + /* Check for virtual methods. */ + if (TYPE_POLYMORPHIC_P (type)) + { + warning (OPT_Wattributes, + "%qE attribute ignored on type with virtual methods", name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* Check for non-trivial base classes. */ + if (TYPE_BINFO (type) && BINFO_N_BASE_BINFOS (TYPE_BINFO (type)) > 0) + { + for (unsigned int i = 0; + i < BINFO_N_BASE_BINFOS (TYPE_BINFO (type)); ++i) + { + tree base_binfo = BINFO_BASE_BINFO (TYPE_BINFO (type), i); + tree base_type = BINFO_TYPE (base_binfo); + + if (!trivial_for_calls_p (base_type)) + { + warning (OPT_Wattributes, + "%qE attribute ignored on type with non-trivial base" + " class %qT", name, base_type); + *no_add_attrs = true; + return NULL_TREE; + } + } + } + + /* Check for non-trivial member types. */ + for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) + { + if (TREE_CODE (field) == FIELD_DECL && !DECL_ARTIFICIAL (field)) + { + tree field_type = TREE_TYPE (field); + + /* Handle array types. */ + while (TREE_CODE (field_type) == ARRAY_TYPE) + field_type = TREE_TYPE (field_type); + + if (CLASS_TYPE_P (field_type) + && !trivial_for_calls_p (field_type)) + { + warning (OPT_Wattributes, "%qE attribute ignored" + " on type with non-trivial member" + " %qD of type %qT", name, field, field_type); + *no_add_attrs = true; + return NULL_TREE; + } + } + } + + /* Check that not all copy/move constructors are deleted. */ + bool has_non_deleted_copy_or_move = false; + + /* Check for non-deleted copy constructor. */ + if (TYPE_HAS_COPY_CTOR (type) && !TYPE_HAS_COMPLEX_COPY_CTOR (type)) + has_non_deleted_copy_or_move = true; + + /* Check for non-deleted move constructor. */ + if (classtype_has_non_deleted_move_ctor (type)) + has_non_deleted_copy_or_move = true; + + /* Check declared constructors. */ + if (CLASSTYPE_CONSTRUCTORS (type)) + { + for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (type)); iter; ++iter) + { + tree fn = *iter; + if ((copy_fn_p (fn) || move_fn_p (fn)) && !DECL_DELETED_FN (fn)) + { + has_non_deleted_copy_or_move = true; + break; + } + } + } + + if (!has_non_deleted_copy_or_move) + { + warning (OPT_Wattributes, "%qE attribute ignored when all copy and" + " move constructors are deleted", name); + *no_add_attrs = true; + return NULL_TREE; + } + } + + if (CLASS_TYPE_P (type) && LANG_TYPE_CLASS_CHECK (type)) + CLASSTYPE_HAS_TRIVIAL_ABI (type) = 1; + + return NULL_TREE; +} + /* Return a new PTRMEM_CST of the indicated TYPE. The MEMBER is the thing pointed to by the constant. */ diff --git a/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi1.C b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi1.C new file mode 100644 index 00000000000..bd2cb81ba2a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi1.C @@ -0,0 +1,35 @@ +// { dg-do compile { target c++11 } } +// Test basic functionality of [[gnu::trivial_abi]] attribute + +struct [[gnu::trivial_abi]] TrivialAbi1 { + int x; + ~TrivialAbi1() { } // Non-trivial destructor, but should be allowed with trivial_abi +}; + +class [[gnu::trivial_abi]] TrivialAbi2 { + int y; +public: + TrivialAbi2(const TrivialAbi2&) { } // Non-trivial copy constructor, but should be allowed + ~TrivialAbi2() { } +}; + +// Test that the attribute is recognized and stored +static_assert(__builtin_is_trivially_relocatable(TrivialAbi1), "TrivialAbi1 should be trivially relocatable"); +static_assert(__builtin_is_trivially_relocatable(TrivialAbi2), "TrivialAbi2 should be trivially relocatable"); + +// Test basic usage in function parameters and return values +TrivialAbi1 func1(TrivialAbi1 param) { + return param; +} + +TrivialAbi2 func2(TrivialAbi2 param) { + return param; +} + +union [[gnu::trivial_abi]] TrivialUnion { + int a; + float b; + ~TrivialUnion() { } +}; + +static_assert(__builtin_is_trivially_relocatable(TrivialUnion), "TrivialUnion should be trivially relocatable"); \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi2.C b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi2.C new file mode 100644 index 00000000000..0961d502d91 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi2.C @@ -0,0 +1,49 @@ +// { dg-do compile { target c++11 } } +// Test error cases and restrictions for [[gnu::trivial_abi]] attribute + +// Test: attribute rejected on non-class types +int [[gnu::trivial_abi]] x; // { dg-warning "attribute ignored" } +typedef int [[gnu::trivial_abi]] int_t; // { dg-warning "attribute ignored" } + +// Test: attribute rejected on types with virtual functions +struct [[gnu::trivial_abi]] WithVirtual { // { dg-warning "attribute ignored on type with virtual methods" } + virtual void f() { } +}; + +// Test: attribute rejected on types with virtual bases +struct Base { }; +struct [[gnu::trivial_abi]] WithVirtualBase : virtual Base { // { dg-warning "attribute ignored on type with virtual base classes" } + int x; +}; + +// Test: attribute rejected when all copy/move constructors are deleted +struct [[gnu::trivial_abi]] AllDeleted { // { dg-warning "attribute ignored when all copy and move constructors are deleted" } + AllDeleted(const AllDeleted&) = delete; + AllDeleted(AllDeleted&&) = delete; + int x; +}; + +// Test: attribute rejected on types with non-trivial base classes +struct NonTrivialBase { + NonTrivialBase(const NonTrivialBase&) { } // Non-trivial copy +}; + +struct [[gnu::trivial_abi]] WithNonTrivialBase : NonTrivialBase { // { dg-warning "attribute ignored on type with non-trivial base class" } + int x; +}; + +// Test: attribute rejected on types with non-trivial member types +struct NonTrivialMember { + NonTrivialMember(const NonTrivialMember&) { } // Non-trivial copy +}; + +struct [[gnu::trivial_abi]] WithNonTrivialMember { // { dg-warning "attribute ignored on type with non-trivial member.*of type.*NonTrivialMember" } + NonTrivialMember member; + int x; +}; + +// Test: attribute rejected on array members of non-trivial types +struct [[gnu::trivial_abi]] WithNonTrivialArray { // { dg-warning "attribute ignored on type with non-trivial member.*of type.*NonTrivialMember" } + NonTrivialMember arr[5]; + int x; +}; \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi3.C b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi3.C new file mode 100644 index 00000000000..6324c7abb8e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi3.C @@ -0,0 +1,51 @@ +// { dg-do compile { target c++11 } } +// Test ABI behavior of [[gnu::trivial_abi]] attribute +// This test verifies that types with trivial_abi are passed by value +// instead of by invisible reference, even with non-trivial constructors + +struct [[gnu::trivial_abi]] TrivialAbiType { + int data; + TrivialAbiType() : data(0) { } + TrivialAbiType(const TrivialAbiType& other) : data(other.data) { } + TrivialAbiType(TrivialAbiType&& other) : data(other.data) { } + ~TrivialAbiType() { } +}; + +struct NonTrivialType { + int data; + NonTrivialType() : data(0) { } + NonTrivialType(const NonTrivialType& other) : data(other.data) { } + NonTrivialType(NonTrivialType&& other) : data(other.data) { } + ~NonTrivialType() { } +}; + +// Test that trivial_abi types are considered trivially relocatable +static_assert(__builtin_is_trivially_relocatable(TrivialAbiType), + "TrivialAbiType should be trivially relocatable"); +static_assert(!__builtin_is_trivially_relocatable(NonTrivialType), + "NonTrivialType should not be trivially relocatable"); + +// Function templates to test parameter passing +template<typename T> +void test_param_passing(T param) { + // Function body doesn't matter for ABI test +} + +// Test usage +void test_functions() { + TrivialAbiType trivial_obj; + NonTrivialType non_trivial_obj; + + // Both should compile, but with different ABI behavior + test_param_passing(trivial_obj); // Should pass by value + test_param_passing(non_trivial_obj); // Should pass by invisible reference +} + +// Test return values +TrivialAbiType return_trivial() { + return TrivialAbiType{}; +} + +NonTrivialType return_non_trivial() { + return NonTrivialType{}; +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi4.C b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi4.C new file mode 100644 index 00000000000..2e8de94570b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi4.C @@ -0,0 +1,69 @@ +// { dg-do compile { target c++11 } } +// Test edge cases and inheritance with [[gnu::trivial_abi]] attribute + +// Test: Derived classes with trivial_abi base classes +struct [[gnu::trivial_abi]] TrivialBase { + int x; + ~TrivialBase() { } +}; + +// This should be allowed - base class has trivial_abi +struct [[gnu::trivial_abi]] DerivedFromTrivial : TrivialBase { + int y; + ~DerivedFromTrivial() { } +}; + +static_assert(__builtin_is_trivially_relocatable(TrivialBase), + "TrivialBase should be trivially relocatable"); +static_assert(__builtin_is_trivially_relocatable(DerivedFromTrivial), + "DerivedFromTrivial should be trivially relocatable"); + +// Test: Multiple inheritance with all trivial bases +struct [[gnu::trivial_abi]] TrivialBase2 { + int z; + ~TrivialBase2() { } +}; + +struct [[gnu::trivial_abi]] MultipleInheritance : TrivialBase, TrivialBase2 { + int w; + ~MultipleInheritance() { } +}; + +static_assert(__builtin_is_trivially_relocatable(MultipleInheritance), + "MultipleInheritance should be trivially relocatable"); + +// Test: Empty class with trivial_abi +struct [[gnu::trivial_abi]] EmptyTrivialAbi { + ~EmptyTrivialAbi() { } +}; + +static_assert(__builtin_is_trivially_relocatable(EmptyTrivialAbi), + "EmptyTrivialAbi should be trivially relocatable"); + +// Test: Class with only trivial members +struct [[gnu::trivial_abi]] OnlyTrivialMembers { + int a; + float b; + char c[10]; + ~OnlyTrivialMembers() { } +}; + +static_assert(__builtin_is_trivially_relocatable(OnlyTrivialMembers), + "OnlyTrivialMembers should be trivially relocatable"); + +// Test: Template class with trivial_abi +template<typename T> +struct [[gnu::trivial_abi]] TemplateTrivialAbi { + T data; + ~TemplateTrivialAbi() { } +}; + +// This should work for scalar types +using IntTrivialAbi = TemplateTrivialAbi<int>; +static_assert(__builtin_is_trivially_relocatable(IntTrivialAbi), + "IntTrivialAbi should be trivially relocatable"); + +// Test with another trivial_abi type as template parameter +using NestedTrivialAbi = TemplateTrivialAbi<TrivialBase>; +static_assert(__builtin_is_trivially_relocatable(NestedTrivialAbi), + "NestedTrivialAbi should be trivially relocatable"); \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi5.C b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi5.C new file mode 100644 index 00000000000..696d20fa696 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi5.C @@ -0,0 +1,72 @@ +// { dg-do compile { target c++11 } } +// Test compatibility of [[gnu::trivial_abi]] with other GCC features + +// Test: Compatibility with other attributes +struct [[gnu::trivial_abi, gnu::packed]] TrivialAbiPacked { + char a; + int b; + ~TrivialAbiPacked() { } +}; + +static_assert(__builtin_is_trivially_relocatable(TrivialAbiPacked), + "TrivialAbiPacked should be trivially relocatable"); + +// Test: Compatibility with alignas +struct [[gnu::trivial_abi]] alignas(16) TrivialAbiAligned { + int x; + ~TrivialAbiAligned() { } +}; + +static_assert(__builtin_is_trivially_relocatable(TrivialAbiAligned), + "TrivialAbiAligned should be trivially relocatable"); + +// Test: Forward declaration and later definition +struct TrivialForward; +struct [[gnu::trivial_abi]] TrivialForward { + int x; + ~TrivialForward() { } +}; + +static_assert(__builtin_is_trivially_relocatable(TrivialForward), + "TrivialForward should be trivially relocatable"); + +// Test: typedef and using declarations +using TrivialAlias = TrivialAbiPacked; +typedef TrivialAbiAligned TrivialTypedef; + +static_assert(__builtin_is_trivially_relocatable(TrivialAlias), + "TrivialAlias should be trivially relocatable"); +static_assert(__builtin_is_trivially_relocatable(TrivialTypedef), + "TrivialTypedef should be trivially relocatable"); + +// Test: Local classes +void test_local_class() { + struct [[gnu::trivial_abi]] LocalTrivial { + int x; + ~LocalTrivial() { } + }; + + static_assert(__builtin_is_trivially_relocatable(LocalTrivial), + "LocalTrivial should be trivially relocatable"); +} + +// Test: Named nested structs with trivial_abi +struct ContainerClass { + struct [[gnu::trivial_abi]] NestedStruct { + int nested_member; + ~NestedStruct() { } + }; +}; + +// Test: Nested classes +struct Outer { + struct [[gnu::trivial_abi]] Nested { + int value; + ~Nested() { } + }; +}; + +static_assert(__builtin_is_trivially_relocatable(ContainerClass::NestedStruct), + "ContainerClass::NestedStruct should be trivially relocatable"); +static_assert(__builtin_is_trivially_relocatable(Outer::Nested), + "Outer::Nested should be trivially relocatable"); \ No newline at end of file -- 2.51.0
