Repository navigation
Conversation
ksh8281
left a comment
There was a problem hiding this comment.
Thanks for the PR!
I have a concern about potential memory leaks due to conservative GC scanning. Since Wasm globals write arbitrary raw numbers (i32, i64, f32, etc.), allocating them as uncollectable memory will lead to severe false pointer retention (leaks) as the GC scans the entire block conservatively.
Could you please update this to use a custom mark procedure (proc) to create a GC kind and only mark/scan valid pointer slots?
Also, please update gcutil to the latest branch (bdwgc_8_3_pre_260715) for the time being.
|
I would keep the GC object as malloced. Its value should be an union, which contains primitive values, or a pointer to a pointer for references. The referenced pointer is allocated by GC. |
|
@ksh8281 Do you mean |
|
@zherczeg |
|
A global type is known at allocation time, so the global type can tell that we have a primitive value or a pointer. A pointer is a pointer to a "vector", which has a single pointer value. So it is kind of a table with a single value, but this table is allocated by GC. This way globals keeps their current form except for references when GC is enabled. |
|
Another option is having a real "global vector" for references, which is stored in the Store. This vector has used and unused slots. Releasing globals is more complex that way. |
|
Is enough to use |
|
I am not sure I understand the question. The global object should remain mostly the same. It is a store object, and it cannot be on the webassembly heap, so it does not need to be handled by GC. |
|
Regarding the Global allocation and how we should handle the pointer tracking with bdwgc, here is my feedback on the proposed changes:
In this case, using GC_MALLOC_ATOMIC_UNCOLLECTABLE is the most appropriate choice to avoid unnecessary scanning. Case B: The internal content indeed contains pointers that the GC must traverse. We could go with typed GC (like the one linked by clover1213). However, since the kind value is very small here, there is practically no risk of conservative GC misinterpreting it as an arbitrary pointer. Therefore, if the Value is just a simple raw pointer, using GC_MALLOC_UNCOLLECTABLE should be more than enough.
Similar to how it is implemented in Escargot's CustomAllocator.cpp, we should write a custom proc that evaluates the object at runtime, determines if it behaves as a pointer, and explicitly notifies bdwgc (i.e., "this is a pointer") only when it is actually identified as one. Let me know what you think about this approach! |
|
Globals are not real objects, they are objects in Walrus only for practical reasons. They represent values that can be imported/exported by WebAssembly instances. They are stored in a Store, and they should have a reference counter, not just stored until the Store is destroyed. When a global object is created, everything is known about it, including its type. The type is important for importing/exporting checks. A global object could be allocated by GC_MALLOC_UNCOLLECTABLE / GC_FREE, if it contains a reference, or regular malloc/free if it does not. Or it can have a pointer to a single reference. The latter increases the runtime a bit. |
|
For the code consistency, GCArray already uses I think if the type is constant, this kind of method will prevent GC confusion. |
|
Yes, that makes sense. Since a Global’s type is known at creation and does not change, we can choose the allocation strategy based on its type. My earlier suggestion about a custom marking procedure is unnecessary in this case. The same type-based approach as GCArray applies, but the allocation must also respect the Global’s explicitly managed lifetime. Reference globals can use |
| Global* Global::createGlobal(Store* store, const Value& value, const MutableType& type) | ||
| { | ||
| #ifdef ENABLE_GC | ||
| void* mem = Value::isRefType(type.type()) ? GC_MALLOC_UNCOLLECTABLE(sizeof(Global)) : GC_MALLOC_ATOMIC_UNCOLLECTABLE(sizeof(Global)); |
There was a problem hiding this comment.
Is GC_MALLOC_ATOMIC_UNCOLLECTABLE is a regular malloc? Is it recommended instead of a malloc?
There was a problem hiding this comment.
GC_MALLOC_ATOMIC_UNCOLLECTABLE is uncollectable GC allocation.
Issue #502 is caused by early collection of global object that originally written in new allocation.
Marking uncollctable keeps object safe.
There was a problem hiding this comment.
That is clear to me. My question is GC_MALLOC_ATOMIC_UNCOLLECTABLE is better than regular malloc? Why?
There was a problem hiding this comment.
Using regular free(or delete) is possible, but it needs separate memory deallocation path into two ways.
I just think that is not a smooth way for consistency.
There was a problem hiding this comment.
Ok. I hope these blocks are not difficult to process for GC.
|
|
||
| #ifdef ENABLE_GC | ||
| void operator delete(void* ptr); | ||
| #endif |
There was a problem hiding this comment.
Longer term task: Store objects should be freed only by Store, so we might need to make their free operations private. And we should also add a reference counting.
| (drop (array.new_default $a (i32.const 65536))) | ||
| (local.set $i (i32.add (local.get $i) (i32.const 1))) | ||
| (br_if $l (i32.lt_u (local.get $i) (i32.const 3000)))) | ||
| (struct.get $s 0 (global.get $g)))) |
There was a problem hiding this comment.
Do you need this large constant? Is this a long running test?
Maybe we should add a walrus_gc builtin rather than these large consts.
There was a problem hiding this comment.
The manual call of builtin seems makes false positive. (I'm currently testing it. Just added builtin function.) We need mess up the memory to reproduce this bug.
There was a problem hiding this comment.
That is very strange. You need a short loop to remove the reference from the stack, but the referenced object should go away.
There was a problem hiding this comment.
@zherczeg
IMO this code is enough to test the correct GC for the global object.
In the loop, arrays are repeatedly allocated and dropped to trigger the GC.
Even more, an explicit GC call is invoked.
So, this code could confirm that GC tracking is correctly executed for the global object.
There was a problem hiding this comment.
Thank you for explaining. My only worry is that 305419896 might make the test long running, and there is no guarantee that GC is triggered depending on the available memory. So I prefer a forced GC. A small loop should be enough to clear the stack from the earlier references, and GC should free those objects. However, @makachanm said it does not work, and I asked why.
|
|
||
| #ifdef ENABLE_GC | ||
| #include "GCUtil.h" | ||
| #include <new> |
There was a problem hiding this comment.
Is this include <new> necessary?
009cbd2 to
b8b54c7
Compare
|
Do I have more to do for this patch? (Is there are something I missing about?) |
| ft, | ||
| [](ExecutionState& state, Value* argv, Value* result, void* data) { | ||
| #ifdef ENABLE_GC | ||
| GC_gcollect(); |
There was a problem hiding this comment.
Would you change this function to GC_gcollect_and_unmap();?
This gc function is more powerful because it additionally returns the reclaimed memory to OS
| [](ExecutionState& state, Value* argv, Value* result, void* data) { | ||
| }, | ||
| nullptr)); | ||
| } else if (import->fieldName() == "gc") { |
There was a problem hiding this comment.
I would prefer walrus_gc, because the others are spec test definitions, and if they introduce a gc in the future, it would be good to detect it. It is unlikely that they introduce walrus_gc.
zherczeg
left a comment
There was a problem hiding this comment.
LGTM. Only the loop of 305419896 is strange for me.
Fix #502.