Skip to content

Commit

Permalink
Implement "placement new"-style constructors generation
Browse files Browse the repository at this point in the history
  • Loading branch information
ZimM-LostPolygon committed Oct 3, 2024
1 parent 9765264 commit 2fda825
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 23 deletions.
17 changes: 16 additions & 1 deletion dear_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/MetadataFormat.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions src/code_dom/classstructunion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
1 change: 1 addition & 0 deletions src/code_dom/functiondeclaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 30 additions & 19 deletions src/generators/gen_function_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -255,16 +260,22 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s
# <function name>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<const " + \
self_pointer_cast = "reinterpret_cast<const " + \
self_class_type.get_original_fully_qualified_name(include_leading_colons=True) + \
"*>(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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/generators/gen_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/modifiers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion src/modifiers/mod_flatten_class_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/modifiers/mod_mark_placement_constructor_structs.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions src/modifiers/mod_remove_heap_constructors_and_destructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 2fda825

Please sign in to comment.