QuickJS JSShape剖析

JSShape定义

JSShape在内存中的分布如下图
JSShape在内存中的分布如下图
代码定义如下:

#define JS_PROP_INITIAL_SIZE 2
#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */
#define JS_ARRAY_INITIAL_SIZE 2

typedef struct JSShapeProperty {
    uint32_t hash_next : 26; /* 0 if last in list */
    uint32_t flags : 6;   /* JS_PROP_XXX */
    JSAtom atom; /* JS_ATOM_NULL = free property entry */
} JSShapeProperty;

struct JSShape {
    /* 
       请注意读下面这句话:
       hash table of size hash_mask + 1 before the start of the
       structure (see prop_hash_end()).    
       
    */
    JSGCObjectHeader header;
    /* true if the shape is inserted in the shape hash table. If not,
       JSShape.hash is not valid */
    //如果它的值为true,说明它被插入了rt->shape_hash表中了,如果false,下面的hash是无效的
    uint8_t is_hashed;
    /* If true, the shape may have small array index properties 'n' with 0
       <= n <= 2^31-1. If false, the shape is guaranteed not to have
       small array index properties */
    uint8_t has_small_array_index;
    uint32_t hash; /* current hash value */
    uint32_t prop_hash_mask;//属性 hash_mask,用于计算属性的hash
    int prop_size; /* allocated properties 属性尺寸*/
    int prop_count; /* include deleted properties 属性大小*/
    int deleted_prop_count;//删除的属性大小
    JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list 在rt->shape_hash中的下一个JSShape */
    JSObject *proto;//对象的__proto__对象
    JSShapeProperty prop[0]; /* prop_size elements 属性数组*/
};


typedef enum {
    JS_GC_OBJ_TYPE_JS_OBJECT,
    JS_GC_OBJ_TYPE_FUNCTION_BYTECODE,
    JS_GC_OBJ_TYPE_SHAPE,
    JS_GC_OBJ_TYPE_VAR_REF,
    JS_GC_OBJ_TYPE_ASYNC_FUNCTION,
    JS_GC_OBJ_TYPE_JS_CONTEXT,
} JSGCObjectTypeEnum;

/* header for GC objects. GC objects are C data structures with a
   reference count that can reference other GC objects. JS Objects are
   a particular type of GC object. */
struct JSGCObjectHeader {
    int ref_count; /* must come first, 32-bit */
    JSGCObjectTypeEnum gc_obj_type : 4;
    uint8_t mark : 4; /* used by the GC */
    uint8_t dummy1; /* not used by the GC */
    uint16_t dummy2; /* not used by the GC */
    struct list_head link;
};

JSShape在代码中的定义如上。JSShape可以看作是JSObject的模板。JSObject的属性和原型对象信息都保存在JSShape中。每一个JSObject都持有一个JSShape,但是某一个JSShape可能被多个JSObject持有。每个JSShape被创建时,都将会计算它的hash,计算方法是调用 shape_initial_hash 函数。JSShape的hash和创建它时,它持有的proto对象有关系。所有的JSShape都存储在 rt->shape_hash 数组中。几个具有相同hash的JSShape组成了一个单向链表,它的 shape_hash_next 字段指向下一个具有相同hash的JSShape。JSShape对存储在其中的JSShapeProperty也进行了hash。目的是便于快速的寻找JSObject的属性。

JSShape 与 rt->shape_hash 的关系

JSShape 与 rt->shape_hash 的关系

一个JSShape创建之后,会被以它的hash为索引,存储在 rt->shape_hash 中。同时,为了解决hash碰撞问题,它使用了拉链法,为相同hash的JSShape创建了一个链表。具体实现就是为每一个JSShape设置一个 shape_hash_next 字段,它指向下一个具有相同hash的JSShape。

几个基础的函数

先看几个与JSShape相关的基础函数

init_shape_hash

// 初始化 rt->shape_hash
static int init_shape_hash(JSRuntime *rt)
{
    rt->shape_hash_bits = 4;   /* 16 shapes */
    rt->shape_hash_size = 1 << rt->shape_hash_bits;
    rt->shape_hash_count = 0;
    rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
                                   rt->shape_hash_size);
    if (!rt->shape_hash)
        return -1;
    return 0;
}

