-
Notifications
You must be signed in to change notification settings - Fork 730
Adding option to pass user data to allocator functions #1765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| Alloc_With_Allocator, | ||
| /* user allocator mode, allocate memory from user defined | ||
| malloc function with user data */ | ||
| Alloc_With_Allocator_With_Data, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should that be moved to the bottom as a last option? Otherwise this change breaks ABI backward compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, updated
1054b87 to
75fc972
Compare
| void *realloc_func; | ||
| void *free_func; | ||
| void *data; | ||
| } allocator_with_data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious if you also explored alternative approach where we have a single struct (allocator) with void* data inside ifdef? It might make the code less readable, but that possibly would simplify the implementation? That's just an idea though, not sure if you already thought about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say inside ifdef you mean add a build option?
I did consider just putting data in the allocator struct and then only using in the Alloc_With_Allocator_With_Data case but thought it might be clearer and more typesafe to do it like this - however I don't mind changing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I meant the build option, something like:
typedef union MemAllocOption {
struct {
void *heap_buf;
uint32_t heap_size;
} pool;
struct {
void *malloc_func;
void *realloc_func;
void *free_func;
#ifdef MEM_ALLOC_WITH_USER_DATA
void *user_data;
#endif
} allocator;
} MemAllocOption;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a look at this - Wamr is not using configure_file anywhere for definitions so adding an #ifdef in a header like this is a bit cumbersome - I can do it, but the host will need to also add the definition when including the header
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, didn't realize that.
core/iwasm/common/wasm_memory.c
Outdated
| static void (*free_func)(void *ptr) = NULL; | ||
|
|
||
| static void *allocator_data = NULL; | ||
| static void *(*malloc_func_with_data)(void *data, unsigned int size) = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also considered here reusing the existing variables but erasing the type and casting when the functions are called, but went this way - also happy to change that
|
@nchamzn, seems that in you host application, you pass the malloc_func/free_func callback (mode Alloc_With_Allocator) to runtime init, and want to track the allocation statistics, right? Not sure why not wrap the malloc_func/free_func, do the statistics in the wrapper APIs, and pass the wrapper APIs to runtime? For example: static data;
void *my_malloc(uint32 size) {
data->alloc_count++;
return engine_malloc(size);
}
void my_free(void *ptr) {
data->free_count++;
engine_free(ptr);
}
RuntimeInitArgs args;
memset(&args, 0, sizeof(RuntimeInitArgs));
args.mem_alloc_type = Alloc_With_Allocator;
args.mem_alloc_option.allocator.malloc_func = reinterpret_cast<void*>(my_malloc);
args.mem_alloc_option.allocator.realloc_func = reinterpret_cast<void*>(my_realloc);
args.mem_alloc_option.allocator.free_func = reinterpret_cast<void*>(my_free);
// ...
auto engine = wasm_engine_new_with_args(args.mem_alloc_type, &args.mem_alloc_option);Had better not extend the runtime option, if it is unnecessary. |
|
@wenyongh I agree better to not modify the API, and actually in its current design, where the engine itself and allocation functions are singletons, there's no much benefit of having user data, because it can be a global variable instead (like you suggested in your comment). Having said that, I wonder if we should start working towards having a concept of contexts instead of singletons/global objects so it's possible to embed multiple VMs in a single process? For example:
I presume moving away from global objects would require much larger refactoring and probably needs more through analysis; also, I'm aware I don't know have all the data behind decisions that were made in WAMR a long time ago, and there might be good reasons to keep it as it is. I've added this topic to the next TSC meeting agenda to discuss this topic with the others and gather their feedback. |
|
The suggestion is how we have implemented this at the moment but I see it as unnecessarily complex and means we have to compromise on design/encapsulation of the embedding application - not passing user data forces the embedding application to use a singleton/static even though it shouldn't be required. I already have a singleton For what it's worth, this pattern is used by all other Vms I've embedded before: |
1a7badf to
59a0bbc
Compare
@loganek Per my understanding, your mainly idea is to create runtime/vm context multiple times to eliminate the singletons/global objects in wasm_memory.c and some other places, and for each wasm_runtime_init/full_init, we return a runtime context instead, right? By this way we can custom the individual behavior for each runtime context. My concern is that this would greatly change the runtime APIs and cause incompatibility with old versions, for example, for wasm_runtime_load/wasm_runtime_instantiate and some other APIs, we should add runtime context as a new argument. And not sure whether it is necessary? Since we have already supported created multiple wasm module/module_inst in a thread. Or do we just want to support using different memory allocator for a module/module_inst? That seems easier but should also require lots of changes. |
| #ifdef MEM_ALLOC_WITH_USER_DATA | ||
| void *user_data; | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not add the macro control in the exported header files, since the host embedder may use the header file directly, link with the built vm library file and don't add macro to build the host application. Normally we use cmake variables or C macros to build the library, but we don't add extra macro control in host application for the wasm runtime exported header file.
Prefer not to add the macro control, just add the user_data field and add comments, and set it for wasm_runtime_init if the runtime is built with user data enabled. And ignore it if runtime is built without user data enabled.
Refer to: https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/core/iwasm/include/wasm_export.h#L140-L148
For these fields, we don't add macro to control them either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I mentioned this here. If you are happy with this being always defined then that is much easier. Thanks for the link, that helps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered using CMake configure_file by the way? Then the header could include config.h and this would be configured by cmake to have the correct definitions defined depending on the build configuration
core/iwasm/common/wasm_c_api.c
Outdated
| opts->allocator.free_func; | ||
| init_args.mem_alloc_option.allocator.realloc_func = | ||
| opts->allocator.realloc_func; | ||
| #ifdef MEM_ALLOC_WITH_USER_DATA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally we use #if MEM_ALLOC_WITH_USER_DATA != 0 and #if MEM_ALLOC_WITH_USER_DATA == 0 instead of #ifdef MEM_ALLOC_WITH_USER_DATA and #ifndef MEM_ALLOC_WITH_USER_DATA.
And add a macro definition in core/config.h:
#ifndef MEM_ALLOC_WITH_USER_DATA
#define MEM_ALLOC_WITH_USER_DATA 0
#endif
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, is there any reason (other than historical) for that pattern? I saw it's more common to use ifndef/ifdef pattern and the config file have all possible options listed, but commented if not used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the ifndef/ifdef pattern is also very common, but it may be not very convenience to use the config file: if it isn't created and all the configurations are controlled by makefile, it may be not easy to learn all the configs in the makefile. And if the config header file is used, (1) if it is auto generated e.g. autoconf.h, it may be under the output build folder and not easy to find it, (2) if it is one file of the project and all the configs are listed, then as you mentioned, if we want to enable/disable one config, we may need to uncomment/comment the macro manually or by makefile, just not so convenience as #if xxx != 0/#if xxx == 0 pattern with which normally we don't need to modify the config file.
Yes, I thought of having a context per runtime. And yes, I agree that will be a lot of changes, however, none of that should at least affect
No, I didn't think of having per-module allocator, although if there's a need for that, and it doesn't cause any risk, we might consider that in the design as well. As I said, I agree this is a complex change and we might not eventually want to do that if there's too much risk in having that; I'd suggest let's discuss it first before making any further steps. |
59a0bbc to
cdefe96
Compare
|
Had to make some more changes because when the build definition is set, the function type is different which leads to some issues when the build definition is set and the user decides to use the system allocator. I resolved these issues by calling the system allocator functions (os_malloc etc) directly when using the system allocator option. |
cdefe96 to
311181b
Compare
311181b to
e4b9a8c
Compare
…nce#1765) Add an option to pass user data to the allocator functions. It is common to do this so that the host embedder can pass a struct as user data and access that struct from the allocator, which gives the host embedder the ability to do things such as track allocation statistics within the allocator. Compile with `cmake -DWASM_MEM_ALLOC_WITH_USER_DATA=1` to enable the option, and the allocator functions provided by the host embedder should be like below (an extra argument `data` is added): void *malloc(void *data, uint32 size) { .. } void *realloc(void *data, uint32 size) { .. } void free(void *data, void *ptr) { .. } Signed-off-by: Andrew Chambers <[email protected]>
Add an option to pass user data to the allocator functions. It is common to
do this so that the host embedder can pass a struct as user data and access
that struct from the allocator, which gives the host embedder the ability to
do things such as track allocation statistics within the allocator.
Compile with `cmake -DWASM_MEM_ALLOC_WITH_USER_DATA=1` to enable
the option, and the allocator functions provided by the host embedder should
be like below (an extra argument `data` is added):
void *malloc(void *data, uint32 size) { .. }
void *realloc(void *data, uint32 size) { .. }
void free(void *data, void *ptr) { .. }
Signed-off-by: Andrew Chambers <[email protected]>
Add an option to pass user data to the allocator functions. It is common to
do this so that the host embedder can pass a struct as user data and access
that struct from the allocator, which gives the host embedder the ability to
do things such as track allocation statistics within the allocator.
Compile with `cmake -DWASM_MEM_ALLOC_WITH_USER_DATA=1` to enable
the option, and the allocator functions provided by the host embedder should
be like below (an extra argument `data` is added):
void *malloc(void *data, uint32 size) { .. }
void *realloc(void *data, uint32 size) { .. }
void free(void *data, void *ptr) { .. }
Signed-off-by: Andrew Chambers <[email protected]>
…nce#1765) Add an option to pass user data to the allocator functions. It is common to do this so that the host embedder can pass a struct as user data and access that struct from the allocator, which gives the host embedder the ability to do things such as track allocation statistics within the allocator. Compile with `cmake -DWASM_MEM_ALLOC_WITH_USER_DATA=1` to enable the option, and the allocator functions provided by the host embedder should be like below (an extra argument `data` is added): void *malloc(void *data, uint32 size) { .. } void *realloc(void *data, uint32 size) { .. } void free(void *data, void *ptr) { .. } Signed-off-by: Andrew Chambers <[email protected]>
…nce#1765) Add an option to pass user data to the allocator functions. It is common to do this so that the host embedder can pass a struct as user data and access that struct from the allocator, which gives the host embedder the ability to do things such as track allocation statistics within the allocator. Compile with `cmake -DWASM_MEM_ALLOC_WITH_USER_DATA=1` to enable the option, and the allocator functions provided by the host embedder should be like below (an extra argument `data` is added): void *malloc(void *data, uint32 size) { .. } void *realloc(void *data, uint32 size) { .. } void free(void *data, void *ptr) { .. } Signed-off-by: Andrew Chambers <[email protected]>
Adds an option to pass user data to the allocator functions. It's common to do this so that if I am embedding Wamr in to my application then I can pass a struct as user data and access that struct from the allocator which gives me the ability to do things such as track allocation statistics within my allocator.
Notes
MemAllocOptionandmem_alloc_type_tto a new header, I don't know if there was a reason they were duplicated.Example usage: