From 2fda82530fb9c183c7cfb2c8b7b4001e9c586f01 Mon Sep 17 00:00:00 2001 From: Serhii Yolkin Date: Thu, 3 Oct 2024 18:20:49 +0200 Subject: [PATCH] Implement "placement new"-style constructors generation --- dear_bindings.py | 17 ++++++- docs/MetadataFormat.md | 1 + src/code_dom/classstructunion.py | 1 + src/code_dom/functiondeclaration.py | 1 + src/generators/gen_function_stubs.py | 49 ++++++++++++------- src/generators/gen_metadata.py | 1 + src/modifiers/__init__.py | 1 + src/modifiers/mod_flatten_class_functions.py | 19 ++++++- .../mod_mark_placement_constructor_structs.py | 10 ++++ ...emove_heap_constructors_and_destructors.py | 4 +- 10 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 src/modifiers/mod_mark_placement_constructor_structs.py diff --git a/dear_bindings.py b/dear_bindings.py index f00f4bd..6e914e0 100644 --- a/dear_bindings.py +++ b/dear_bindings.py @@ -220,7 +220,6 @@ def convert_header( mod_set_arguments_as_nullable.apply(dom_root, ["fmt"], False) # All arguments called "fmt" are non-nullable mod_remove_operators.apply(dom_root) - mod_remove_heap_constructors_and_destructors.apply(dom_root) mod_convert_references_to_pointers.apply(dom_root) if no_struct_by_value_arguments: mod_convert_by_value_struct_args_to_pointers.apply(dom_root) @@ -246,6 +245,22 @@ def convert_header( 'ImRect', 'ImGuiListClipperRange' ]) + + # Mark certain types as needing placement new constructors that initialize the memory block passed into them + mod_mark_placement_constructor_structs.apply(dom_root, [ + 'ImFontConfig', + 'ImFontGlyphRangesBuilder', + 'ImGuiListClipper', + 'ImGuiSelectionBasicStorage', + 'ImGuiSelectionExternalStorage', + 'ImGuiStorage', + 'ImGuiTextBuffer', + 'ImGuiTextFilter', + 'ImGuiWindowClass', + ]) + + mod_remove_heap_constructors_and_destructors.apply(dom_root) + mod_mark_internal_members.apply(dom_root) mod_flatten_class_functions.apply(dom_root) mod_flatten_inheritance.apply(dom_root) diff --git a/docs/MetadataFormat.md b/docs/MetadataFormat.md index 62360af..8908e61 100644 --- a/docs/MetadataFormat.md +++ b/docs/MetadataFormat.md @@ -409,6 +409,7 @@ for reference, but without any internal details. | original_fully_qualified_name | The original C++ name of the structure | | kind _(previously "type")_ | The type of the structure (either `struct` or `union`) | | by_value | Is this structure normally pass-by-value? | +| has_placement_constructor | Does this structure have an initializer function? | | forward_declaration | Is this a forward-declaration of the structure? | | is_anonymous | Is this an anonymous struct? | | fields | List of contained fields | diff --git a/src/code_dom/classstructunion.py b/src/code_dom/classstructunion.py index 7bc681f..3ce984e 100644 --- a/src/code_dom/classstructunion.py +++ b/src/code_dom/classstructunion.py @@ -10,6 +10,7 @@ def __init__(self): self.is_anonymous = True self.is_forward_declaration = True self.is_by_value = False # Is this to be passed by value? (set during modification) + self.has_placement_constructor = False # Do we need to generate a placement new style constructor to initialize default values? self.structure_type = None # Will be "STRUCT", "CLASS" or "UNION" self.is_imgui_api = False # Does this use IMGUI_API? self.base_classes = None # List of base classes, as tuples with their accessibility (i.e. ("private", "CBase")) diff --git a/src/code_dom/functiondeclaration.py b/src/code_dom/functiondeclaration.py index 95a4e36..187f160 100644 --- a/src/code_dom/functiondeclaration.py +++ b/src/code_dom/functiondeclaration.py @@ -18,6 +18,7 @@ def __init__(self): self.is_operator = False self.is_constructor = False self.is_by_value_constructor = False # Is this a by-value type constructor? (set during flattening) + self.is_placement_constructor = False # Is this a 'placement new'-style constructor? (set during flattening) self.is_destructor = False self.is_imgui_api = False self.im_fmtargs = None diff --git a/src/generators/gen_function_stubs.py b/src/generators/gen_function_stubs.py index 9e9fed0..5137ae0 100644 --- a/src/generators/gen_function_stubs.py +++ b/src/generators/gen_function_stubs.py @@ -134,7 +134,7 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s is_const_function = False self_class_type = function.original_class # Constructors are a special case as they don't get self passed in - if self_class_type is not None and not function.is_constructor and not function.is_static: + if self_class_type is not None and (not function.is_constructor or function.is_placement_constructor) and not function.is_static: has_self = True # The function's own is_const will be false as it has been transformed into a non-const stub, but the # self argument will be const in the case it was originally const @@ -220,23 +220,28 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s # Generate return type cast if necessary if function.is_constructor: - # Constructors are a special case that returns the type they are constructing + # 'placement new' style constructors don't return anything + if function.is_placement_constructor: + return_cast_prefix = "" + return_cast_suffix = "" + else: + # Constructors are a special case that returns the type they are constructing - # To use generate_cast() we need to generate a type element that represents what the C++ new() call will - # be returning - original_type_name = original_function.get_parent_class().get_fully_qualified_name() + # To use generate_cast() we need to generate a type element that represents what the C++ new() call will + # be returning + original_type_name = original_function.get_parent_class().get_fully_qualified_name() - if not function.is_by_value_constructor: - original_type_name += "*" + if not function.is_by_value_constructor: + original_type_name += "*" - new_type = code_dom.DOMType() - new_type.tokens = utils.create_tokens_for_type(original_type_name) + new_type = code_dom.DOMType() + new_type.tokens = utils.create_tokens_for_type(original_type_name) - return_cast_prefix, return_cast_suffix = generate_cast(new_type, - function.return_type, - imgui_custom_types, - nested_classes, - to_cpp=False) + return_cast_prefix, return_cast_suffix = generate_cast(new_type, + function.return_type, + imgui_custom_types, + nested_classes, + to_cpp=False) else: return_cast_prefix, return_cast_suffix = generate_cast(original_function.return_type, function.return_type, @@ -255,16 +260,22 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s # V function that takes a va_list function_call_name += "V" + self_pointer_cast = None if has_self: # Cast self pointer if is_const_function: - thunk_call += "reinterpret_cast(self)->" + "*>(self)" else: - thunk_call += "reinterpret_cast<" + \ + self_pointer_cast = "reinterpret_cast<" + \ self_class_type.get_original_fully_qualified_name(include_leading_colons=True) + \ - "*>(self)->" + "*>(self)" + + if function.is_placement_constructor: + thunk_call += "IM_PLACEMENT_NEW(" + self_pointer_cast + ") " + else: + thunk_call += self_pointer_cast + "->" else: # If the function is not a member function, prefix the call with :: to avoid accidentally picking # up functions from the wrong namespace @@ -278,7 +289,7 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s thunk_call += "&" if function.is_constructor: - if not function.is_by_value_constructor: + if not function.is_by_value_constructor and not function.is_placement_constructor: # Add new (unless this is by-value, in which case we don't want it) thunk_call += "new " # Constructor calls use the typename, not the nominal function name within the type diff --git a/src/generators/gen_metadata.py b/src/generators/gen_metadata.py index b93ab74..2fac907 100644 --- a/src/generators/gen_metadata.py +++ b/src/generators/gen_metadata.py @@ -371,6 +371,7 @@ def emit_struct(struct): result["original_fully_qualified_name"] = struct.get_original_fully_qualified_name() result["kind"] = struct.structure_type.lower() # Lowercase this for consistency with C result["by_value"] = struct.is_by_value + result["has_placement_constructor"] = struct.has_placement_constructor result["forward_declaration"] = struct.is_forward_declaration result["is_anonymous"] = struct.is_anonymous diff --git a/src/modifiers/__init__.py b/src/modifiers/__init__.py index 5c8c9b9..37cacf4 100644 --- a/src/modifiers/__init__.py +++ b/src/modifiers/__init__.py @@ -29,6 +29,7 @@ from . import mod_rename_function_by_signature from . import mod_forward_declare_structs from . import mod_mark_by_value_structs +from . import mod_mark_placement_constructor_structs from . import mod_add_includes from . import mod_change_includes from . import mod_remove_includes diff --git a/src/modifiers/mod_flatten_class_functions.py b/src/modifiers/mod_flatten_class_functions.py index 58e3460..903528b 100644 --- a/src/modifiers/mod_flatten_class_functions.py +++ b/src/modifiers/mod_flatten_class_functions.py @@ -13,6 +13,7 @@ def apply(dom_root): current_add_point = struct is_by_value = struct.is_by_value + has_placement_constructor = struct.has_placement_constructor # Find any child functions # Note that this doesn't handle functions in nested classes correctly @@ -23,15 +24,31 @@ def apply(dom_root): if function.is_constructor: # Constructors get modified to return a pointer to the newly-created object function.return_type = code_dom.DOMType() + function.return_type.parent = function if is_by_value: # By-value types have constructors that return by-value, unsurprisingly function.return_type.tokens = [utils.create_token(struct.name)] + elif has_placement_constructor: + function.return_type.tokens = [utils.create_token("void")] + + # Add a self argument as the first argument of the function + self_arg = code_dom.DOMFunctionArgument() + self_arg.parent = function + self_arg.name = "self" + self_arg.arg_type = code_dom.DOMType() + self_arg.arg_type.parent = self_arg + + self_arg.is_instance_pointer = True + self_arg.arg_type.tokens = [utils.create_token(struct.name), utils.create_token("*")] + + function.arguments.insert(0, self_arg) else: function.return_type.tokens = [utils.create_token(struct.name), utils.create_token("*")] - function.return_type.parent = function + # Make a note for the code generator that this is a by-value constructor function.is_by_value_constructor = is_by_value + function.is_placement_constructor = has_placement_constructor elif function.is_destructor: if is_by_value: # We don't support this for fairly obvious reasons diff --git a/src/modifiers/mod_mark_placement_constructor_structs.py b/src/modifiers/mod_mark_placement_constructor_structs.py new file mode 100644 index 0000000..4cf9843 --- /dev/null +++ b/src/modifiers/mod_mark_placement_constructor_structs.py @@ -0,0 +1,10 @@ +from src import code_dom +from src import utils + + +# This modifier adds a marker to structs that should be treated as having a placement new style constructor, +# which subsequent modifiers (and the code generator) can use +def apply(dom_root, has_placement_constructor_structs): + for struct in dom_root.list_all_children_of_type(code_dom.DOMClassStructUnion): + if struct.name in has_placement_constructor_structs: + struct.has_placement_constructor = True diff --git a/src/modifiers/mod_remove_heap_constructors_and_destructors.py b/src/modifiers/mod_remove_heap_constructors_and_destructors.py index 07b94a3..4844bec 100644 --- a/src/modifiers/mod_remove_heap_constructors_and_destructors.py +++ b/src/modifiers/mod_remove_heap_constructors_and_destructors.py @@ -2,10 +2,10 @@ # This modifier removes constructions and destructors that would result in heap allocations -# (i.e. those not on value types) +# (i.e. those not on value types or types marked as having placement new constructors) def apply(dom_root): for function in dom_root.list_all_children_of_type(code_dom.DOMFunctionDeclaration): if function.is_constructor or function.is_destructor: parent_class = function.get_parent_class() - if (parent_class is not None) and (not parent_class.is_by_value): + if (parent_class is not None) and (not parent_class.is_by_value) and (not parent_class.has_placement_constructor): function.parent.remove_child(function)