在JavaScript中,变量是弱类型的。但是C是一个强类型语言。但是QuicksJS作为一个使用C语言编写的虚拟机,少不了和JavaScript代码中的变量交互。如何来解决这个问题呢? QuickJS中,使用JSValue来表示这种变量。因此,JSValue是一个JavaScript变量的代表。它既可以是基础类型也可以是一个对象。它用到了引用计数,因此,明确的对它进行复制(JS_DupValue(),添加引用计数)和释放(JS_FreeValue(),减少引用计数)很重要。
先看它的定义。
typedef union JSValueUnion {
int32_t int32;
double float64;
void *ptr;
} JSValueUnion;
typedef struct JSValue {
JSValueUnion u;
int64_t tag;
} JSValue;
enum {
/* all tags with a reference count are negative */
JS_TAG_FIRST = -11, /* first negative tag */
JS_TAG_BIG_DECIMAL = -11,
JS_TAG_BIG_INT = -10,
JS_TAG_BIG_FLOAT = -9,
JS_TAG_SYMBOL = -8,
JS_TAG_STRING = -7,
JS_TAG_MODULE = -3, /* used internally */
JS_TAG_FUNCTION_BYTECODE = -2, /* used internally */
JS_TAG_OBJECT = -1,
JS_TAG_INT = 0,
JS_TAG_BOOL = 1,
JS_TAG_NULL = 2,
JS_TAG_UNDEFINED = 3,
JS_TAG_UNINITIALIZED = 4,
JS_TAG_CATCH_OFFSET = 5,
JS_TAG_EXCEPTION = 6,
JS_TAG_FLOAT64 = 7,
/* any larger tag is FLOAT64 if JS_NAN_BOXING */
};
JSValue是一个结构体,它定义了tag和u两个字段。其中tag,表示它的值的类型,有上述代码中的19种类型。另外一个u是JSValueUnion,用于存储值或指针,分别包含:int32,float64和指针ptr。
#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .int32 = val }, tag }
/* value handling */
static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val)
{
return JS_MKVAL(JS_TAG_BOOL, (val != 0));
}
static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val)
{
return JS_MKVAL(JS_TAG_INT, val);
}
static js_force_inline JSValue JS_NewCatchOffset(JSContext *ctx, int32_t val)
{
return JS_MKVAL(JS_TAG_CATCH_OFFSET, val);
}
static js_force_inline JSValue JS_NewInt64(JSContext *ctx, int64_t val)
{
JSValue v;
if (val == (int32_t)val) {
v = JS_NewInt32(ctx, val);
} else {
v = __JS_NewFloat64(ctx, val);
}
return v;
}
static js_force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val)
{
JSValue v;
if (val <= 0x7fffffff) {
v = JS_NewInt32(ctx, val);
} else {
v = __JS_NewFloat64(ctx, val);
}
return v;
}
数字型的JSValue创建非常简单,使用宏定义,定义了一个结构体。如果是32位的整数,放到JSValueUnion的int32字段中。如果是浮点数字,就放到float64字段中。以JS_NewInt32()为例,上面的宏定义,预处理时,将会变成下面的代码
static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val)
{
return (JSValue){
(JSValueUnion){
.int32 = val
},
JS_TAG_INT
};
}
其中的(JSValueUnion)和(JSValue)其实就是c语言中的强制转换。希望读者在此不要困惑。
//由纯ascii字符构成的字符串,创建JSValue
static JSValue js_new_string8(JSContext *ctx, const uint8_t *buf, int len)
{
JSString *str;
// 如果是空字符串,直接返回JS_ATOM_empty_string
if (len <= 0) {
return JS_AtomToString(ctx, JS_ATOM_empty_string);
}
str = js_alloc_string(ctx, len, 0);
if (!str)
return JS_EXCEPTION;
memcpy(str->u.str8, buf, len);
str->u.str8[len] = '\0';
return JS_MKPTR(JS_TAG_STRING, str);
}
typedef struct StringBuffer {
JSContext *ctx;
JSString *str;
int len;
int size;
int is_wide_char;
int error_status;
} StringBuffer;
/* It is valid to call string_buffer_end() and all string_buffer functions even
if string_buffer_init() or another string_buffer function returns an error.
If the error_status is set, string_buffer_end() returns JS_EXCEPTION.
*/
static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size,
int is_wide)
{
s->ctx = ctx;
s->size = size;
s->len = 0;
s->is_wide_char = is_wide;
s->error_status = 0;
s->str = js_alloc_string(ctx, size, is_wide);
if (unlikely(!s->str)) {
s->size = 0;
return s->error_status = -1;
}
#ifdef DUMP_LEAKS
/* the StringBuffer may reallocate the JSString, only link it at the end */
list_del(&s->str->link);
#endif
return 0;
}
static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size)
{
return string_buffer_init2(ctx, s, size, 0);
}
static void string_buffer_free(StringBuffer *s)
{
js_free(s->ctx, s->str);
s->str = NULL;
}
static int string_buffer_set_error(StringBuffer *s)
{
js_free(s->ctx, s->str);
s->str = NULL;
s->size = 0;
s->len = 0;
return s->error_status = -1;
}
static no_inline int string_buffer_widen(StringBuffer *s, int size)
{
JSString *str;
size_t slack;
int i;
if (s->error_status)
return -1;
str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack);
if (!str)
return string_buffer_set_error(s);
size += slack >> 1;
for(i = s->len; i-- > 0;) {
str->u.str16[i] = str->u.str8[i];
}
s->is_wide_char = 1;
s->size = size;
s->str = str;
return 0;
}
static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c)
{
JSString *new_str;
int new_size;
size_t new_size_bytes, slack;
if (s->error_status)
return -1;
if (new_len > JS_STRING_LEN_MAX) {
JS_ThrowInternalError(s->ctx, "string too long");
return string_buffer_set_error(s);
}
new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX);
if (!s->is_wide_char && c >= 0x100) {
return string_buffer_widen(s, new_size);
}
new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char;
new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack);
if (!new_str)
return string_buffer_set_error(s);
new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX);
s->size = new_size;
s->str = new_str;
return 0;
}
static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c)
{
if (unlikely(s->len >= s->size)) {
if (string_buffer_realloc(s, s->len + 1, c))
return -1;
}
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
} else if (c < 0x100) {
s->str->u.str8[s->len++] = c;
} else {
if (string_buffer_widen(s, s->size))
return -1;
s->str->u.str16[s->len++] = c;
}
return 0;
}
/* 0 <= c <= 0xff */
static int string_buffer_putc8(StringBuffer *s, uint32_t c)
{
if (unlikely(s->len >= s->size)) {
if (string_buffer_realloc(s, s->len + 1, c))
return -1;
}
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
} else {
s->str->u.str8[s->len++] = c;
}
return 0;
}
/* 0 <= c <= 0xffff */
static int string_buffer_putc16(StringBuffer *s, uint32_t c)
{
if (likely(s->len < s->size)) {
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
return 0;
} else if (c < 0x100) {
s->str->u.str8[s->len++] = c;
return 0;
}
}
return string_buffer_putc_slow(s, c);
}
/* 0 <= c <= 0x10ffff */
static int string_buffer_putc(StringBuffer *s, uint32_t c)
{
if (unlikely(c >= 0x10000)) {
/* surrogate pair */
c -= 0x10000;
if (string_buffer_putc16(s, (c >> 10) + 0xd800))
return -1;
c = (c & 0x3ff) + 0xdc00;
}
return string_buffer_putc16(s, c);
}
static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len)
{
int i;
if (s->len + len > s->size) {
if (string_buffer_realloc(s, s->len + len, 0))
return -1;
}
if (s->is_wide_char) {
for (i = 0; i < len; i++) {
s->str->u.str16[s->len + i] = p[i];
}
s->len += len;
} else {
memcpy(&s->str->u.str8[s->len], p, len);
s->len += len;
}
return 0;
}
static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len)
{
int c = 0, i;
for (i = 0; i < len; i++) {
c |= p[i];
}
if (s->len + len > s->size) {
if (string_buffer_realloc(s, s->len + len, c))
return -1;
} else if (!s->is_wide_char && c >= 0x100) {
if (string_buffer_widen(s, s->size))
return -1;
}
if (s->is_wide_char) {
memcpy(&s->str->u.str16[s->len], p, len << 1);
s->len += len;
} else {
for (i = 0; i < len; i++) {
s->str->u.str8[s->len + i] = p[i];
}
s->len += len;
}
return 0;
}
static int string_buffer_concat(StringBuffer *s, const JSString *p,
uint32_t from, uint32_t to)
{
if (to <= from)
return 0;
if (p->is_wide_char)
return string_buffer_write16(s, p->u.str16 + from, to - from);
else
return string_buffer_write8(s, p->u.str8 + from, to - from);
}
static JSValue string_buffer_end(StringBuffer *s)
{
JSString *str;
str = s->str;
if (s->error_status)
return JS_EXCEPTION;
if (s->len == 0) {
js_free(s->ctx, str);
s->str = NULL;
return JS_AtomToString(s->ctx, JS_ATOM_empty_string);
}
if (s->len < s->size) {
/* smaller size so js_realloc should not fail, but OK if it does */
/* XXX: should add some slack to avoid unnecessary calls */
/* XXX: might need to use malloc+free to ensure smaller size */
str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) +
(s->len << s->is_wide_char) + 1 - s->is_wide_char);
if (str == NULL)
str = s->str;
s->str = str;
}
if (!s->is_wide_char)
str->u.str8[s->len] = 0;
#ifdef DUMP_LEAKS
list_add_tail(&str->link, &s->ctx->rt->string_list);
#endif
str->is_wide_char = s->is_wide_char;
str->len = s->len;
s->str = NULL;
return JS_MKPTR(JS_TAG_STRING, str);
}
/* create a string from a UTF-8 buffer */
//根据utf-8编码的字符串创建JSValue(JS_TAG_String)
JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len)
{
const uint8_t *p, *p_end, *p_start, *p_next;
uint32_t c;
StringBuffer b_s, *b = &b_s;
size_t len1;
p_start = (const uint8_t *)buf;
p_end = p_start + buf_len;
p = p_start;
//遍历字符串,直到字符串中非ascii字符部分结束
while (p < p_end && *p < 128)
p++;
len1 = p - p_start;
if (len1 > JS_STRING_LEN_MAX)
return JS_ThrowInternalError(ctx, "string too long");
// 判断字符串是否位纯ascii字符
if (p == p_end) {
/* ASCII string */
//如果是纯ascii,调用js_new_string8来创建JSValue
return js_new_string8(ctx, (const uint8_t *)buf, buf_len);
} else {
if (string_buffer_init(ctx, b, buf_len))
goto fail;
string_buffer_write8(b, p_start, len1);
while (p < p_end) {
if (*p < 128) {
string_buffer_putc8(b, *p++);
} else {
/* parse utf-8 sequence, return 0xFFFFFFFF for error */
c = unicode_from_utf8(p, p_end - p, &p_next);
if (c < 0x10000) {
p = p_next;
} else if (c <= 0x10FFFF) {
p = p_next;
/* surrogate pair */
c -= 0x10000;
string_buffer_putc16(b, (c >> 10) + 0xd800);
c = (c & 0x3ff) + 0xdc00;
} else {
/* invalid char */
c = 0xfffd;
/* skip the invalid chars */
/* XXX: seems incorrect. Why not just use c = *p++; ? */
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
p++;
if (p < p_end) {
p++;
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
p++;
}
}
string_buffer_putc16(b, c);
}
}
}
return string_buffer_end(b);
fail:
string_buffer_free(b);
return JS_EXCEPTION;
}
static JSValue JS_ConcatString3(JSContext *ctx, const char *str1,
JSValue str2, const char *str3)
{
StringBuffer b_s, *b = &b_s;
int len1, len3;
JSString *p;
if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) {
str2 = JS_ToStringFree(ctx, str2);
if (JS_IsException(str2))
goto fail;
}
p = JS_VALUE_GET_STRING(str2);
len1 = strlen(str1);
len3 = strlen(str3);
if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char))
goto fail;
string_buffer_write8(b, (const uint8_t *)str1, len1);
string_buffer_concat(b, p, 0, p->len);
string_buffer_write8(b, (const uint8_t *)str3, len3);
JS_FreeValue(ctx, str2);
return string_buffer_end(b);
fail:
JS_FreeValue(ctx, str2);
return JS_EXCEPTION;
}
JSValue JS_NewString(JSContext *ctx, const char *str)
{
return JS_NewStringLen(ctx, str, strlen(str));
}
JSValue JS_NewAtomString(JSContext *ctx, const char *str)
{
JSAtom atom = JS_NewAtom(ctx, str);
if (atom == JS_ATOM_NULL)
return JS_EXCEPTION;
JSValue val = JS_AtomToString(ctx, atom);
JS_FreeAtom(ctx, atom);
return val;
}
#define ATOM_GET_STR_BUF_SIZE 64
#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }
#define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag)
#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)
#define JS_VALUE_GET_PTR(v) ((v).u.ptr)
//添加JSValue引用计数
static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v)
{
if (JS_VALUE_HAS_REF_COUNT(v)) {
JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
p->ref_count++;
}
return (JSValue)v;
}
static inline BOOL __JS_AtomIsTaggedInt(JSAtom v)
{
return (v & JS_ATOM_TAG_INT) != 0;
}
static inline JSAtom __JS_AtomFromUInt32(uint32_t v)
{
return v | JS_ATOM_TAG_INT;
}
static inline uint32_t __JS_AtomToUInt32(JSAtom atom)
{
return atom & ~JS_ATOM_TAG_INT;
}
static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom)
{
return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom);
}
static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string)
{
char buf[ATOM_GET_STR_BUF_SIZE];
//判断是否是整形,因为JSAtom存储时,发现存储的字符串是整形数字时,直接就转为u32去存储了
if (__JS_AtomIsTaggedInt(atom)) {
snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom));
return JS_NewString(ctx, buf);
} else {
//从字符串池中取出来对应字符串。
JSRuntime *rt = ctx->rt;
JSAtomStruct *p;
assert(atom < rt->atom_size);
p = rt->atom_array[atom];
if (p->atom_type == JS_ATOM_TYPE_STRING) {
goto ret_string;
} else if (force_string) {
if (p->len == 0 && p->is_wide_char != 0) {
/* no description string */
p = rt->atom_array[JS_ATOM_empty_string];
}
ret_string:
// #define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }
// 创建JSValue
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
} else {
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p));
}
}
}
JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom)
{
return __JS_AtomToValue(ctx, atom, FALSE);
}
JSValue JS_AtomToString(JSContext *ctx, JSAtom atom)
{
return __JS_AtomToValue(ctx, atom, TRUE);
}
#define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag)
#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)
#define JS_VALUE_GET_PTR(v) ((v).u.ptr)
//添加JSValue引用计数
static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v)
{
if (JS_VALUE_HAS_REF_COUNT(v)) {
JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
p->ref_count++;
}
return (JSValue)v;
}
void __JS_FreeValue(JSContext *ctx, JSValue v);
void __JS_FreeValue(JSContext *ctx, JSValue v)
{
__JS_FreeValueRT(ctx->rt, v);
}
/* called with the ref_count of 'v' reaches zero. */
void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
{
uint32_t tag = JS_VALUE_GET_TAG(v);
#ifdef DUMP_FREE
{
printf("Freeing ");
if (tag == JS_TAG_OBJECT) {
JS_DumpObject(rt, JS_VALUE_GET_OBJ(v));
} else {
JS_DumpValueShort(rt, v);
printf("\n");
}
}
#endif
switch(tag) {
case JS_TAG_STRING:
{
JSString *p = JS_VALUE_GET_STRING(v);
if (p->atom_type) {
JS_FreeAtomStruct(rt, p);
} else {
#ifdef DUMP_LEAKS
list_del(&p->link);
#endif
js_free_rt(rt, p);
}
}
break;
case JS_TAG_OBJECT:
case JS_TAG_FUNCTION_BYTECODE:
{
JSGCObjectHeader *p = JS_VALUE_GET_PTR(v);
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
list_del(&p->link);
list_add(&p->link, &rt->gc_zero_ref_count_list);
if (rt->gc_phase == JS_GC_PHASE_NONE) {
free_zero_refcount(rt);
}
}
}
break;
case JS_TAG_MODULE:
abort(); /* never freed here */
break;
#ifdef CONFIG_BIGNUM
case JS_TAG_BIG_INT:
case JS_TAG_BIG_FLOAT:
{
JSBigFloat *bf = JS_VALUE_GET_PTR(v);
bf_delete(&bf->num);
js_free_rt(rt, bf);
}
break;
case JS_TAG_BIG_DECIMAL:
{
JSBigDecimal *bf = JS_VALUE_GET_PTR(v);
bfdec_delete(&bf->num);
js_free_rt(rt, bf);
}
break;
#endif
case JS_TAG_SYMBOL:
{
JSAtomStruct *p = JS_VALUE_GET_PTR(v);
JS_FreeAtomStruct(rt, p);
}
break;
default:
printf("__JS_FreeValue: unknown tag=%d\n", tag);
abort();
}
}
static inline void JS_FreeValue(JSContext *ctx, JSValue v)
{
if (JS_VALUE_HAS_REF_COUNT(v)) {
JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v);
if (--p->ref_count <= 0) {
__JS_FreeValue(ctx, v);
}
}
}