فهرست منبع

Add GC finalizer mechanism (#2325)

Allow to add a finalizer callback function for a GC anyref object,
which will be called when reclaimed.
Xu Jun 2 سال پیش
والد
کامیت
254bbddfff

+ 17 - 0
core/iwasm/common/gc/gc_common.c

@@ -835,3 +835,20 @@ wasm_runtime_get_gc_heap_handle(WASMModuleInstanceCommon *module_inst)
 #endif
     return NULL;
 }
+
+bool
+wasm_runtime_get_wasm_object_extra_info_flag(WASMObjectRef obj)
+{
+    return obj->header & WASM_OBJ_EXTRA_INFO_FLAG;
+}
+
+void
+wasm_runtime_set_wasm_object_extra_info_flag(WASMObjectRef obj, bool set)
+{
+    if (set) {
+        obj->header |= WASM_OBJ_EXTRA_INFO_FLAG;
+    }
+    else {
+        obj->header &= ~WASM_OBJ_EXTRA_INFO_FLAG;
+    }
+}

+ 16 - 0
core/iwasm/common/gc/gc_object.c

@@ -731,3 +731,19 @@ wasm_object_get_ref_list(WASMObjectRef obj, bool *p_is_compact_mode,
         return false;
     }
 }
+
+bool
+wasm_obj_set_gc_finalizer(wasm_exec_env_t exec_env, const wasm_obj_t obj,
+                          wasm_obj_finalizer_t cb, void *data)
+{
+    void *heap_handle = get_gc_heap_handle(exec_env);
+    return mem_allocator_set_gc_finalizer(heap_handle, obj, (gc_finalizer_t)cb,
+                                          data);
+}
+
+void
+wasm_obj_unset_gc_finalizer(wasm_exec_env_t exec_env, void *obj)
+{
+    void *heap_handle = get_gc_heap_handle(exec_env);
+    mem_allocator_unset_gc_finalizer(heap_handle, obj);
+}

+ 3 - 0
core/iwasm/common/gc/gc_object.h

@@ -18,6 +18,7 @@ extern "C" {
  * mark the object:
  *   bits[0] is 1: the object is an externref object
  *   bits[1] is 1: the object is an anyref object
+ *   bits[2] is 1: the object has extra information
  *   if both bits[0] and bits[1] are 0, then this object header must
  *   be a pointer of a WASMRttType, denotes that the object is a
  *   struct object, or an array object, or a function object
@@ -30,6 +31,8 @@ typedef uintptr_t WASMObjectHeader;
 
 #define WASM_OBJ_ANYREF_OBJ_FLAG (((uintptr_t)1) << 1)
 
+#define WASM_OBJ_EXTRA_INFO_FLAG (((uintptr_t)1) << 2)
+
 /* Representation of WASM objects */
 typedef struct WASMObject {
     WASMObjectHeader header;

+ 26 - 0
core/iwasm/include/gc_export.h

@@ -135,6 +135,8 @@ typedef struct WASMArrayObject *wasm_array_obj_t;
 typedef struct WASMFuncObject *wasm_func_obj_t;
 typedef uintptr_t wasm_i31_obj_t;
 
+typedef void (*wasm_obj_finalizer_t)(const wasm_obj_t obj, void *data);
+
 /* Defined type related operations */
 
 /**
@@ -898,6 +900,30 @@ wasm_runtime_pop_local_object_ref(wasm_exec_env_t exec_env);
 WASM_RUNTIME_API_EXTERN void
 wasm_runtime_pop_local_object_refs(wasm_exec_env_t exec_env, uint32_t n);
 
+/**
+ * Set finalizer to the given object, if another finalizer is set to the same
+ * object, the previous one will be cancelled
+ *
+ * @param exec_env the execution environment
+ * @param obj object to set finalizer
+ * @param cb finalizer function to be called before this object is freed
+ * @param data custom data to be passed to finalizer function
+ *
+ * @return true if success, false otherwise
+ */
+bool
+wasm_obj_set_gc_finalizer(wasm_exec_env_t exec_env, const wasm_obj_t obj,
+                          wasm_obj_finalizer_t cb, void *data);
+
+/**
+ * Unset finalizer to the given object
+ *
+ * @param exec_env the execution environment
+ * @param obj object to unset finalizer
+ */
+void
+wasm_obj_unset_gc_finalizer(wasm_exec_env_t exec_env, void *obj);
+
 #ifdef __cplusplus
 }
 #endif

+ 153 - 0
core/shared/mem-alloc/ems/ems_alloc.c

@@ -948,3 +948,156 @@ gci_dump(gc_heap_t *heap)
         heap->is_heap_corrupted = true;
     }
 }