JSShape创建之后,被存储在 rt->shape_hash 中。该函数作用就是为 rt->shape_hash 分配内存,并初始化一些用于存储或标记JSShape的信息。

shape_hash

// 将两个数字想加然后乘以魔法数得到hash
/* same magic hash multiplier as the Linux kernel */
static uint32_t shape_hash(uint32_t h, uint32_t val)
{
    return (h + val) * 0x9e370001;
}

函数的作用就是根据两个数组计算JSShape的hash

shape_initial_hash

// 根据原型对象计算hash
// 原理就是将proto指针,作为一个普通的数字计算它的hash
static uint32_t shape_initial_hash(JSObject *proto)
{
    uint32_t h;
    // 将1和proto的地址想加然后乘以魔法数得到hash
    h = shape_hash(1, (uintptr_t)proto);
    // 如果proto的大小超过4字节,然后将proto指针的高32位与刚得到的hash重新计算一个新的hash
    if (sizeof(proto) > 4)
        h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32);
        
    return h;
}

函数的作用是根据JSShape持有的__proto__对象计算JSShape的初始的hash

get_shape_hash

/* truncate the shape hash to 'hash_bits' bits */
// 截断shape的hash成 "hash位"
static uint32_t get_shape_hash(uint32_t h, int hash_bits)
{
    return h >> (32 - hash_bits);
}

配合上面的 shape_initial_hash 函数一起使用,目的是将JSShape的hash截断,以使它的值不会超过指定的大小。

get_shape_size

/* Shape support */
//获取shape的大小,注意,入参是hash_size和prop_size,申请的大小是:hash_size * 4 + sizeof(JSShape) +  prop_size * sizeof(JSShapeProperty)
static inline size_t get_shape_size(size_t hash_size, size_t prop_size)
{
    return hash_size * sizeof(uint32_t) + sizeof(JSShape) +
        prop_size * sizeof(JSShapeProperty);
}

该函数的作用是获取shape的内存占用大小。入参是hash_size和prop_size。代码比较简单,就是将hash_size * sizeof(uint32_t) 加上 prop_size * sizeof(JSShapeProperty) 以及 sizeof(JSShape)。可以看出来, 某一个JSShape在内存中占用的大小绝不仅仅是 JSShape结构体的大小与属性大小的和,它还包含了一个 hash_size * sizeof(uint32_t) 大小的内存块。这个内存块的作用是 以某一个 JSShapeProperty 的hash做索引,存储它在 JSShape->prop 中的索引。

get_shape_from_alloc

// 从申请的内存中获取JSShape
// 创建一个新的JSShape时,申请的内存大小是 get_shape_size 计算出来的
static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size)
{
    return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size);
}

该函数的作用根据申请的内存得到JSShape。为什么需要这个函数?从文章开始的图来看,在内存中,JSShape的开始位置的前面是存在一块内存,用于支持它的属性的hash查找。因此,我们创建内存时,申请的大小是包含这一部分的。得到内存之后,需要得到指向JSShape结构体的指针,因此这个指针就是使用这个函数来得到。

prop_hash_end

// 将JSShape转为内存指针 
static inline uint32_t *prop_hash_end(JSShape *sh)
{
    return (uint32_t *)sh;
}

获取JSShape在内存中的指针,将JSShape强转为内存指针。

get_alloc_from_shape

// 根据JSShape获取创建它时,它申请的内存起始地址
static inline void *get_alloc_from_shape(JSShape *sh)
{
    // 获取起始地址 
    return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1);
}

根据JSShape获取创建它时,它申请的内存起始地址。

get_shape_prop

// 获取JSShape中保存的JSObject的属性的地址
static inline JSShapeProperty *get_shape_prop(JSShape *sh)
{
    return sh->prop;
}

获取JSShape中保存的JSObject的属性的地址。

resize_shape_hash

