Replies: 6 comments 16 replies
-
cc @gingerBill @Kelimion @laytan need some feedback. |
Beta Was this translation helpful? Give feedback.
-
Off the top of my head, since I also have been looking at this for a "stupid pet project":
|
Beta Was this translation helpful? Give feedback.
-
I disagree, enforcing Odin/base/runtime/internal.odin Line 176 in e72d0ba Even .Alloc could be implemented on a layer on top by first querying if it is supported and then otherwise doing .Alloc_Non_Zeroed and zeroing it.
An additional thing we should do if we enforce a feature, is not allowing it to be queried (because it is enforced to be there).
Also don't think this is needed because all the actual resizes usually go through that function I linked which enforces those things. Although it may be good to also have the right behaviour when you just call the allocator function directly, but it does seem like the same logic in two places.
This goes against the ZII that we go for in Odin, + currently the default temp allocator being initialised only on first use saves that first memory block from being allocated for programs that don't use it.
I would make that Additionally you should probably define what happens in .Resize when old_size is <= 0 too. Another tricky thing with .Resize without a correct old_size, is that you can't just allocate a new region, copy, and free the old region (because you don't know how much to copy). |
Beta Was this translation helpful? Give feedback.
-
Something to mention is that @Feoramund did this sort of thing with the io.Stream interface a bit ago here: #4112 A test suite like that is something we should have for this too. |
Beta Was this translation helpful? Give feedback.
-
There was an issue recently (#4195) demonstrating the |
Beta Was this translation helpful? Give feedback.
-
We also need to define whether |
Beta Was this translation helpful? Give feedback.
-
Tangentially related: #957
Motivation
For the past couple days I've been cooking up a PR that documents the
core:mem
package as a whole and fixes the issues with implementations of various allocators I could find. Upon analysing the source code for the existing implementation of allocators I've identified a few issues related to consistency of implementation between different allocators. Excluding obvious bugs we have the following consistency problems:1. Behavior upon calling allocator procedure with uninitialized data.
This affects the following allocators: Arena, Scratch, Stack, Small stack, Dynamic Arena (Also known as
Dynamic_Pool
), Buddy allocator (basically all of them). The following behaviors are currently present inmem
package (links point to relevant source code lines in themaster
branch):context.allocator
.Invalid_Argument
.Invalid_Argument
There is no clear rule made for whether an uninitialized allocator should return an
.Invalid_Argument
error or panic, where as I would argue, calling an allocator function without an initialization of allocator's backing storage should be either an panic or a manifestation of the rule of "making zero value useful", i.e. to initialize the uninitialized allocator upon first allocation request.2. Changing the allocation's alignment
Someone happened to ask recently over discord, whether it is possible to use
realloc
to change allocation's alignment. The first answer they received was that it technically should be possible, but in reality some allocators do not handle this case or the behavior is unreliable at best.For example, the scratch allocator does not handle this case due to an early return:
Odin/core/mem/allocators.odin
Lines 264 to 278 in e72d0ba
Same with stack and small stack:
Odin/core/mem/allocators.odin
Lines 431 to 433 in e72d0ba
All allocators that use
default_resize_bytes_align
(arena, stack, small )also do not handle this case ifsize == old_size
, due to an early return:Odin/core/mem/alloc.odin
Lines 240 to 242 in e72d0ba
3. Lack of documentation / trust in the interface
Currently there is no documentation surrounding the Allocator API. As such there can be no reasonable expectations as to what is and what isn't allowed on the other side of the interface, as it should be evident from the two examples above.
For example it is not made clear that a lack of error in the
alloc()
procedure does not mean that the allocated memory can be dereferenced, because the nil allocator returnsnil
pointers without an error. It is not made clear thatResize
with specific arguments must act likeFree
orAlloc
. There's no one to say thatquery_features
can't return the.Mode_Not_Implemented
error.These are purely issues born out of the lack of documentation. And although many of us would consider these things obvious, or at least, obvious by reading the source code, the documentation needs to be a little bit more rigorous when it comes to saying what is and isn't allowed/expected from an allocator implementation.
The proposal
As such this is a proposal for the allocator interface that attempts to capture and expand upon existing implementation. If implemented, it would affect the documentation and implementation of specific allocators, which might be implemented as part of #4209 or otherwise.
Implemented modes
At the bare minimum, the allocator must support the following modes and is not allowed to output the
.Mode_Not_Implemented
error:Alloc
,Alloc_Non_Zeroed
Resize
,Resize_Non_Zeroed
Query_Features
If the allocator does not have a meaningful way to resize an allocation, the default implementation should be used (
default_resize_bytes_align
). Allocators not designed for allocating memory (such as the nil allocator and the panic allocator) are excempt from this rule.The modes available for querying:
Free
,Free_All
,Query_Info
. Other modes will be unavailable for querying on an API level.Alloc
will not be an allocator "feature", but rather it's "function".Initialization
The allocators must be initialized before the first call to any of the allocation functions. Allocators that use a backing allocator to store data, or do not require a backing buffer (such as nil allocator, panic allocator, scratch allocator) may be excempt from the rule. Not initializing the allocator before use should result in a panic.
Non-zeroed modes
The non-zeroed modes must not explicitly zero-initialize any new memory. This requirement is currently violated by a few allocators. If the allocator uses a backing allocator, it must also ask the backing allocator to allocate non-zeroed memory during non-zeroed operations.
Resize implementation
The following parameters are used in a resize operation:
The resize operation must behave as follows (order of checks applies):
old_memory
isnil
, it should behave just like `alloc(new_size, alignment)new_size
is0
, it should behave just likefree(old_memory, old_size)
new_size
and alignmentalignment
.Notes:
old_memory
isnil
andnew_size
is0
,realloc
should be a no-op.old_size
parameter is less than or equal to0
, the allocator assumes an allocation with unknown size. If the allocator requires a size to reallocate an allocation,.Invalid_Argument
error must be returned.old_size
parameter is greater than0
, the allocator has sufficient metadata to tell the allocation's size from a pointer, and the two sizes are not equal, the.Invalid_Argument
error must be returned.old_memory
is not aligned on a boundary specified byalignment
, the buffer needs to be moved in such a way that it is aligned.Alloc implementation
The following parameters are used in an alloc operations:
The following rules apply:
new_size
is0
, the operation is a no-op..Invalid_Argument
error should be returned..Out_Of_Memory
error is returned.Free implementation
The following parameters are used in a free operation:
The following rules apply:
old_memory
isnil
, the operation is a no-op.old_memory
is outside of allocator's backing storage, the.Invalid_Pointer
error should be returned.old_memory
is not a valid allocation, the.Invalid_Pointer
error should be returned.old_size
parameter is less than or equal to0
, the allocator assumes an allocation with unknown size. If the allocator requires a size to free an allocation,.Invalid_Argument
error must be returned.old_size
parameter is greater than0
, the allocator has sufficient metadata to tell the allocation's size from a pointer, and the two sizes are not equal, the.Invalid_Argument
error must be returned.Affected functionality
default_resize_bytes_align
will see a change where it would check for whether a pointer is aligned to the new alignment, and if not, it will reallocate the memory.Pretty sure that's it. This proposal tries to capture existing implementation without having to reimplement anything. It is the as strict as I can define the allocation interface without having to change too much.
The linked discussion #957 has an interesting suggestion that removes the need for alloc and free modes in the allocation procedure. However in practice, it doesn't make a difference, especially with the changes from #4209, where the allocation procedure does a switch statement and calls respective functions.
Beta Was this translation helpful? Give feedback.
All reactions