QuickJS JSString & JSAtom

QuickJS JSString & JSAtom

 JSString & JSAtom

上图看起来有点糊,实际上是因为内容多,文字小,被缩放了。可以放大看,是清晰的

JSString定义

QuickJS中,使用JSString结构体封装了字符串变量。使用JSAtom来完成对字符串的存储和比较。QuickJS支持ASCII和Unicode两种字符串编码格式。
先看下,它们的定义

typedef struct JSRefCountHeader {
    int ref_count;
} JSRefCountHeader;

struct {
    JSRefCountHeader header; /* must come first, 32-bit */
    uint32_t len : 31;
    uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
    /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3,
       for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3
       XXX: could change encoding to have one more bit in hash */
    uint32_t hash : 30;
    uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
    uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL 使用拉链法解决hash碰撞问题 */
#ifdef DUMP_LEAKS
    struct list_head link; /* string list */
#endif
    union {
        uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */
        uint16_t str16[0];
    } u;
};

JSString是一个结构体。
JSString中,第一个JSRefCountHeader是一个结构体。它用来记录引用次数。作用是用来存储该字符串的引用次数,GC程序据此来决定是否回收该内存。len的长度是31bit,用于记录字符串的长度。is_wide_char用于标识该字符串是ASCII还是Unicode编码。hash长度是30bit,是其装载的字符串的hash值,根据hash的定义,该值具有唯一性,因此可用比较字符串内容是否一致。atom_type长度是2bit,标识该JSString的atom_type。它有JS_ATOM_TYPE_STRING、JS_ATOM_TYPE_GLOBAL_SYMBOL、JS_ATOM_TYPE_SYMBOL和JS_ATOM_TYPE_PRIVATE几种值。hash_next是下一个和当前字符串hash相同的字符串在atom_array数组中的索引,作用是使用拉链法解决hash碰撞问题。再然后是一个联合体。定义了8位和16位的字符串,分别用于存储ASCII和Unicode字符串。

创建JSString

根据字符串创建JSString。需要注意的是,需要区分字符串是纯ascii还是Unicode编码。

char *buf = "test string"; 
JSString *str;
int len = strlen(buf);
str = js_alloc_string(ctx, len, 0);//调用js_alloc_string创建的对象atom_type=0,字符串内容是空的,因此下面的代码就是将字符串拷贝到其字符串中去 
memcpy(str->u.str8, buf, len);//将字符串拷贝进去
str->u.str8[len] = '\0';//截止字符串

static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char)
{
    JSString *p;
    p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char);
    if (unlikely(!p)) {
        JS_ThrowOutOfMemory(ctx);
        return NULL;
    }
    return p;
}

/* Note: the string contents are uninitialized */
static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char)
{
    JSString *str;
    str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char);
    if (unlikely(!str))
        return NULL;
    str->header.ref_count = 1;//创建一个引用计数
    str->is_wide_char = is_wide_char;//是否是ascii
    str->len = max_len;//字符串长度
    str->atom_type = 0;//默认设置不是JSAtom类型
    str->hash = 0;          /* optional but costless */
    str->hash_next = 0;     /* optional */
#ifdef DUMP_LEAKS
    list_add_tail(&str->link, &rt->string_list);
#endif
    return str;
}

释放JSString

就像《权力的游戏》中红包女巫说的一样:All Men Must Die. 事实上,世间万物皆难逃一死。JSString使用完了之后,也需要被释放掉。

/* same as JS_FreeValueRT() but faster */
static inline void js_free_string(JSRuntime *rt, JSString *str)
{
    if (--str->header.ref_count <= 0) {
        if (str->atom_type) {
            JS_FreeAtomStruct(rt, str);
        } else {
#ifdef DUMP_LEAKS
            list_del(&str->link);
#endif
            js_free_rt(rt, str);
        }
    }
}

static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
{
#if 0   /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */
    if (unlikely(i == JS_ATOM_NULL)) {
        p->header.ref_count = INT32_MAX / 2;
        return;
    }
#endif
    uint32_t i = p->hash_next;  /* atom_index 其含义是某一字符串在rt->atom_array中的索引 */
    if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
        JSAtomStruct *p0, *p1;
        uint32_t h0;

        h0 = p->hash & (rt->atom_hash_size - 1);
        i = rt->atom_hash[h0];
        p1 = rt->atom_array[i];
        if (p1 == p) {
            rt->atom_hash[h0] = p1->hash_next;
        } else {
            for(;;) {
                assert(i != 0);
                p0 = p1;
                i = p1->hash_next;
                p1 = rt->atom_array[i];
                if (p1 == p) {
                    p0->hash_next = p1->hash_next;
                    break;
                }
            }
        }
    }
    /* insert in free atom list */
    rt->atom_array[i] = atom_set_free(rt->atom_free_index);
    rt->atom_free_index = i;
    /* free the string structure */
#ifdef DUMP_LEAKS
    list_del(&p->link);
#endif
    js_free_rt(rt, p);
    rt->atom_count--;
    assert(rt->atom_count >= 0);
}

void js_free_rt(JSRuntime *rt, void *ptr)
{
    rt->mf.js_free(&rt->malloc_state, ptr);
}