// 根据新的 shape_hash_bits 申请新的shape_hash
/* 1. 重新申请内存当做新的shape_hash
   2. 将老的shape_hash内的JSShape拷贝到新的shape_hash中(这一步,将会重新计算hash的shape)
*/
static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits)
{
    int new_shape_hash_size, i;
    uint32_t h;
    JSShape **new_shape_hash, *sh, *sh_next;

    // 2^n 新 rt->shape_hash 的大小
    new_shape_hash_size = 1 << new_shape_hash_bits;
    // 申请的新 rt->shape_hash 内存
    new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) *
                                   new_shape_hash_size);
    if (!new_shape_hash)
        return -1;
    
    // 将 rt->shape_hash 中的数据迁移到新的 shape_hash 中
    for(i = 0; i < rt->shape_hash_size; i++) {
        // 抓住一个shape,并遍历所有和这个shape的hash相同的shape,将其复制到新的数组中
        for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) {
            sh_next = sh->shape_hash_next;
            // 根据新的shape_hash_bits 计算新的hash
            h = get_shape_hash(sh->hash, new_shape_hash_bits);
            // 重建链表
            sh->shape_hash_next = new_shape_hash[h];
            // 将当前shape存放在新的shape_hash表中
            new_shape_hash[h] = sh;
        }
    }
    // 迁移结束之后,释放原来的shape
    js_free_rt(rt, rt->shape_hash);
    // 更新hash_bit
    rt->shape_hash_bits = new_shape_hash_bits;
    // 更新hash_size
    rt->shape_hash_size = new_shape_hash_size;
    // 指向新的 shape_hash
    rt->shape_hash = new_shape_hash;
    return 0;
}

js_shape_hash_link 与 js_shape_hash_unlink

// 将 JSShape 链入 rt->shape_hash
static void js_shape_hash_link(JSRuntime *rt, JSShape *sh)
{
    uint32_t h;
    h = get_shape_hash(sh->hash, rt->shape_hash_bits);
    // 不管rt->shape_hash[h]是否存有JSShape,都将其赋值给要插入的JSShape的next中
    sh->shape_hash_next = rt->shape_hash[h];
    // 将JSShape 放入 rt->shape_hash[h]中
    rt->shape_hash[h] = sh;
    // 统计加一
    rt->shape_hash_count++;
}

// 将JSShape从rt->shape_hash中移除
static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh)
{
    uint32_t h;
    JSShape **psh;
   
    //找到 rt->shape_hash 中相同要移除的shape
    h = get_shape_hash(sh->hash, rt->shape_hash_bits);
    psh = &rt->shape_hash[h];
    while (*psh != sh)
        psh = &(*psh)->shape_hash_next;
    // 找到之后,将其赋值为shape->shape_hash_next
    *psh = sh->shape_hash_next;
    // 减少rt->shape_hash中的shape的计数
    rt->shape_hash_count--;
}

JSShape的创建

JSShape与JSObject是绑定在一起的。而JSObject需要根据JSShape来创建。创建JSShape的代码如下:

/* create a new empty shape with prototype 'proto' */
static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto,
                                        int hash_size, int prop_size)
{
    JSRuntime *rt = ctx->rt;
    void *sh_alloc;
    JSShape *sh;

    /* resize the shape hash table if necessary */
    if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) {
        resize_shape_hash(rt, rt->shape_hash_bits + 1);
    }

    //申请内存时,申请的大小是hash_size和prop_size之和
    sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size));
    if (!sh_alloc)
        return NULL;
    // 从申请的内存基础地址 + hash_size 转化成 shape
    // 这句代码很有意思,换个思路,说明每一个JSShape之前都有一段 hash_size 大小的内存与 JSShape有关系,这段内存用于存储属性的hash
    sh = get_shape_from_alloc(sh_alloc, hash_size);
    sh->header.ref_count = 1;
    add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
    if (proto)
        JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto));
    sh->proto = proto;
    // 接着上面说的,JSShape前面空出来的内存先清空
    memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) *
           hash_size);
    // 设置prop_hash_mask,这个参数用于计算JSShape的属性的hash
    sh->prop_hash_mask = hash_size - 1;
    sh->prop_size = prop_size;// 属性尺寸
    sh->prop_count = 0;// 记录属性的数量
    sh->deleted_prop_count = 0; // 已经删除了的属性的数量
    
    // 将JSShape插入到 rt->shape_hash 表中
    /* insert in the hash table */
    // 先根据proto计算hash
    sh->hash = shape_initial_hash(proto);
    sh->is_hashed = TRUE;
    sh->has_small_array_index = FALSE;
    // 将shape链入rt->shape_hash中
    js_shape_hash_link(ctx->rt, sh);
    return sh;
}