+
+#if WASM_ENABLE_GC != 0
+extra_info_node_t *
+gc_search_extra_info_node(gc_handle_t handle, gc_object_t obj,
+                          gc_size_t *p_index)
+{
+    gc_heap_t *vheap = (gc_heap_t *)handle;
+    int32 low = 0, high = vheap->extra_info_node_cnt - 1;
+    int32 mid;
+    extra_info_node_t *node;
+
+    if (!vheap->extra_info_nodes)
+        return NULL;
+
+    while (low < high) {
+        mid = (low + high) / 2;
+        node = vheap->extra_info_nodes[mid];
+
+        if (obj == node->obj) {
+            if (p_index) {
+                *p_index = mid;
+            }
+            return node;
+        }
+        else if (obj < node->obj) {
+            high = mid - 1;
+        }
+        else {
+            low = mid + 1;
+        }
+    }
+
+    if (p_index) {
+        *p_index = low;
+    }
+    return NULL;
+}
+
+static bool
+insert_extra_info_node(gc_heap_t *vheap, extra_info_node_t *node)
+{
+    gc_size_t index;
+    extra_info_node_t *orig_node;
+
+    if (!vheap->extra_info_nodes) {
+        vheap->extra_info_nodes = vheap->extra_info_normal_nodes;
+        vheap->extra_info_node_capacity = sizeof(vheap->extra_info_normal_nodes)
+                                          / sizeof(extra_info_node_t *);
+        vheap->extra_info_nodes[0] = node;
+        vheap->extra_info_node_cnt = 1;
+        return true;
+    }
+
+    /* extend array */
+    if (vheap->extra_info_node_cnt == vheap->extra_info_node_capacity) {
+        extra_info_node_t **new_nodes = NULL;
+        gc_size_t new_capacity = vheap->extra_info_node_capacity * 3 / 2;
+        gc_size_t total_size = sizeof(extra_info_node_t *) * new_capacity;
+
+        new_nodes = (extra_info_node_t **)BH_MALLOC(total_size);
+        if (!new_nodes) {
+            LOG_ERROR("alloc extra info nodes failed");
+            return false;
+        }
+
+        bh_memcpy_s(new_nodes, total_size, vheap->extra_info_nodes,
+                    sizeof(extra_info_node_t) * vheap->extra_info_node_cnt);
+        if (vheap->extra_info_nodes != vheap->extra_info_normal_nodes) {
+            BH_FREE(vheap->extra_info_nodes);
+        }
+
+        vheap->extra_info_nodes = new_nodes;
+        vheap->extra_info_node_capacity = new_capacity;
+    }
+
+    orig_node = gc_search_extra_info_node(vheap, node->obj, &index);
+    if (orig_node) {
+        /* replace the old node */
+        vheap->extra_info_nodes[index] = node;
+        BH_FREE(orig_node);
+    }
+    else {
+        bh_memmove_s(vheap->extra_info_nodes + index + 1,
+                     (vheap->extra_info_node_capacity - index - 1)
+                         * sizeof(extra_info_node_t *),
+                     vheap->extra_info_nodes + index,
+                     (vheap->extra_info_node_cnt - index)
+                         * sizeof(extra_info_node_t *));
+        vheap->extra_info_nodes[index] = node;
+        vheap->extra_info_node_cnt += 1;
+    }
+
+    return true;
+}
+
+bool
+gc_set_finalizer(gc_handle_t handle, gc_object_t obj, gc_finalizer_t cb,
+                 void *data)
+{
+    extra_info_node_t *node = NULL;
+    gc_heap_t *vheap = (gc_heap_t *)handle;
+
+    node = (extra_info_node_t *)BH_MALLOC(sizeof(extra_info_node_t));
+
+    if (!node) {
+        LOG_ERROR("alloc a new extra info node failed");
+        return GC_FALSE;
+    }
+    memset(node, 0, sizeof(extra_info_node_t));
+
+    node->finalizer = cb;
+    node->obj = obj;
+    node->data = data;
+
+    LOCK_HEAP(vheap);
+    if (!insert_extra_info_node(vheap, node)) {
+        BH_FREE(node);
+        UNLOCK_HEAP(vheap);
+        return GC_FALSE;
+    }
+    UNLOCK_HEAP(vheap);
+
+    gct_vm_set_extra_info_flag(obj, true);
+    return GC_TRUE;
+}
+
+void
+gc_unset_finalizer(gc_handle_t handle, gc_object_t obj)
+{
+    gc_size_t index;
+    gc_heap_t *vheap = (gc_heap_t *)handle;
+    extra_info_node_t *node;
+
+    LOCK_HEAP(vheap);
+    node = gc_search_extra_info_node(vheap, obj, &index);
+
+    if (!node) {
+        UNLOCK_HEAP(vheap);
+        return;
+    }
+
+    BH_FREE(node);
+    bh_memmove_s(
+        vheap->extra_info_nodes + index,
+        (vheap->extra_info_node_capacity - index) * sizeof(extra_info_node_t *),
+        vheap->extra_info_nodes + index + 1,
+        (vheap->extra_info_node_cnt - index) * sizeof(extra_info_node_t *));
+    vheap->extra_info_node_cnt -= 1;
+    UNLOCK_HEAP(vheap);
+
+    gct_vm_set_extra_info_flag(obj, false);
+}
+#endif