如果JSString是JSAtom类型的,需要调用 JS_FreeAtomStruct 来释放内存。否则,只需要调用 js_free_rt 即可完成释放。js_free_rt 函数比较简单,只是调用了内存释放函数进行了释放。 JS_FreeAtomStruct 稍微复杂一些,我们稍后在介绍玩JSAtom时,再详解。

JSAtom

JSAtom直接从定义上看,不甚明白。我们来看下QuickJS官方文档是怎么说明它的。

Object property names and some strings are stored as Atoms (unique
strings) to save memory and allow fast comparison. Atoms are
represented as a 32 bit integer. Half of the atom range is reserved
for immediate integer literals from 0 to 2^31 - 1

什么意思呢?简单翻译下:对象属性名称和一些字符串使用Atoms(唯一字符串)来进行存储以节省内存和允许快速比对。Atoms呈现为32位整数。Atom的一半保留给立即数文本,范围是从0到2^31-1。也就是说,JSAtom创建的目的是:保存对象属性名称和一些字符串,以便于节省内存和方便对字符串的快速比较大小。
JSAtom的定义如下,非常简单,字面上理解就是一个32位的无符号整形数字。虽然它只是一个小小的定义,但是却能让人感觉到大大的懵逼。

typedef uint32_t JSAtom;

想要搞明白JSAtom,还需要一些其它的代码来辅助。JSAtom有以下几种类型:

enum {
    JS_ATOM_TYPE_STRING = 1,
    JS_ATOM_TYPE_GLOBAL_SYMBOL,
    JS_ATOM_TYPE_SYMBOL,
    JS_ATOM_TYPE_PRIVATE,
};

分别表示字符串、全局symbol,symbol和私有类型。
所有的JSAtom类型变量统一存储在JSRuntime中。我们把相关代码摘出来一起研究下。

struct JSRuntime {
    
    ......
    
    // 记录atom_hash数组的长度
    int atom_hash_size; /* power of two 2二次方*/
    // 
    int atom_count;//当前已经创建的atom的数量
    int atom_size;//atom_array的容量
    int atom_count_resize; /* resize hash table at this count atom_count达到这个值时,将会重新给atom_hash数组分配大小*/
    //atom_hash是一维数组,以字符串的hash做索引,存储字符串在atom_array中的索引
    uint32_t *atom_hash;
    //atom_array是二维数组,存储JSAtomStruct,也就是JSString。每一个字符串可由 atom_hash中存储的索引进行定位
    JSAtomStruct **atom_array;//存储JSString的数组
    int atom_free_index; /* 0 = none 标识当前可用的atom_hash的index*/
 
    ......
 
};

typedef struct JSString JSAtomStruct;

JSAtom是为了解决JSString存储时内存占用效率低和比较时复杂的问题而出现的。那么它是怎么存储字符串的呢?我们可以看到,上述字段中atom_array是一个JSAtomStruct数组。从上面的定义我们可以看出来,JSAtomStruct其实就是JSString。因此, QuickJS使用atom_array存储JSString。将JSString放入数组中之后,如果才能在需要的时候找到它呢?我们把字符串放在atom_array中了,每一个字符串都对应一个索引,只要知道索引就知道字符串的位置了。 这个索引是一个32位的无符号整数。看起来是不是有点眼熟?JSAtom也是32位无符号整数。对,没错,JSAtom其实就是这个索引。因此,JSAtom其实就是某一个字符串在rt->atom_array中的索引。
可是JSAtom如何来节省内存的呢?
如果不使用JSAtom,直接对“hello,world”这个字符串进行存储的话,需要先感觉字符串创建JSString,然后将JSString存入rt->atom_array中,同时返回字符串所在的内存地址或者atom_array的索引。这将会消耗 11+(32+31+1+30+2+32)/8 = 27个字节。然后,后面每次存储"hello,world"这个字符串时,都需要多消耗27个字节。如果相同的字符串被多次创建的话,对内存会是一个比较大的开销。但是,如果使用JSAtom的话,首次存储"hello,world"需要消耗 11+(32+31+1+30+2+32)/8 = 27个字节。以后每次存储时,都会在字符串池–atom_array中搜索一边,如果搜索到字符串池中有相同的字符串时,我们就不存储了,直接返回它在字符串池中的索引即可。这样做,相同字符串,不论存储多少次,都只相当于在内存中存储了一次。如此便节省了内存。
但是JSAtom又是如何实现快速比较的呢?
字符串的比对,一般是遍历两个字符串数组,一个字节一个字节的进行比对。比如说我们常用的c语言标准库中的strcmp函数在某些系统上就是如此实现的。如果字符串较长,比较频次较高,会消耗较多的cpu和内存占用。而JSAtom将这些字符串的hash(32位无符号整数)计算出来,并保存起来,这样,比较时,只需要比较字符串的hash就好了,因此能节省时间、内存和CPU消耗。
说到这里,我们需要提出一个问题,hash能代表字符串吗?能也不能。为什么呢?因为大概率字符串之间的hash值是不同的,所以说能。但是hash存在碰撞问题:两个不同内容的字符串可能其hash值却相同,所以说不能。你可能说:这不是逗我吗?不逗你,QuickJS解决了这个问题。它采用了一种叫做拉链法的思路解决了哈希碰撞问题。在此,我不展开讲拉链法了,有兴趣的读者可以去看看相关介绍文章。回到JSString的定义中去,有一个字段叫做hash_next,就是用拉链法记录下一个相同hash值的字符串在rt->atom_array中的索引。