static JSShape *js_new_shape(JSContext *ctx, JSObject *proto)
{
    return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
                         JS_PROP_INITIAL_SIZE);
}

JSShape的克隆与添加引用

/* The shape is cloned. The new shape is not inserted in the shape
   hash table */
static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1)
{
    JSShape *sh;
    void *sh_alloc, *sh_alloc1;
    size_t size;
    JSShapeProperty *pr;
    uint32_t i, hash_size;

    hash_size = sh1->prop_hash_mask + 1;
    size = get_shape_size(hash_size, sh1->prop_size);
    sh_alloc = js_malloc(ctx, size);
    if (!sh_alloc)
        return NULL;
    sh_alloc1 = get_alloc_from_shape(sh1);
    memcpy(sh_alloc, sh_alloc1, size);
    sh = get_shape_from_alloc(sh_alloc, hash_size);
    sh->header.ref_count = 1;
    add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
    sh->is_hashed = FALSE;
    if (sh->proto) {
        JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
    }
    for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
        JS_DupAtom(ctx, pr->atom);
    }
    return sh;
}

// 添加引用系数
static JSShape *js_dup_shape(JSShape *sh)
{
    sh->header.ref_count++;
    return sh;
}

JSShape的释放

// 释放shape
static void js_free_shape0(JSRuntime *rt, JSShape *sh)
{
    uint32_t i;
    JSShapeProperty *pr;

    assert(sh->header.ref_count == 0);
    // 如果shape加入了rt->shape_hash,需要先从列表中移除
    if (sh->is_hashed)
        js_shape_hash_unlink(rt, sh);
    if (sh->proto != NULL) {
        JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
    }
    pr = get_shape_prop(sh);
    for(i = 0; i < sh->prop_count; i++) {
        JS_FreeAtomRT(rt, pr->atom);
        pr++;
    }
    // 移除引用
    remove_gc_object(&sh->header);
    // 释放掉创建shape时,申请的内存
    js_free_rt(rt, get_alloc_from_shape(sh));
}

static void js_free_shape(JSRuntime *rt, JSShape *sh)
{
    if (unlikely(--sh->header.ref_count <= 0)) {
        js_free_shape0(rt, sh);
    }
}

//判断shape是否是空的,然后释放
static void js_free_shape_null(JSRuntime *rt, JSShape *sh)
{
    if (sh)
        js_free_shape(rt, sh);
}

JSShape属性数组扩展

/* make space to hold at least 'count' properties */
/**
  更改shape属性数组的大小
*/
static no_inline int resize_properties(JSContext *ctx, JSShape **psh,
                                       JSObject *p, uint32_t count)
{
    JSShape *sh;
    uint32_t new_size, new_hash_size, new_hash_mask, i;
    JSShapeProperty *pr;
    void *sh_alloc;
    intptr_t h;


    sh = *psh;
    // 计算需要分配的prop大小
    new_size = max_int(count, sh->prop_size * 3 / 2);
    /* Reallocate prop array first to avoid crash or size inconsistency
       in case of memory allocation failure */
    // 先申请prop数组,以免申请内存失败
    if (p) {
        JSProperty *new_prop;
        new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
        if (unlikely(!new_prop))
            return -1;
        p->prop = new_prop;
    }
    //新hash的大小
    new_hash_size = sh->prop_hash_mask + 1;
    while (new_hash_size < new_size)
        new_hash_size = 2 * new_hash_size;
    
    // 判断是否需要重新分配属性的 hash table
    if (new_hash_size != (sh->prop_hash_mask + 1)) {
        JSShape *old_sh;
        /* resize the hash table and the properties */
        old_sh = sh;
        //重新申请内存用于存放shape
        sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
        if (!sh_alloc)
            return -1;
        //
        sh = get_shape_from_alloc(sh_alloc, new_hash_size);
        list_del(&old_sh->header.link);
        /* copy all the fields and the properties */
        // 复制所有的字段和属性
        memcpy(sh, old_sh,
               sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count);
        list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
        new_hash_mask = new_hash_size - 1;
        sh->prop_hash_mask = new_hash_mask;
        memset(prop_hash_end(sh) - new_hash_size, 0,
               sizeof(prop_hash_end(sh)[0]) * new_hash_size);
        for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) {
            if (pr->atom != JS_ATOM_NULL) {
                // 先获取shape的prop的atom,然后和 new_hash_mash 做与操作得到 h
                // 和new_hash_mask进行"与"操作的目的使得到的值小于 new_hash_mash
                h = ((uintptr_t)pr->atom & new_hash_mask);
                // 将JSShape前面一段内存作为数组,以 -h-1 为索引获取指定位置的值,赋值给当前prop的hash_next
                pr->hash_next = prop_hash_end(sh)[-h - 1];
                // 将上述索引指向的位置赋值为 i+1
                prop_hash_end(sh)[-h - 1] = i + 1;
            }
        }
        // 释放无用的shape
        js_free(ctx, get_alloc_from_shape(old_sh));
    } else {
        /* only resize the properties */
        list_del(&sh->header.link);
        sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh),
                              get_shape_size(new_hash_size, new_size));
        if (unlikely(!sh_alloc)) {
            /* insert again in the GC list */
            list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
            return -1;
        }
        sh = get_shape_from_alloc(sh_alloc, new_hash_size);
        list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
    }
    *psh = sh;
    sh->prop_size = new_size;
    return 0;
}

