Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR implements a new trait logic. New approach to handle traits.
This new approach focuses on increasing memory efficiency and improving performance. It achieves this with some important differences compared to the current trait implementation.
About Current Implementation
The current implementation uses a relatively efficient method of handling traits. But here we are.
Some improvements were possible.
According to the approach of the current implementation, each trait instance should have 4 different data;
Allocation is a pointer to the data itself that the trait stores. Managed by GC. The current implementation handles this well. If a pointer that is already traced by the GC is passed to the trait, for example a smart pointer, the trait uses it by directly referencing that smart pointer rather than making a new allocation. This helps reduce memory allocations and increases efficiency.
Traits may take both a smart pointer or a normal instance for supported types. Accordingly, the deallocation method and type comparison also vary. If separate code was generated for both forms with and without smart pointers, this could significantly increase the size and compilation time of the executable. Since it does not contribute much to the runtime cost, it stores whether the stored data is a smart pointer or not with a simple boolean flag. In this way, it is sufficient to generate a single handler for each form of trait type.
Allocation pointer is stored without any explicit type as required by dynamic programming. Therefore, when it comes to deallocation, it needs to be handled in a special function. The deallocator function is not handled by the compiler. C++'s generic types and compiler are relied upon. This is clearly bad. Because the generated code requires the trait data type to be transferred to C++ code as a generic type. This leads to the creation of a new instance of the trait container class for each trait. It's clearly something that negatively impacts executable size, and this is all to enable a single generic type to create a deallocator function. It should be replaced.
The compiler generates a map with an array created on the stack for each trait type. In order to know which wrapper in which array element it should point to at runtime, it must add the offset data of the required type to the trait container class. This isn't that bad, but it does involve unnecessary overhead: a pointer arithmetic for the required type is executed every time trait is used. This is simply like
array_ptr + offset
.About New Implementation
The new approach is very similar to the current practice but includes some significant efficiency improvements.
According to the approach of the new implementation, each trait instance should have 3 different data;
No update. Efficient enough.
No update. Efficient enough.
The new approach maintains a general pointer and this pointer is not traced by the GC because it is guaranteed to always will point to static memory that will be available for the lifetime of the program. This pointer points directly to the type handler structure automatically created by the compiler. The only difference is that this handler structure now includes the deallocator function required for the type. The deallocator function is the first field of the structure. In this way, with a simple reinterpretation, the trait container can call the deallocator function when necessary. Each time trait is used, the type pointer is reinterpreted for the correct type, providing direct access to the required function, without the overhead of the previous pointer arithmetic. With this approach, there is no longer a need for separate type offset and deallocator function pointer datas.