使用JSAtom表示JSString

我们从代码上来看,如何用JSAtome来表示JSString。

计算JSString的hash

先看看QuickJS是如何计算字符串的hash的。

/* XXX: could use faster version ? */
static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h)
{
    size_t i;

    for(i = 0; i < len; i++)
        h = h * 263 + str[i];
    return h;
}

static inline uint32_t hash_string16(const uint16_t *str,
                                     size_t len, uint32_t h)
{
    size_t i;

    for(i = 0; i < len; i++)
        h = h * 263 + str[i];
    return h;
}

static uint32_t hash_string(const JSString *str, uint32_t h)
{
    if (str->is_wide_char)
        h = hash_string16(str->u.str16, str->len, h);
    else
        h = hash_string8(str->u.str8, str->len, h);
    return h;
}

计算JSString的hash方式是调用hash_string函数。这个函数中判断了JSString的编码方式,如果是Unicode就调用hash_string16,否则调用hash_string8。我们简单分析hash_string8。该函数有三个参数,分别是待hash的字符串的内容和长度以及参数h—其值一般为atom_type中的一种。内部代码很简单,就是将h*263加上某一位的字符得到的值重新赋值个h,然后遍历字符串的字符,将字符串中的所有字符都代入计算。最后得到的数字就是该串字符串的hash。可以看出,相同的字符串,当调用hash_string时传入的h不同,得到的hash是不相同的。

JSAtom的创建

JSAtom是根据JSString创建的。创建Atom在源码中有多个位置,我们举一个例子来看下创建过程。

static inline uint32_t atom_get_free(const JSAtomStruct *p)
{
    return (uintptr_t)p >> 1;
}

static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p)
{
    JSRuntime *rt = ctx->rt;
    uint32_t n;
    if (is_num_string(&n, p)) {
        if (n <= JS_ATOM_MAX_INT) {
            js_free_string(rt, p);
            return __JS_AtomFromUInt32(n);
        }
    }
    /* XXX: should generate an exception */
    return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING);
}

static inline BOOL __JS_AtomIsConst(JSAtom v)
{
#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1
        return (int32_t)v <= 0;
#else
        return (int32_t)v < JS_ATOM_END;
#endif
}

/* string case (internal). Return JS_ATOM_NULL if error. 'str' is
   freed. */
static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
{
    uint32_t h, h1, i;
    JSAtomStruct *p;
    int len;

#if 0
    printf("__JS_NewAtom: ");  JS_DumpString(rt, str); printf("\n");
#endif
    //在字符串常量池中获取字符串
    if (atom_type < JS_ATOM_TYPE_SYMBOL) {
        /* str is not NULL */
        //如果str的类型和需要创建的atom_type相等,直接从字符串池中获取它的索引---atom
        if (str->atom_type == atom_type) {
            /* str is the atom, return its index */
            i = js_get_atom_index(rt, str);
            /* reduce string refcount and increase atom's unless constant */
            //如果是内置JSAtom,则减少字符串的引用计数
            if (__JS_AtomIsConst(i))
                str->header.ref_count--;
            return i;
        }
        //如果没有,尝试查找已经注册到atom_array中的atom
        /* try and locate an already registered atom */
        len = str->len;
        h = hash_string(str, atom_type);//计算str的hash
        h &= JS_ATOM_HASH_MASK;//目的是将高两位清空,因为在JSString中,hash(30bit)和atom_type(2bit)共用一个32位内存
        h1 = h & (rt->atom_hash_size - 1);//确保得到的hash值小于 rt->atom_hash_size
        i = rt->atom_hash[h1];//从atom_hash中获取索引
        while (i != 0) {
            //根据索引,获取已经保存的JSString
            p = rt->atom_array[i];
            if (p->hash == h &&
                p->atom_type == atom_type &&
                p->len == len &&
                js_string_memcmp(p, str, len) == 0) {
                // 找到了跳转到done
                if (!__JS_AtomIsConst(i))
                    p->header.ref_count++;
                goto done;
            }
            i = p->hash_next;//拉链法解决hash碰撞,寻找下一个相同hash的JSString,本质是一个JSString在rt->atom_array中的索引
        }
    } else {
        h1 = 0; /* avoid warning */
        if (atom_type == JS_ATOM_TYPE_SYMBOL) {
            h = JS_ATOM_HASH_SYMBOL;
        } else {
            h = JS_ATOM_HASH_PRIVATE;
            atom_type = JS_ATOM_TYPE_SYMBOL;
        }
    }

    //执行到这里,说明atom_array中没有找到JSString,因此需要创建Atom(即:将JSString注册到atom_array)
    //rt->atom_free_index表明首次创建Atom或者atom_array不够用了,因此,需要重新申请内存
    if (rt->atom_free_index == 0) {
        /* allow new atom entries */
        uint32_t new_size, start;
        JSAtomStruct **new_array;

        /* alloc new with size progression 3/2:
           4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092
           preallocating space for predefined atoms (at least 195).
         */
         //计算新的atom_size大小
        new_size = max_int(211, rt->atom_size * 3 / 2);
        if (new_size > JS_ATOM_MAX)
            goto fail;
        /* XXX: should use realloc2 to use slack space 应该使用realloc2来使用空闲空间*/
        //根据大小重新申请内存给atom_array,使用realloc的原因是因为之前可能已经存储了数据
        new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size);
        if (!new_array)
            goto fail;
        /* Note: the atom 0 is not used */
        start = rt->atom_size;
        //如果没有创建过,则第一个位置固定存储为JS_ATOM_NULL 
        if (start == 0) {
            /* JS_ATOM_NULL entry */
            p = js_mallocz_rt(rt, sizeof(JSAtomStruct));
            if (!p) {
                js_free_rt(rt, new_array);
                goto fail;
            }
            p->header.ref_count = 1;  /* not refcounted */
            p->atom_type = JS_ATOM_TYPE_SYMBOL;
#ifdef DUMP_LEAKS
            list_add_tail(&p->link, &rt->string_list);
#endif
            new_array[0] = p;
            rt->atom_count++;
            start = 1;
        }
        //保存atom_size
        rt->atom_size = new_size;
        //申请的new_array赋值给rt->atom_array
        rt->atom_array = new_array;
        //设置atom_array的空闲索引
        rt->atom_free_index = start;
        //将其它索引全部置空
        for(i = start; i < new_size; i++) {
            uint32_t next;
            // 将索引号存入申请的rt->atom_array中
            // 注意:加1的目的是错开一位,这样好检测空闲地址的索引
            if (i == (new_size - 1))
                next = 0;
            else
                next = i + 1;
            //每一个空白的entry内容都设置为自己的索引+1
            /*
            static inline JSAtomStruct *atom_set_free(uint32_t v)
            {
                return (JSAtomStruct *)(((uintptr_t)v << 1) | 1);
            }
            */
            rt->atom_array[i] = atom_set_free(next);
        }
    }

    //如果JSSting不为空
    if (str) {
        if (str->atom_type == 0) { //普通字符串
            p = str;//直接将JSString赋值给p
            p->atom_type = atom_type;//设置类型
            //此处没有释放str的内存,直接就使用了他的地址
        } else {
            //创建一个新的JSString,拷贝了它的内存,并对str进行了释放
            p = js_malloc_rt(rt, sizeof(JSString) +
                             (str->len << str->is_wide_char) +
                             1 - str->is_wide_char);
            if (unlikely(!p))
                goto fail;
            p->header.ref_count = 1;
            p->is_wide_char = str->is_wide_char;
            p->len = str->len;
#ifdef DUMP_LEAKS
            list_add_tail(&p->link, &rt->string_list);
#endif
            //内存拷贝
            memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) +
                   1 - str->is_wide_char);
            //释放str
            js_free_string(rt, str);
        }
    } else {
        //空字符串
        p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */
        if (!p)
            return JS_ATOM_NULL;
        p->header.ref_count = 1;
        p->is_wide_char = 1;    /* Hack to represent NULL as a JSString */
        p->len = 0;
#ifdef DUMP_LEAKS
        list_add_tail(&p->link, &rt->string_list);
#endif
    }

    /* use an already free entry */
    i = rt->atom_free_index;//使用空闲的entry
    