JSShape添加属性

// 给shape添加属性
static int add_shape_property(JSContext *ctx, JSShape **psh,
                              JSObject *p, JSAtom atom, int prop_flags)
{
    JSRuntime *rt = ctx->rt;
    JSShape *sh = *psh;
    JSShapeProperty *pr, *prop;
    uint32_t hash_mask, new_shape_hash = 0;
    intptr_t h;

    /* update the shape hash */
    if (sh->is_hashed) {
        js_shape_hash_unlink(rt, sh);
        new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags);
    }

    // 判断是否需要重新申请属性数组,如果不够,shape->prop将会被扩展
    if (unlikely(sh->prop_count >= sh->prop_size)) {
        if (resize_properties(ctx, psh, p, sh->prop_count + 1)) {
            /* in case of error, reinsert in the hash table.
               sh is still valid if resize_properties() failed */
            if (sh->is_hashed)
                js_shape_hash_link(rt, sh);
            return -1;
        }
        sh = *psh;
    }
    
    if (sh->is_hashed) {
        sh->hash = new_shape_hash;
        js_shape_hash_link(rt, sh);
    }
    
    /* Initialize the new shape property.
       The object property at p->prop[sh->prop_count] is uninitialized */
    prop = get_shape_prop(sh);
    pr = &prop[sh->prop_count++];
    pr->atom = JS_DupAtom(ctx, atom);
    pr->flags = prop_flags;
    sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom);
    /* add in hash table */
    hash_mask = sh->prop_hash_mask;
    // 根据属性名称的 atom 和 hash_mask 计算 h
    h = atom & hash_mask;
    // 建立属性的hash链,注意,JSShapeProperty->hash_next 存储的是指向 JSShape->prop 的索引
    pr->hash_next = prop_hash_end(sh)[-h - 1];
    // 将属性计数放入JSShape首地址的 -h-1 处
    prop_hash_end(sh)[-h - 1] = sh->prop_count;
    return 0;
}

JSShape查找属性

static force_inline JSShapeProperty *find_own_property1(JSObject *p,
                                                        JSAtom atom)
{
    JSShape *sh;
    JSShapeProperty *pr, *prop;
    intptr_t h;
    sh = p->shape;
    // 根据属性名称的atom和prop_hash_mask 计算 h
    h = (uintptr_t)atom & sh->prop_hash_mask;
    // 根据h,获取属性在JSShape->prop中的索引
    h = prop_hash_end(sh)[-h - 1];
    // 获取JSShape->prop
    prop = get_shape_prop(sh);
    while (h) {
        // 根据索引,获取指定的JSShapeProperty
        pr = &prop[h - 1];
        // 判断JSShapeProperty的atom是不是和目标atom相等
        if (likely(pr->atom == atom)) {
            // 如果相等,则返回目标JSShapeProperty 
            return pr;
        }
        //不想等就继续遍历
        h = pr->hash_next;
    }
    return NULL;
}