+ 13 - 0
core/shared/mem-alloc/ems/ems_gc.c

@@ -2,6 +2,7 @@
  * Copyright (C) 2022 Tencent Corporation.  All rights reserved.
  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  */
+#include "ems/ems_gc.h"
 #include "ems_gc_internal.h"
 
 #define GB (1 << 30UL)
@@ -99,6 +100,18 @@ sweep_instance_heap(gc_heap_t *heap)
             /* merge previous free areas with current one */
             if (!last)
                 last = cur;
+
+            if (ut == HMU_WO) {
+                /* Invoke registered finalizer */
+                gc_object_t cur_obj = hmu_to_obj(cur);
+                if (gct_vm_get_extra_info_flag(cur_obj)) {
+                    extra_info_node_t *node = gc_search_extra_info_node(
+                        (gc_handle_t)heap, cur_obj, NULL);
+                    bh_assert(node);
+                    node->finalizer(node->obj, node->data);
+                    gc_unset_finalizer((gc_handle_t)heap, cur_obj);
+                }
+            }
         }
         else {
             /* current block is still live */

+ 47 - 0
core/shared/mem-alloc/ems/ems_gc.h

@@ -70,6 +70,19 @@ typedef enum {
     GC_STAT_MAX
 } GC_STAT_INDEX;
 
+typedef void (*gc_finalizer_t)(void *obj, void *data);
+
+#ifndef EXTRA_INFO_NORMAL_NODE_CNT
+#define EXTRA_INFO_NORMAL_NODE_CNT 32
+#endif
+
+/* extra information attached to specific object */
+typedef struct extra_info_node {
+    gc_object_t obj;
+    gc_finalizer_t finalizer;
+    void *data;
+} extra_info_node_t;
+
 /**
  * GC initialization from a buffer, which is separated into
  * two parts: the beginning of the buffer is used to create
@@ -186,6 +199,34 @@ gc_alloc_wo(void *heap, gc_size_t size);
 
 void
 gc_free_wo(void *vheap, void *ptr);
+
+extra_info_node_t *
+gc_search_extra_info_node(gc_handle_t handle, gc_object_t obj,
+                          gc_size_t *p_index);
+
+/**
+ * Set finalizer to the given object, if another finalizer is set to the same
+ * object, the previous one will be cancelled
+ *
+ * @param handle handle of the heap
+ * @param obj object to set finalizer
+ * @param cb finalizer function to be called before this object is freed
+ * @param data custom data to be passed to finalizer function
+ *
+ * @return true if success, false otherwise
+ */
+bool
+gc_set_finalizer(gc_handle_t handle, gc_object_t obj, gc_finalizer_t cb,
+                 void *data);
+
+/**
+ * Unset finalizer to the given object
+ *
+ * @param handle handle of the heap
+ * @param obj object to unset finalizer
+ */
+void
+gc_unset_finalizer(gc_handle_t handle, gc_object_t obj);
 #endif
 
 #else /* else of BH_ENABLE_GC_VERIFY */
@@ -258,6 +299,12 @@ wasm_runtime_get_wasm_object_ref_list(gc_object_t obj, bool *p_is_compact_mode,
                                       gc_uint16 **p_ref_list,
                                       gc_uint32 *p_ref_start_offset);
 
+bool
+wasm_runtime_get_wasm_object_extra_info_flag(gc_object_t obj);
+
+void
+wasm_runtime_set_wasm_object_extra_info_flag(gc_object_t obj, bool set);
+
 void
 wasm_runtime_gc_prepare();
 

+ 12 - 0
core/shared/mem-alloc/ems/ems_gc_internal.h

@@ -311,6 +311,16 @@ typedef struct gc_heap_struct {
     gc_size_t gc_threshold_factor;
     gc_size_t total_gc_count;
     gc_size_t total_gc_time;
+    /* Usually there won't be too many extra info node, so we try to use a fixed
+     * array to store them, if the fixed array don't have enough space to store
+     * the nodes, a new space will be allocated from heap */
+    extra_info_node_t *extra_info_normal_nodes[EXTRA_INFO_NORMAL_NODE_CNT];
+    /* Used to store extra information such as finalizer for specified nodes, we
+     * introduce a seperate space to store these information so only nodes who
+     * really require extra information will occupy additional memory spaces. */
+    extra_info_node_t **extra_info_nodes;
+    gc_size_t extra_info_node_cnt;
+    gc_size_t extra_info_node_capacity;
 #endif
 #if GC_STAT_DATA != 0
     gc_uint64 total_size_allocated;
@@ -337,6 +347,8 @@ gc_update_threshold(gc_heap_t *heap)
 #define gct_vm_gc_finished wasm_runtime_gc_finalize
 #define gct_vm_begin_rootset_enumeration wasm_runtime_traverse_gc_rootset
 #define gct_vm_get_wasm_object_ref_list wasm_runtime_get_wasm_object_ref_list
+#define gct_vm_get_extra_info_flag wasm_runtime_get_wasm_object_extra_info_flag
+#define gct_vm_set_extra_info_flag wasm_runtime_set_wasm_object_extra_info_flag
 
 #endif /* end of WAMS_ENABLE_GC != 0 */
 

+ 16 - 0
core/shared/mem-alloc/mem_alloc.c

@@ -4,6 +4,7 @@
  */
 
 #include "mem_alloc.h"
+#include <stdbool.h>
 
 #if DEFAULT_MEM_ALLOCATOR == MEM_ALLOCATOR_EMS
 
@@ -113,6 +114,21 @@ mem_allocator_get_alloc_info(mem_allocator_t allocator, void *mem_alloc_info)
     return true;
 }
 
+#if WASM_ENABLE_GC != 0
+bool
+mem_allocator_set_gc_finalizer(mem_allocator_t allocator, void *obj,
+                               gc_finalizer_t cb, void *data)
+{
+    return gc_set_finalizer((gc_handle_t)allocator, (gc_object_t)obj, cb, data);
+}
+
+void
+mem_allocator_unset_gc_finalizer(mem_allocator_t allocator, void *obj)
+{
+    gc_unset_finalizer((gc_handle_t)allocator, (gc_object_t)obj);
+}
+#endif
+
 #else /* else of DEFAULT_MEM_ALLOCATOR */
 
 #include "tlsf/tlsf.h"

+ 9 - 0
core/shared/mem-alloc/mem_alloc.h

@@ -17,6 +17,8 @@ extern "C" {
 
 typedef void *mem_allocator_t;
 
+typedef void (*gc_finalizer_t)(void *obj, void *data);
+
 mem_allocator_t
 mem_allocator_create(void *mem, uint32_t size);
 
@@ -67,6 +69,13 @@ mem_allocator_enable_gc_reclaim(mem_allocator_t allocator, void *cluster);
 
 int
 mem_allocator_add_root(mem_allocator_t allocator, WASMObjectRef obj);
+
+bool
+mem_allocator_set_gc_finalizer(mem_allocator_t allocator, void *obj,
+                               gc_finalizer_t cb, void *data);
+
+void
+mem_allocator_unset_gc_finalizer(mem_allocator_t allocator, void *obj);
 #endif /* end of WASM_ENABLE_GC != 0 */
 
 bool