/*  
    /* 当不同的数据块连续存储时,从一个数据块结构体指针指向相邻的不同的数据块的结构体的时候,只需要指针值加1,而不是加上当前整个结构体的大小。因为结构体指针的移动单位是当前整个结构体的大小。*/
    //获取空闲entry的索引,对应下面的atom_set_free,就相对好理解一些
    static inline uint32_t atom_get_free(const JSAtomStruct *p)
    {
        //结构体指针右移,将会产生一个结构体size的偏移。换句话说,右移之后指向了相邻的下一个结构体
        return (uintptr_t)p >> 1;
    }
    //将值左移一位,再或上1
    static inline JSAtomStruct *atom_set_free(uint32_t v)
    {
        return (JSAtomStruct *)(((uintptr_t)v << 1) | 1);
    }
*/
    rt->atom_free_index = atom_get_free(rt->atom_array[i]);//获取下一个空闲的entry索引
    rt->atom_array[i] = p;//将JSString存储到atom_array中

    p->hash = h;//保存hash
    p->hash_next = i;   /* 拉链法解决hash碰撞 atom_index 记录它在atom_array中的索引
    在这里,将其自己的索引保存在hash_next是因为,根据前面的查找,没有找到有相同索引的其它字符串,所以hash_next在这里暂时设置为自己。
    */
    p->atom_type = atom_type;//设置类型

    //将atom_count增加1,它表示已经创建的atom的数量增加1
    rt->atom_count++;
    
    //如果类型不是JS_ATOM_TYPE_SYMBOL,就将rt->atom_hash中的索引保存到hash_next中。为啥?因为JS中每一个symbol是唯一的,所以,每一个symbol字符串都会存储。
    if (atom_type != JS_ATOM_TYPE_SYMBOL) {
        // rt->atom_hash[h1] 获取该JSString在atom_hash
        // 根据该JSString的hash 获取atom_hash中存储的某JSString在atom_array的索引
        // 程序运行到这里,说明是新添加字符串到字符串池中,因此rt->atom_hash[h1]的值肯定是0
        p->hash_next = rt->atom_hash[h1];//拉链法解决hash碰撞
        rt->atom_hash[h1] = i;//将该JSString在atom_array中的索引保存到以该JSString的hash为索引的atom_hash数组中。
        //如果atom_count不够用了,重新申请大小
        if (unlikely(rt->atom_count >= rt->atom_count_resize))
            JS_ResizeAtomHash(rt, rt->atom_hash_size * 2);
    }

    //    JS_DumpAtoms(rt);
    return i;

 fail:
    i = JS_ATOM_NULL;
 done:
    //释放str
    if (str)
        js_free_string(rt, str);
    return i;
}