// 和上面类似,参数多了一个ppr,将获取的属性保存在ppr中
static force_inline JSShapeProperty *find_own_property(JSProperty **ppr,
                                                       JSObject *p,
                                                       JSAtom atom)
{
    JSShape *sh;
    JSShapeProperty *pr, *prop;
    intptr_t h;
    sh = p->shape;
    h = (uintptr_t)atom & sh->prop_hash_mask;
    h = prop_hash_end(sh)[-h - 1];
    prop = get_shape_prop(sh);
    while (h) {
        pr = &prop[h - 1];
        if (likely(pr->atom == atom)) {
            *ppr = &p->prop[h - 1];
            /* the compiler should be able to assume that pr != NULL here */
            return pr;
        }
        h = pr->hash_next;
    }
    *ppr = NULL;
    return NULL;
}

JSShape删除属性

static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom)
{
    JSShape *sh;
    JSShapeProperty *pr, *lpr, *prop;
    JSProperty *pr1;
    uint32_t lpr_idx;
    intptr_t h, h1;

 redo:
    sh = p->shape;
    h1 = atom & sh->prop_hash_mask;
    h = prop_hash_end(sh)[-h1 - 1];
    prop = get_shape_prop(sh);
    lpr = NULL;
    lpr_idx = 0;   /* prevent warning */
    while (h != 0) {
        pr = &prop[h - 1];
        if (likely(pr->atom == atom)) {
            /* found ! */
            if (!(pr->flags & JS_PROP_CONFIGURABLE))
                return FALSE;
            /* realloc the shape if needed */
            if (lpr)
                lpr_idx = lpr - get_shape_prop(sh);
            if (js_shape_prepare_update(ctx, p, &pr))
                return -1;
            sh = p->shape;
            /* remove property */
            if (lpr) {
                lpr = get_shape_prop(sh) + lpr_idx;
                lpr->hash_next = pr->hash_next;
            } else {
                prop_hash_end(sh)[-h1 - 1] = pr->hash_next;
            }
            sh->deleted_prop_count++;
            /* free the entry */
            pr1 = &p->prop[h - 1];
            free_property(ctx->rt, pr1, pr->flags);
            JS_FreeAtom(ctx, pr->atom);
            /* put default values */
            pr->flags = 0;
            pr->atom = JS_ATOM_NULL;
            pr1->u.value = JS_UNDEFINED;

            /* compact the properties if too many deleted properties */
            if (sh->deleted_prop_count >= 8 &&
                sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) {
                compact_properties(ctx, p);
            }
            return TRUE;
        }
        lpr = pr;
        h = pr->hash_next;
    }

    if (p->is_exotic) {
        if (p->fast_array) {
            uint32_t idx;
            if (JS_AtomIsArrayIndex(ctx, &idx, atom) &&
                idx < p->u.array.count) {
                if (p->class_id == JS_CLASS_ARRAY ||
                    p->class_id == JS_CLASS_ARGUMENTS) {
                    /* Special case deleting the last element of a fast Array */
                    if (idx == p->u.array.count - 1) {
                        JS_FreeValue(ctx, p->u.array.u.values[idx]);
                        p->u.array.count = idx;
                        return TRUE;
                    }
                    if (convert_fast_array_to_array(ctx, p))
                        return -1;
                    goto redo;
                } else {
                    return FALSE;
                }
            }
        } else {
            const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic;
            if (em && em->delete_property) {
                return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom);
            }
        }
    }
    /* not found */
    return TRUE;
}

/* remove the deleted properties. */
static int compact_properties(JSContext *ctx, JSObject *p)
{
    JSShape *sh, *old_sh;
    void *sh_alloc;
    intptr_t h;
    uint32_t new_hash_size, i, j, new_hash_mask, new_size;
    JSShapeProperty *old_pr, *pr;
    JSProperty *prop, *new_prop;
    
    sh = p->shape;
    assert(!sh->is_hashed);

    new_size = max_int(JS_PROP_INITIAL_SIZE,
                       sh->prop_count - sh->deleted_prop_count);
    assert(new_size <= sh->prop_size);

    new_hash_size = sh->prop_hash_mask + 1;
    while ((new_hash_size / 2) >= new_size)
        new_hash_size = new_hash_size / 2;
    new_hash_mask = new_hash_size - 1;

    /* resize the hash table and the properties */
    old_sh = sh;
    sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
    if (!sh_alloc)
        return -1;
    sh = get_shape_from_alloc(sh_alloc, new_hash_size);
    list_del(&old_sh->header.link);
    memcpy(sh, old_sh, sizeof(JSShape));
    list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
    
    memset(prop_hash_end(sh) - new_hash_size, 0,
           sizeof(prop_hash_end(sh)[0]) * new_hash_size);

    j = 0;
    old_pr = old_sh->prop;
    pr = sh->prop;
    prop = p->prop;
    for(i = 0; i < sh->prop_count; i++) {
        if (old_pr->atom != JS_ATOM_NULL) {
            pr->atom = old_pr->atom;
            pr->flags = old_pr->flags;
            h = ((uintptr_t)old_pr->atom & new_hash_mask);
            pr->hash_next = prop_hash_end(sh)[-h - 1];
            prop_hash_end(sh)[-h - 1] = j + 1;
            prop[j] = prop[i];
            j++;
            pr++;
        }
        old_pr++;
    }
    assert(j == (sh->prop_count - sh->deleted_prop_count));
    sh->prop_hash_mask = new_hash_mask;
    sh->prop_size = new_size;
    sh->deleted_prop_count = 0;
    sh->prop_count = j;

    p->shape = sh;
    js_free(ctx, get_alloc_from_shape(old_sh));
    
    /* reduce the size of the object properties */
    new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
    if (new_prop)
        p->prop = new_prop;
    return 0;
}

查找JSShape

/* find a hashed empty shape matching the prototype. Return NULL if
   not found 
   从rt->shape_hash中寻找与proto相同的JSShape 
*/
static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto)
{
    JSShape *sh1;
    uint32_t h, h1;
    // 获取shape初始hash
    h = shape_initial_hash(proto);
    // 将hash截断,保证得到的hash小于 rt->shape_hash_bits
    h1 = get_shape_hash(h, rt->shape_hash_bits);
    // 首先以h1作为索引,从 rt->shape_hash 中获取一个JSShape
    for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
        // 因为可能存在多个JSShape具有相同的hash,判断下是否是同一个
        if (sh1->hash == h &&
            sh1->proto == proto &&
            sh1->prop_count == 0) {
            return sh1;
        }
        //如果不是,根据JSShape的链表, 遍历下一个
    }
    return NULL;
}

/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if
   not found */
// 查找存在与给定属性的名称和属性标识都相同的shape
static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh,
                                       JSAtom atom, int prop_flags)
{
    JSShape *sh1;
    uint32_t h, h1, i, n;

    h = sh->hash;
    h = shape_hash(h, atom);
    h = shape_hash(h, prop_flags);
    h1 = get_shape_hash(h, rt->shape_hash_bits);
    for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
        /* we test the hash first so that the rest is done only if the
           shapes really match */
        if (sh1->hash == h &&
            sh1->proto == sh->proto &&
            sh1->prop_count == ((n = sh->prop_count) + 1)) {
            for(i = 0; i < n; i++) {
                if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) ||
                    unlikely(sh1->prop[i].flags != sh->prop[i].flags))
                    goto next;
            }
            if (unlikely(sh1->prop[n].atom != atom) ||
                unlikely(sh1->prop[n].flags != prop_flags))
                goto next;
            return sh1;
        }
    next: ;
    }
    return NULL;
}

从 rt->shape_hash 中查找出包含给定的属性名称和标识的JSShape。两个函数,分别使用原型对象和对象属性的维度去查询想要的JSShape。

JSObject与JSShape