__JS_NewAtom 是创建Atom的最终实现函数。创建Atom需要JSRuntime、JSString和atom_type三个参数。首先,判断atom_type的类型,如果是0、JS_ATOM_TYPE_STRING或JS_ATOM_TYPE_GLOBAL_SYMBOL,就使用判断字符串池中是否存在了该字符串。如果是JS_ATOM_TYPE_SYMBOL或者JS_ATOM_TYPE_PRIVATE,就重新构建JSAtom。
如果atom_type是0、JS_ATOM_TYPE_STRING或JS_ATOM_TYPE_GLOBAL_SYMBOL,首先判断下JSString的类型是否为JSAtom,如果是,就使用 js_get_atom_index 从 rt->atom_hash 中获取JSString的JSAtom。如果类型不相等,就尝试从 rt->atom_array 中找到完全相同的JSString。如果找到了,就直接返回该字符串池中的JSAtom。如果不是,继续走下面的流程。这一步中,我们计算了字符串的hash,保存为h,同时计算了该JSString在rt->atom_hash中存储其在rt->atom_array索引的索引。
然后,接下来是判断 rt->atom_free_index 是否等于0。如果等于0,说明 rt->atom_array 内存不够用了,需要分配或扩展内存。分配多少内存呢?每次重新扩展后的内存是原来内存的3/2倍。扩展内存使用的是 js_realloc_rt 方法。然后通过 rt->atom_size 判断字符串池是否被使用过。如果没有,固定设置 rt->atom_array 第一个entry是空字符串。再将新申请的内存赋值给rt->atom_array,并重新何止好atom_size大小和atom_array空闲索引。最后,将 atom_array 未用的entry都设置为 ((index<1)| 1 ) 。
处理好内存分配之后,继续JSAtom的创建。首先判断JSString指针是否为0,如果为空,则创建一个空的JSString。如果不是,判断JSString的atom_type是否为0,如果为0,设置它的atom_type为需要创建的atom_type。否则,就拷贝一个新的JSString。
获取 rt->atom_array 中的空白索引 rt->atom_free_index 。我们的JSString将会放在它的这个位置。此外,我们需要更新 rt->atom_free_index ,将其指向的位置移向下一个entry。此外,需要设置JSString的hash为我们前面计算后存储在h变量中的值。将它的hash_next设置为自己的索引。它的atom_type同样设置为创建时传入的atom_type。同样,需要记加atom_count,它用来存储当前Runtime中共存在多少个JSAtom。
如果atom_type不等于JS_ATOM_TYPE_SYMBOL,我们需要将当前JSString的hash_next设置为 rt->atom_hash 中以h1的值为索引的值。这是为hash碰撞建立一个链表。同时更新 rt->atom_hash[h1] 的值为本JSString在 rt->atom_array 中的索引。最后,判断下是否需要扩展atom_hash的大小。
最后,创建完毕之后,将本JSString在 rt->atom_array 中的索引返回。从这里,我们可以看出JSAtom其实本质是JSString在 rt->atom_array 中索引。

根据JSString获取JSAtom

static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p)
{
    // 首先保存JSString的hash_next,它存储的是和p具有相同hash的另外一个字符串在rt->atom_arrat中的索引。
    uint32_t i = p->hash_next;  /* atom_index */
    //判断JSString的类型是不是JS_ATOM_TYPE_SYMBOL
    if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {//如果不是JS_ATOM_TYPE_SYMBOL
        JSAtomStruct *p1;//临时变量
        //下面代码中p->hash & (rt->atom_hash_size - 1) 的作用是保证 p->hash 不会超出rt->atom_has的大小,以免出现野指针。
        //这句代码的作用是根据该JSString的hash获取该字符串在rt->atom_array中的索引
        i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)];
        p1 = rt->atom_array[i];//根据索引,获取对应的字符串
        //下面的遍历代码作用是:因为存在hash碰撞的可能性,不同内容的字符串可能存在同一个hash值,因此,需要遍历链表,找出真正的索引---即atom
        while (p1 != p) {
            assert(i != 0);
            i = p1->hash_next;//寻找相同hash的下一个索引
            p1 = rt->atom_array[i];
        }
    }
    return i;//返回atom在rt->atom_array中的索引
}

根据已有JSString找到它的JSAtom。首先是取到它的hash_next。根据上面的创建过程,我们知道,如果一个JSAtom的atom_type等于JS_ATOM_TYPE_SYMBOL,那么,它的JSAtom就等于hash_next。如果不等于,我们需要根据它的hash,计算它在atom_hash中存储自己的atom的索引。计算出他在atom_hash中的索引之后,取出它的atom。此时,并不难完全确认这个atom就是它自己的。因为存在hash碰撞的问题,因此,需要遍历它的hash_next,直到完全确认找到的JSString和自己完全一样,才返回JSAtom。

内置JSAtom

QuickJS在初始化时,创建了一些内置的JSAtom。这些是JavaScript关键字或者一些常用的字符串。通过宏定义的方式,引入quickjs-atom.h中的一些宏定义,以在预处理中创建js_atom_init字符串。
quickjs-atom.h