//使用给定shape创建对象
static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id)
{
    JSObject *p;

    js_trigger_gc(ctx->rt, sizeof(JSObject));
    //1. 申请内存
    p = js_malloc(ctx, sizeof(JSObject));
    if (unlikely(!p))
        goto fail;
    p->class_id = class_id;
    p->extensible = TRUE;
    p->free_mark = 0;
    p->is_exotic = 0;
    p->fast_array = 0;
    p->is_constructor = 0;
    p->is_uncatchable_error = 0;
    p->tmp_mark = 0;
    p->is_HTMLDDA = 0;
    p->first_weak_ref = NULL;
    p->u.opaque = NULL;
    p->shape = sh;
    p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
    if (unlikely(!p->prop)) {
        js_free(ctx, p);
    fail:
        js_free_shape(ctx->rt, sh);
        return JS_EXCEPTION;
    }

    switch(class_id) {
    case JS_CLASS_OBJECT:
        break;
    case JS_CLASS_ARRAY:
        {
            JSProperty *pr;
            p->is_exotic = 1;
            p->fast_array = 1;
            p->u.array.u.values = NULL;
            p->u.array.count = 0;
            p->u.array.u1.size = 0;
            /* the length property is always the first one */
            if (likely(sh == ctx->array_shape)) {
                pr = &p->prop[0];
            } else {
                /* only used for the first array */
                /* cannot fail */
                pr = add_property(ctx, p, JS_ATOM_length,
                                  JS_PROP_WRITABLE | JS_PROP_LENGTH);
            }
            pr->u.value = JS_NewInt32(ctx, 0);
        }
        break;
    case JS_CLASS_C_FUNCTION:
        p->prop[0].u.value = JS_UNDEFINED;
        break;
    case JS_CLASS_ARGUMENTS:
    case JS_CLASS_UINT8C_ARRAY:
    case JS_CLASS_INT8_ARRAY:
    case JS_CLASS_UINT8_ARRAY:
    case JS_CLASS_INT16_ARRAY:
    case JS_CLASS_UINT16_ARRAY:
    case JS_CLASS_INT32_ARRAY:
    case JS_CLASS_UINT32_ARRAY:
#ifdef CONFIG_BIGNUM
    case JS_CLASS_BIG_INT64_ARRAY:
    case JS_CLASS_BIG_UINT64_ARRAY:
#endif
    case JS_CLASS_FLOAT32_ARRAY:
    case JS_CLASS_FLOAT64_ARRAY:
        p->is_exotic = 1;
        p->fast_array = 1;
        p->u.array.u.ptr = NULL;
        p->u.array.count = 0;
        break;
    case JS_CLASS_DATAVIEW:
        p->u.array.u.ptr = NULL;
        p->u.array.count = 0;
        break;
    case JS_CLASS_NUMBER:
    case JS_CLASS_STRING:
    case JS_CLASS_BOOLEAN:
    case JS_CLASS_SYMBOL:
    case JS_CLASS_DATE:
#ifdef CONFIG_BIGNUM
    case JS_CLASS_BIG_INT:
    case JS_CLASS_BIG_FLOAT:
    case JS_CLASS_BIG_DECIMAL:
#endif
        p->u.object_data = JS_UNDEFINED;
        goto set_exotic;
    case JS_CLASS_REGEXP:
        p->u.regexp.pattern = NULL;
        p->u.regexp.bytecode = NULL;
        goto set_exotic;
    default:
    set_exotic:
        if (ctx->rt->class_array[class_id].exotic) {
            p->is_exotic = 1;
        }
        break;
    }
    p->header.ref_count = 1;
    add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT);
    return JS_MKPTR(JS_TAG_OBJECT, p);
}

// 将JSValue转化成JSObject
static JSObject *get_proto_obj(JSValueConst proto_val)
{
    if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT)
        return NULL;
    else
        return JS_VALUE_GET_OBJ(proto_val);
}

/* WARNING: proto must be an object or JS_NULL */
// 使用给定原型创建对象
JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val,
                               JSClassID class_id)
{
    JSShape *sh;
    JSObject *proto;
    
    // 将JSValue转化成JSObject
    proto = get_proto_obj(proto_val);
    // 
    sh = find_hashed_shape_proto(ctx->rt, proto);
    if (likely(sh)) {
        sh = js_dup_shape(sh);
    } else {
        sh = js_new_shape(ctx, proto);
        if (!sh)
            return JS_EXCEPTION;
    }
    return JS_NewObjectFromShape(ctx, sh, class_id);
}

JSShape像是JSObject的模板。JSShape保存着JSObject的属性信息。每一个JSObject都持有一个JSShape,反之则不然。同时,JSShape持有着JSObject的原型对象。 JavaScript的继承属性便是由JSShape持有的原型对象实现的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值