/*
 * QuickJS atom definitions
 * 
 * Copyright (c) 2017-2018 Fabrice Bellard
 * Copyright (c) 2017-2018 Charlie Gordon
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifdef DEF

/* Note: first atoms are considered as keywords in the parser */
DEF(null, "null") /* must be first */
DEF(false, "false")
DEF(true, "true")
DEF(if, "if")
DEF(else, "else")
DEF(return, "return")
DEF(var, "var")
DEF(this, "this")
DEF(delete, "delete")
DEF(void, "void")
DEF(typeof, "typeof")
DEF(new, "new")
DEF(in, "in")
DEF(instanceof, "instanceof")
DEF(do, "do")
DEF(while, "while")
DEF(for, "for")
DEF(break, "break")
DEF(continue, "continue")
DEF(switch, "switch")
DEF(case, "case")
DEF(default, "default")
DEF(throw, "throw")
DEF(try, "try")
DEF(catch, "catch")
DEF(finally, "finally")
DEF(function, "function")
DEF(debugger, "debugger")
DEF(with, "with")
/* FutureReservedWord */
DEF(class, "class")
DEF(const, "const")
DEF(enum, "enum")
DEF(export, "export")
DEF(extends, "extends")
DEF(import, "import")
DEF(super, "super")
/* FutureReservedWords when parsing strict mode code */
DEF(implements, "implements")
DEF(interface, "interface")
DEF(let, "let")
DEF(package, "package")
DEF(private, "private")
DEF(protected, "protected")
DEF(public, "public")
DEF(static, "static")
DEF(yield, "yield")
DEF(await, "await")

/* empty string */
DEF(empty_string, "")
/* identifiers */
DEF(length, "length")
DEF(fileName, "fileName")
DEF(lineNumber, "lineNumber")
DEF(message, "message")
DEF(errors, "errors")
DEF(stack, "stack")
DEF(name, "name")
DEF(toString, "toString")
DEF(toLocaleString, "toLocaleString")
DEF(valueOf, "valueOf")
DEF(eval, "eval")
DEF(prototype, "prototype")
DEF(constructor, "constructor")
DEF(configurable, "configurable")
DEF(writable, "writable")
DEF(enumerable, "enumerable")
DEF(value, "value")
DEF(get, "get")
DEF(set, "set")
DEF(of, "of")
DEF(__proto__, "__proto__")
DEF(undefined, "undefined")
DEF(number, "number")
DEF(boolean, "boolean")
DEF(string, "string")
DEF(object, "object")
DEF(symbol, "symbol")
DEF(integer, "integer")
DEF(unknown, "unknown")
DEF(arguments, "arguments")
DEF(callee, "callee")
DEF(caller, "caller")
DEF(_eval_, "<eval>")
DEF(_ret_, "<ret>")
DEF(_var_, "<var>")
DEF(_arg_var_, "<arg_var>")
DEF(_with_, "<with>")
DEF(lastIndex, "lastIndex")
DEF(target, "target")
DEF(index, "index")
DEF(input, "input")
DEF(defineProperties, "defineProperties")
DEF(apply, "apply")
DEF(join, "join")
DEF(concat, "concat")
DEF(split, "split")
DEF(construct, "construct")
DEF(getPrototypeOf, "getPrototypeOf")
DEF(setPrototypeOf, "setPrototypeOf")
DEF(isExtensible, "isExtensible")
DEF(preventExtensions, "preventExtensions")
DEF(has, "has")
DEF(deleteProperty, "deleteProperty")
DEF(defineProperty, "defineProperty")
DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor")
DEF(ownKeys, "ownKeys")
DEF(add, "add")
DEF(done, "done")
DEF(next, "next")
DEF(values, "values")
DEF(source, "source")
DEF(flags, "flags")
DEF(global, "global")
DEF(unicode, "unicode")
DEF(raw, "raw")
DEF(new_target, "new.target")
DEF(this_active_func, "this.active_func")
DEF(home_object, "<home_object>")
DEF(computed_field, "<computed_field>")
DEF(static_computed_field, "<static_computed_field>") /* must come after computed_fields */
DEF(class_fields_init, "<class_fields_init>")
DEF(brand, "<brand>")
DEF(hash_constructor, "#constructor")
DEF(as, "as")
DEF(from, "from")
DEF(meta, "meta")
DEF(_default_, "*default*")
DEF(_star_, "*")
DEF(Module, "Module")
DEF(then, "then")
DEF(resolve, "resolve")
DEF(reject, "reject")
DEF(promise, "promise")
DEF(proxy, "proxy")
DEF(revoke, "revoke")
DEF(async, "async")
DEF(exec, "exec")
DEF(groups, "groups")
DEF(status, "status")
DEF(reason, "reason")
DEF(globalThis, "globalThis")
#ifdef CONFIG_BIGNUM
DEF(bigint, "bigint")
DEF(bigfloat, "bigfloat")
DEF(bigdecimal, "bigdecimal")
DEF(roundingMode, "roundingMode")
DEF(maximumSignificantDigits, "maximumSignificantDigits")
DEF(maximumFractionDigits, "maximumFractionDigits")
#endif
#ifdef CONFIG_ATOMICS
DEF(not_equal, "not-equal")
DEF(timed_out, "timed-out")
DEF(ok, "ok")
#endif
DEF(toJSON, "toJSON")
/* class names */
DEF(Object, "Object")
DEF(Array, "Array")
DEF(Error, "Error")
DEF(Number, "Number")
DEF(String, "String")
DEF(Boolean, "Boolean")
DEF(Symbol, "Symbol")
DEF(Arguments, "Arguments")
DEF(Math, "Math")
DEF(JSON, "JSON")
DEF(Date, "Date")
DEF(Function, "Function")
DEF(GeneratorFunction, "GeneratorFunction")
DEF(ForInIterator, "ForInIterator")
DEF(RegExp, "RegExp")
DEF(ArrayBuffer, "ArrayBuffer")
DEF(SharedArrayBuffer, "SharedArrayBuffer")
/* must keep same order as class IDs for typed arrays */
DEF(Uint8ClampedArray, "Uint8ClampedArray") 
DEF(Int8Array, "Int8Array")
DEF(Uint8Array, "Uint8Array")
DEF(Int16Array, "Int16Array")
DEF(Uint16Array, "Uint16Array")
DEF(Int32Array, "Int32Array")
DEF(Uint32Array, "Uint32Array")
#ifdef CONFIG_BIGNUM
DEF(BigInt64Array, "BigInt64Array")
DEF(BigUint64Array, "BigUint64Array")
#endif
DEF(Float32Array, "Float32Array")
DEF(Float64Array, "Float64Array")
DEF(DataView, "DataView")
#ifdef CONFIG_BIGNUM
DEF(BigInt, "BigInt")
DEF(BigFloat, "BigFloat")
DEF(BigFloatEnv, "BigFloatEnv")
DEF(BigDecimal, "BigDecimal")
DEF(OperatorSet, "OperatorSet")
DEF(Operators, "Operators")
#endif
DEF(Map, "Map")
DEF(Set, "Set") /* Map + 1 */
DEF(WeakMap, "WeakMap") /* Map + 2 */
DEF(WeakSet, "WeakSet") /* Map + 3 */
DEF(Map_Iterator, "Map Iterator")
DEF(Set_Iterator, "Set Iterator")
DEF(Array_Iterator, "Array Iterator")
DEF(String_Iterator, "String Iterator")
DEF(RegExp_String_Iterator, "RegExp String Iterator")
DEF(Generator, "Generator")
DEF(Proxy, "Proxy")
DEF(Promise, "Promise")
DEF(PromiseResolveFunction, "PromiseResolveFunction")
DEF(PromiseRejectFunction, "PromiseRejectFunction")
DEF(AsyncFunction, "AsyncFunction")
DEF(AsyncFunctionResolve, "AsyncFunctionResolve")
DEF(AsyncFunctionReject, "AsyncFunctionReject")
DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction")
DEF(AsyncGenerator, "AsyncGenerator")
DEF(EvalError, "EvalError")
DEF(RangeError, "RangeError")
DEF(ReferenceError, "ReferenceError")
DEF(SyntaxError, "SyntaxError")
DEF(TypeError, "TypeError")
DEF(URIError, "URIError")
DEF(InternalError, "InternalError")
/* private symbols */
DEF(Private_brand, "<brand>")
/* symbols */
DEF(Symbol_toPrimitive, "Symbol.toPrimitive")
DEF(Symbol_iterator, "Symbol.iterator")
DEF(Symbol_match, "Symbol.match")
DEF(Symbol_matchAll, "Symbol.matchAll")
DEF(Symbol_replace, "Symbol.replace")
DEF(Symbol_search, "Symbol.search")
DEF(Symbol_split, "Symbol.split")
DEF(Symbol_toStringTag, "Symbol.toStringTag")
DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable")
DEF(Symbol_hasInstance, "Symbol.hasInstance")
DEF(Symbol_species, "Symbol.species")
DEF(Symbol_unscopables, "Symbol.unscopables")
DEF(Symbol_asyncIterator, "Symbol.asyncIterator")
#ifdef CONFIG_BIGNUM
DEF(Symbol_operatorSet, "Symbol.operatorSet")
#endif
    
#endif /* DEF */

再来看看如何创建内部JSAtom

enum {
    __JS_ATOM_NULL = JS_ATOM_NULL,
#define DEF(name, str) JS_ATOM_ ## name,
#include "quickjs-atom.h"
#undef DEF
    JS_ATOM_END,
};

static const char js_atom_init[] =
#define DEF(name, str) str "\0"  //使用'\0'截断字符串
#include "quickjs-atom.h"
#undef DEF
;

......

//更改AtomHash的大小
//两个参数,一个是JSRuntime,atom是存储在里面的。
//第二个参数是新的hash数组大小
static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size)
{
    JSAtomStruct *p;//JSAtomStruct即为JSString
    uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash;
    
    // rt->atom_hash 里面装的是字符串在rt->atom_array的索引
    //字符串在rt->atom_array里面是如何存储的?
    //根据rt->atom_free_index获取空白的index,将字符串保存在该位置
    
    //p->hash 存储的是字符串的hash
    //p->hash_next 存储的是rt->atom_array中p的index
    
    //rt->atom_hash 存储的是以字符串的hash为索引的字符串在rt->atom_array中的索引 
    //rt->atom_array 是JSString类型数组,存储的是JSString
    //rt->atom_free_index 保存的是rt->atom_array空白索引位置
    
    // power of 2 ? 2的次方
    assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */
    //size减1
    new_hash_mask = new_hash_size - 1;//什么作用?首先看上一行的断言,目的是判断new_hash_size是不是2的次方,如:2,4,8,16都是2的次方数。但是6,10,12,14就不是2的次方数。如果不是,直接报错了,后面就不运行了。 从二进制上看,一个2的次方的数减一之后,其实就相当于将该2进制数最高位设置为零,然后后面的所有位设置为1。以8为例子,二进制表示是 0b00001000。减一之后是7,用二进制表示是0b00000111。 得到这个数字的作用是:一个数与这个数进行与运算时,将会把这个数的最高位全部设置为0。也就是说,一个数字和new_hash_mask做与操作,得到的数字不会大于new_hash_size,用来限制新生成的数字的大小。
    
    // 给new_hash分配内存,大小是 sizeof(rt->atom_hash[0]) * new_hash_size
    // rt->atom_hash 的定义是 uint32_t *atom_hash
    // 那么显然sizeof(rt->atom_hash[0])等于4byte
    new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size);
    //new_hash内的值都是0
    if (!new_hash)
        return -1;
    //进行内存拷贝同时更新字符串之间的hash链    
    for(i = 0; i < rt->atom_hash_size; i++) {
        //遍历原来的atom_hash数组,根据其存储的索引,建立新atom_hash中的索引
        //h是什么含义?JSRuntime中的atom_hash数组存放字符串hash值到atom_array的映射
        h = rt->atom_hash[i];//获取对应i位置的hash值
        while (h != 0) {
            //根据hash获取对应位置的JSAtomStruct其实就是JSString
            //疑问:这个hash会不会溢出数组?不会,可以看下面的新hash计算的代码。
            // p->hash & new_hash_mask 保证了生成的新hash不会大于new_hash_size
            p = rt->atom_array[h];//获取对应位置的JSAtomStruct其实就是JSString
            hash_next1 = p->hash_next;//暂存下一个hash值,用于下一个遍历
            /* add in new hash table */
            //根据取出来的JSString的hash得到新的atom_array的index
            j = p->hash & new_hash_mask;//根据上面的解释,新的计算新的atom_array中的hash_index,&new_hash_mask操作保证了新的hash不会溢出数组
            //new_hash[j]里面的值可能为0,也可能不为0。
            //这里,根据新的规则,先将new_hash[j]赋值到hash_next中
            p->hash_next = new_hash[j];//将JSAtomStruct的hash_next赋值为new_hash[j]
            new_hash[j] = h;//将原来的字符串在atom_array的索引保存到新的atom_array的新位置中
            h = hash_next1;//根据其原来的hash_next将原来hash相同的字符串全部重新映射一边
        }
    }
    js_free_rt(rt, rt->atom_hash);
    rt->atom_hash = new_hash;
    rt->atom_hash_size = new_hash_size;//
    /* return the max count from the hash size */
    //#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) 
    rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size);
    //    JS_DumpAtoms(rt);
    return 0;
}

......


/* only works with zero terminated 8 bit strings */
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
                               int atom_type)
{
    JSString *p;
    p = js_alloc_string_rt(rt, len, 0);
    if (!p)
        return JS_ATOM_NULL;
    memcpy(p->u.str8, str, len);
    p->u.str8[len] = '\0';
    return __JS_NewAtom(rt, p, atom_type);
}

static int JS_InitAtoms(JSRuntime *rt)
{
    int i, len, atom_type;
    const char *p;

    rt->atom_hash_size = 0;
    rt->atom_hash = NULL;
    rt->atom_count = 0;
    rt->atom_size = 0;
    rt->atom_free_index = 0;
    if (JS_ResizeAtomHash(rt, 256))     /* there are at least 195 predefined atoms */
        return -1;

    p = js_atom_init;
    for(i = 1; i < JS_ATOM_END; i++) {
        if (i == JS_ATOM_Private_brand)
            atom_type = JS_ATOM_TYPE_PRIVATE;
        else if (i >= JS_ATOM_Symbol_toPrimitive)
            atom_type = JS_ATOM_TYPE_SYMBOL;
        else
            atom_type = JS_ATOM_TYPE_STRING;
        len = strlen(p);//计算当前字符串长度
        if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL)
            return -1;
        p = p + len + 1;//指针移向下一个字符串
    }
    return 0;
}

其中JS_ATOM_END作为内置JSAtom结束的标记索引。QuickJS在创建JSRuntime时,会初始化这些内置JSAtom。首先需要清空JSAtom相关字段。然后,为atom_hash分配内存,在这里,我们分配它的初始大小为256。通过分析 JS_ResizeAtomHash 方法的代码,我们可以知道它的平衡因子为2。JS_ResizeAtomHash 对 rt->atom_hash 进行扩展的原理就是根据需要的分配内存大小,申请新的内存块。然后将之前存储的数据,拷贝到新申请的内存中。这中间,因为有hash碰撞的问题,需要更新 rt->atom_array中每一个JSString的hash_next。最后就是遍历js_atom_init,生成这些基础的JSAtom。生成JSAtom调用的是 __JS_NewAtomInit 方法,这个方法,我们前面剖析过,在此不赘述。

结语

至此,我们完整分析了JSString和JSAtom。通过使用JSAtom,有效的节省了内存消耗和提高了字符串比较效率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值