ref: 0cbd5326f23bd1106f14f934445df50db7481038
parent: 6afabf445cad0dd9afbc1f5870dba730801f09c0
author: Tor Andersson <tor.andersson@artifex.com>
date: Fri Nov 28 11:02:33 EST 2014
Garbage collect (some) strings. Separate literal/interned and garbage collected string types in js_Value. js_pushintern/js_tointern are convenience functions to push/pop strings and automatically intern them (so that the string pointers are guaranteed to be stable). js_pushliteral should push stable strings (either interned or actual literals). js_pushstring will copy the string into garbage collected memory. The pointer returned by js_tostring is guaranteed to be stable only for as long as the stack slot it came from remains untouched. Some uses will always cause a string to be interned: * Using it as a property name. * Wrapping it in a new String() object. * Strings returned by toString(). ToPrimitive must not clobber the stack, so the result has to be unrooted. * Numbers converted to strings (by js_tostring) Likewise, we have nowhere to store the temporary string here. Passing in a scratch buffer to js_tostring could help this problem. Mostly an issue with array accesses (OP_GETPROP, etc) so an auxiliary function and we don't have to clutter the API needlessly.
--- a/jsdate.c
+++ b/jsdate.c
@@ -414,8 +414,10 @@
t = Now();
else if (top == 2) {
v = js_toprimitive(J, 1, JS_HNONE);
- if (v.type == JS_TSTRING)
- t = parseDateTime(v.u.string);
+ if (v.type == JS_TLITERAL)
+ t = parseDateTime(v.u.literal);
+ else if (v.type == JS_TSTRING)
+ t = parseDateTime(v.u.string->p);
else
t = TimeClip(jsV_tonumber(J, &v));
} else {
--- a/jsdump.c
+++ b/jsdump.c
@@ -799,7 +799,8 @@
case JS_TNULL: printf("null"); break;
case JS_TBOOLEAN: printf(v.u.boolean ? "true" : "false"); break;
case JS_TNUMBER: printf("%.9g", v.u.number); break;
- case JS_TSTRING: printf("'%s'", v.u.string); break;
+ case JS_TLITERAL: printf("'%s'", v.u.literal); break;
+ case JS_TSTRING: printf("'%s'", v.u.string->p); break;
case JS_TOBJECT:
if (v.u.object == J->G) {
printf("[Global]");
--- a/jserror.c
+++ b/jserror.c
@@ -24,14 +24,14 @@
js_pop(J, 1);
if (!strcmp(name, ""))
- js_pushliteral(J, message);
+ js_pushstring(J, message);
else if (!strcmp(message, ""))
- js_pushliteral(J, name);
+ js_pushstring(J, name);
else {
- js_pushliteral(J, name);
- js_pushliteral(J, ": ");
+ js_pushstring(J, name);
+ js_pushstring(J, ": ");
js_concat(J);
- js_pushliteral(J, message);
+ js_pushstring(J, message);
js_concat(J);
}
}
--- a/jsgc.c
+++ b/jsgc.c
@@ -73,6 +73,8 @@
static void jsG_markproperty(js_State *J, int mark, js_Property *node)
{
while (node) {
+ if (node->value.type == JS_TSTRING && node->value.u.string->gcmark != mark)
+ node->value.u.string->gcmark = mark;
if (node->value.type == JS_TOBJECT && node->value.u.object->gcmark != mark)
jsG_markobject(J, mark, node->value.u.object);
if (node->getter && node->getter->gcmark != mark)
@@ -106,6 +108,8 @@
js_Value *v = J->stack;
int n = J->top;
while (n--) {
+ if (v->type == JS_TSTRING && v->u.string->gcmark != mark)
+ v->u.string->gcmark = mark;
if (v->type == JS_TOBJECT && v->u.object->gcmark != mark)
jsG_markobject(J, mark, v->u.object);
++v;
@@ -116,9 +120,10 @@
{
js_Function *fun, *nextfun, **prevnextfun;
js_Object *obj, *nextobj, **prevnextobj;
+ js_String *str, *nextstr, **prevnextstr;
js_Environment *env, *nextenv, **prevnextenv;
- int nenv = 0, nfun = 0, nobj = 0;
- int genv = 0, gfun = 0, gobj = 0;
+ int nenv = 0, nfun = 0, nobj = 0, nstr = 0;
+ int genv = 0, gfun = 0, gobj = 0, gstr = 0;
int mark;
int i;
@@ -190,9 +195,22 @@
++nobj;
}
+ prevnextstr = &J->gcstr;
+ for (str = J->gcstr; str; str = nextstr) {
+ nextstr = str->gcnext;
+ if (str->gcmark != mark) {
+ *prevnextstr = nextstr;
+ js_free(J, str);
+ ++gstr;
+ } else {
+ prevnextstr = &str->gcnext;
+ }
+ ++nstr;
+ }
+
if (report)
- printf("garbage collected: %d/%d envs, %d/%d funs, %d/%d objs\n",
- genv, nenv, gfun, nfun, gobj, nobj);
+ printf("garbage collected: %d/%d envs, %d/%d funs, %d/%d objs, %d/%d strs\n",
+ genv, nenv, gfun, nfun, gobj, nobj, gstr, nstr);
}
void js_freestate(js_State *J)
--- a/jsi.h
+++ b/jsi.h
@@ -40,6 +40,7 @@
typedef struct js_Regexp js_Regexp;
typedef struct js_Value js_Value;
typedef struct js_Object js_Object;
+typedef struct js_String js_String;
typedef struct js_Ast js_Ast;
typedef struct js_Function js_Function;
typedef struct js_Environment js_Environment;
@@ -181,6 +182,7 @@
js_Environment *gcenv;
js_Function *gcfun;
js_Object *gcobj;
+ js_String *gcstr;
/* environments on the call stack but currently not in scope */
int envtop;
--- a/jsintern.c
+++ b/jsintern.c
@@ -17,7 +17,7 @@
js_StringNode *node = js_malloc(J, offsetof(js_StringNode, string) + n + 1);
node->left = node->right = &jsS_sentinel;
node->level = 1;
- strcpy(node->string, string);
+ memcpy(node->string, string, n + 1);
return *result = node->string, node;
}
--- a/jsnumber.c
+++ b/jsnumber.c
@@ -21,6 +21,7 @@
static void Np_toString(js_State *J)
{
+ char buf[32];
js_Object *self = js_toobject(J, 0);
int radix = js_isundefined(J, 1) ? 10 : js_tointeger(J, 1);
if (self->type != JS_CNUMBER) js_typeerror(J, "not a number");
@@ -28,7 +29,7 @@
js_rangeerror(J, "invalid radix");
if (radix != 10)
js_rangeerror(J, "invalid radix");
- js_pushliteral(J, jsV_numbertostring(J, self->u.number));
+ js_pushstring(J, jsV_numbertostring(J, buf, self->u.number));
}
/* Customized ToString() on a number */
--- a/json.c
+++ b/json.c
@@ -193,7 +193,7 @@
if (gap) fmtindent(J, sb, gap, level + 1);
sprintf(buf, "%u", k);
js_getproperty(J, -1, buf);
- if (!fmtvalue(J, sb, buf, gap, level + 1))
+ if (!fmtvalue(J, sb, js_intern(J, buf), gap, level + 1))
js_puts(J, sb, "null");
js_pop(J, 1);
}
--- a/jsproperty.c
+++ b/jsproperty.c
@@ -314,6 +314,7 @@
void jsV_resizearray(js_State *J, js_Object *obj, unsigned int newlen)
{
+ char buf[32];
const char *s;
unsigned int k;
if (newlen < obj->u.a.length) {
@@ -321,12 +322,11 @@
js_Object *it = jsV_newiterator(J, obj, 1);
while ((s = jsV_nextiterator(J, it))) {
k = jsV_numbertouint32(jsV_stringtonumber(J, s));
- if (k >= newlen && !strcmp(s, jsV_numbertostring(J, k)))
+ if (k >= newlen && !strcmp(s, jsV_numbertostring(J, buf, k)))
jsV_delproperty(J, obj, s);
}
} else {
for (k = newlen; k < obj->u.a.length; ++k) {
- char buf[32];
sprintf(buf, "%u", k);
jsV_delproperty(J, obj, buf);
}
--- a/jsrun.c
+++ b/jsrun.c
@@ -15,8 +15,8 @@
static void js_stackoverflow(js_State *J)
{
- STACK[TOP].type = JS_TSTRING;
- STACK[TOP].u.string = "stack overflow";
+ STACK[TOP].type = JS_TLITERAL;
+ STACK[TOP].u.literal = "stack overflow";
++TOP;
js_throw(J);
}
@@ -23,8 +23,8 @@
static void js_outofmemory(js_State *J)
{
- STACK[TOP].type = JS_TSTRING;
- STACK[TOP].u.string = "out of memory";
+ STACK[TOP].type = JS_TLITERAL;
+ STACK[TOP].u.literal = "out of memory";
++TOP;
js_throw(J);
}
@@ -50,6 +50,18 @@
J->alloc(J->actx, ptr, 0);
}
+static js_String *jsR_newstring(js_State *J, const char *s, int n)
+{
+ js_String *v = js_malloc(J, offsetof(js_String, p) + n + 1);
+ memcpy(v->p, s, n);
+ v->p[n] = 0;
+ v->gcmark = 0;
+ v->gcnext = J->gcstr;
+ J->gcstr = v;
+ ++J->gccounter;
+ return v;
+}
+
#define CHECKSTACK(n) if (TOP + n >= JS_STACKSIZE) js_stackoverflow(J)
void js_pushvalue(js_State *J, js_Value v)
@@ -93,39 +105,34 @@
{
CHECKSTACK(1);
STACK[TOP].type = JS_TSTRING;
- STACK[TOP].u.string = js_intern(J, v);
+ STACK[TOP].u.string = jsR_newstring(J, v, strlen(v));
++TOP;
}
void js_pushlstring(js_State *J, const char *v, unsigned int n)
{
- char buf[256];
- if (n + 1 < sizeof buf) {
- memcpy(buf, v, n);
- buf[n] = 0;
- js_pushstring(J, buf);
- } else {
- char *s = js_malloc(J, n + 1);
- memcpy(s, v, n);
- s[n] = 0;
- if (js_try(J)) {
- js_free(J, s);
- js_throw(J);
- }
- js_pushstring(J, s);
- js_endtry(J);
- js_free(J, s);
- }
+ CHECKSTACK(1);
+ STACK[TOP].type = JS_TSTRING;
+ STACK[TOP].u.string = jsR_newstring(J, v, n);
+ ++TOP;
}
void js_pushliteral(js_State *J, const char *v)
{
CHECKSTACK(1);
- STACK[TOP].type = JS_TSTRING;
- STACK[TOP].u.string = v;
+ STACK[TOP].type = JS_TLITERAL;
+ STACK[TOP].u.literal = v;
++TOP;
}
+void js_pushintern(js_State *J, const char *v)
+{
+ CHECKSTACK(1);
+ STACK[TOP].type = JS_TLITERAL;
+ STACK[TOP].u.literal = js_intern(J, v);
+ ++TOP;
+}
+
void js_pushobject(js_State *J, js_Object *v)
{
CHECKSTACK(1);
@@ -162,7 +169,7 @@
int js_isnull(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TNULL; }
int js_isboolean(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TBOOLEAN; }
int js_isnumber(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TNUMBER; }
-int js_isstring(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TSTRING; }
+int js_isstring(js_State *J, int idx) { enum js_Type t = stackidx(J, idx)->type; return t == JS_TLITERAL || t == JS_TSTRING; }
int js_isprimitive(js_State *J, int idx) { return stackidx(J, idx)->type != JS_TOBJECT; }
int js_isobject(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TOBJECT; }
@@ -211,6 +218,7 @@
case JS_TNULL: return "object";
case JS_TBOOLEAN: return "boolean";
case JS_TNUMBER: return "number";
+ case JS_TLITERAL: return "string";
case JS_TSTRING: return "string";
case JS_TOBJECT:
if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION)
@@ -264,6 +272,11 @@
return jsV_tostring(J, stackidx(J, idx));
}
+const char *js_tointern(js_State *J, int idx)
+{
+ return js_intern(J, jsV_tostring(J, stackidx(J, idx)));
+}
+
js_Object *js_toobject(js_State *J, int idx)
{
return jsV_toobject(J, stackidx(J, idx));
@@ -1085,6 +1098,7 @@
int offset;
const char *str;
+ char buf[32];
js_Object *obj;
double x, y;
unsigned int ux, uy;
@@ -1212,7 +1226,7 @@
break;
case OP_INITPROP_N:
- str = jsV_numbertostring(J, *pc++);
+ str = jsV_numbertostring(J, buf, *pc++);
obj = js_toobject(J, -2);
jsR_setproperty(J, obj, str, stackidx(J, -1));
js_pop(J, 1);
--- a/jsstring.c
+++ b/jsstring.c
@@ -55,7 +55,7 @@
static void jsB_String(js_State *J)
{
- js_pushliteral(J, js_gettop(J) > 1 ? js_tostring(J, 1) : "");
+ js_pushstring(J, js_gettop(J) > 1 ? js_tostring(J, 1) : "");
}
static void Sp_toString(js_State *J)
--- a/jsvalue.c
+++ b/jsvalue.c
@@ -4,6 +4,9 @@
#include "jsvalue.h"
#include "utf.h"
+#define JSV_ISSTRING(v) (v.type==JS_TSTRING || v.type==JS_TLITERAL)
+#define JSV_TOSTRING(v) (v.type==JS_TSTRING ? v.u.string->p : v.type==JS_TLITERAL ? v.u.literal : NULL)
+
double jsV_numbertointeger(double n)
{
double sign = n < 0 ? -1 : 1;
@@ -94,6 +97,10 @@
if (preferred == JS_HSTRING) {
if (jsV_toString(J, obj) || jsV_valueOf(J, obj)) {
vv = js_tovalue(J, -1);
+ if (vv.type == JS_TSTRING) { /* don't return gc'd string not on stack */
+ vv.type = JS_TLITERAL;
+ vv.u.literal = js_intern(J, vv.u.string->p);
+ }
js_pop(J, 1);
return vv;
}
@@ -100,6 +107,10 @@
} else {
if (jsV_valueOf(J, obj) || jsV_toString(J, obj)) {
vv = js_tovalue(J, -1);
+ if (vv.type == JS_TSTRING) { /* don't return gc'd string not on stack */
+ vv.type = JS_TLITERAL;
+ vv.u.literal = js_intern(J, vv.u.string->p);
+ }
js_pop(J, 1);
return vv;
}
@@ -117,7 +128,8 @@
case JS_TNULL: return 0;
case JS_TBOOLEAN: return v->u.boolean;
case JS_TNUMBER: return v->u.number != 0 && !isnan(v->u.number);
- case JS_TSTRING: return v->u.string[0] != 0;
+ case JS_TLITERAL: return v->u.literal[0] != 0;
+ case JS_TSTRING: return v->u.string->p[0] != 0;
case JS_TOBJECT: return 1;
}
}
@@ -180,7 +192,8 @@
case JS_TNULL: return 0;
case JS_TBOOLEAN: return v->u.boolean;
case JS_TNUMBER: return v->u.number;
- case JS_TSTRING: return jsV_stringtonumber(J, v->u.string);
+ case JS_TLITERAL: return jsV_stringtonumber(J, v->u.literal);
+ case JS_TSTRING: return jsV_stringtonumber(J, v->u.string->p);
case JS_TOBJECT:
{
js_Value vv = jsV_toprimitive(J, v, JS_HNUMBER);
@@ -195,9 +208,9 @@
}
/* ToString() on a number */
-const char *jsV_numbertostring(js_State *J, double f)
+const char *jsV_numbertostring(js_State *J, char buf[32], double f)
{
- char buf[32], digits[32], *p = buf, *s = digits;
+ char digits[32], *p = buf, *s = digits;
int exp, neg, ndigits, point;
if (isnan(f)) return "NaN";
@@ -242,19 +255,21 @@
*p = 0;
}
- return js_intern(J, buf);
+ return buf;
}
/* ToString() on a value */
const char *jsV_tostring(js_State *J, const js_Value *v)
{
+ char buf[32];
switch (v->type) {
default:
case JS_TUNDEFINED: return "undefined";
case JS_TNULL: return "null";
case JS_TBOOLEAN: return v->u.boolean ? "true" : "false";
- case JS_TNUMBER: return jsV_numbertostring(J, v->u.number);
- case JS_TSTRING: return v->u.string;
+ case JS_TNUMBER: return js_intern(J, jsV_numbertostring(J, buf, v->u.number)); /* TODO: no intern here */
+ case JS_TLITERAL: return v->u.literal;
+ case JS_TSTRING: return v->u.string->p;
case JS_TOBJECT:
{
js_Value vv = jsV_toprimitive(J, v, JS_HSTRING);
@@ -282,7 +297,7 @@
static js_Object *jsV_newstring(js_State *J, const char *v)
{
js_Object *obj = jsV_newobject(J, JS_CSTRING, J->String_prototype);
- obj->u.s.string = v;
+ obj->u.s.string = js_intern(J, v); /* TODO: js_String */
obj->u.s.length = utflen(v);
return obj;
}
@@ -296,7 +311,8 @@
case JS_TNULL: js_typeerror(J, "cannot convert null to object");
case JS_TBOOLEAN: return jsV_newboolean(J, v->u.boolean);
case JS_TNUMBER: return jsV_newnumber(J, v->u.number);
- case JS_TSTRING: return jsV_newstring(J, v->u.string);
+ case JS_TLITERAL: return jsV_newstring(J, v->u.literal);
+ case JS_TSTRING: return jsV_newstring(J, v->u.string->p);
case JS_TOBJECT: return v->u.object;
}
}
@@ -435,9 +451,10 @@
{
js_Value va = js_toprimitive(J, -2, JS_HNONE);
js_Value vb = js_toprimitive(J, -1, JS_HNONE);
- if (va.type == JS_TSTRING || vb.type == JS_TSTRING) {
+ if (JSV_ISSTRING(va) || JSV_ISSTRING(vb)) {
const char *sa = jsV_tostring(J, &va);
const char *sb = jsV_tostring(J, &vb);
+ /* TODO: create js_String directly */
char *sab = js_malloc(J, strlen(sa) + strlen(sb) + 1);
strcpy(sab, sa);
strcat(sab, sb);
@@ -463,8 +480,8 @@
js_Value vb = js_toprimitive(J, -1, JS_HNUMBER);
*okay = 1;
- if (va.type == JS_TSTRING && vb.type == JS_TSTRING) {
- return strcmp(va.u.string, vb.u.string);
+ if (JSV_ISSTRING(va) && JSV_ISSTRING(vb)) {
+ return strcmp(JSV_TOSTRING(va), JSV_TOSTRING(vb));
} else {
double x = jsV_tonumber(J, &va);
double y = jsV_tonumber(J, &vb);
@@ -480,12 +497,13 @@
js_Value y = js_tovalue(J, -1);
retry:
- if (x.type == y.type) {
+ if (JSV_ISSTRING(x) && JSV_ISSTRING(y)) {
+ return !strcmp(JSV_TOSTRING(x), JSV_TOSTRING(y));
+ } else if (x.type == y.type) {
if (x.type == JS_TUNDEFINED) return 1;
if (x.type == JS_TNULL) return 1;
if (x.type == JS_TNUMBER) return x.u.number == y.u.number;
if (x.type == JS_TBOOLEAN) return x.u.boolean == y.u.boolean;
- if (x.type == JS_TSTRING) return !strcmp(x.u.string, y.u.string);
if (x.type == JS_TOBJECT) return x.u.object == y.u.object;
return 0;
}
@@ -493,9 +511,9 @@
if (x.type == JS_TNULL && y.type == JS_TUNDEFINED) return 1;
if (x.type == JS_TUNDEFINED && y.type == JS_TNULL) return 1;
- if (x.type == JS_TNUMBER && y.type == JS_TSTRING)
+ if (x.type == JS_TNUMBER && JSV_ISSTRING(y))
return x.u.number == jsV_tonumber(J, &y);
- if (x.type == JS_TSTRING && y.type == JS_TNUMBER)
+ if (JSV_ISSTRING(x) && y.type == JS_TNUMBER)
return jsV_tonumber(J, &x) == y.u.number;
if (x.type == JS_TBOOLEAN) {
@@ -508,11 +526,11 @@
y.u.number = y.u.boolean;
goto retry;
}
- if ((x.type == JS_TSTRING || x.type == JS_TNUMBER) && y.type == JS_TOBJECT) {
+ if ((JSV_ISSTRING(x) || x.type == JS_TNUMBER) && y.type == JS_TOBJECT) {
y = jsV_toprimitive(J, &y, JS_HNONE);
goto retry;
}
- if (x.type == JS_TOBJECT && (y.type == JS_TSTRING || y.type == JS_TNUMBER)) {
+ if (x.type == JS_TOBJECT && (JSV_ISSTRING(y) || y.type == JS_TNUMBER)) {
x = jsV_toprimitive(J, &x, JS_HNONE);
goto retry;
}
@@ -525,12 +543,14 @@
js_Value va = js_tovalue(J, -2);
js_Value vb = js_tovalue(J, -1);
+ if (JSV_ISSTRING(va) && JSV_ISSTRING(vb))
+ return !strcmp(JSV_TOSTRING(va), JSV_TOSTRING(vb));
+
if (va.type != vb.type) return 0;
if (va.type == JS_TUNDEFINED) return 1;
if (va.type == JS_TNULL) return 1;
if (va.type == JS_TNUMBER) return va.u.number == vb.u.number;
if (va.type == JS_TBOOLEAN) return va.u.boolean == vb.u.boolean;
- if (va.type == JS_TSTRING) return !strcmp(va.u.string, vb.u.string);
if (va.type == JS_TOBJECT) return va.u.object == vb.u.object;
return 0;
}
--- a/jsvalue.h
+++ b/jsvalue.h
@@ -1,5 +1,5 @@
-#ifndef js_object_h
-#define js_object_h
+#ifndef js_value_h
+#define js_value_h
typedef struct js_Property js_Property;
typedef struct js_Iterator js_Iterator;
@@ -16,6 +16,7 @@
JS_TNULL,
JS_TBOOLEAN,
JS_TNUMBER,
+ JS_TLITERAL,
JS_TSTRING,
JS_TOBJECT,
};
@@ -44,11 +45,19 @@
union {
int boolean;
double number;
- const char *string;
+ const char *literal;
+ js_String *string;
js_Object *object;
} u;
};
+struct js_String
+{
+ js_String *gcnext;
+ char gcmark;
+ char p[1];
+};
+
struct js_Regexp
{
void *prog;
@@ -137,7 +146,7 @@
unsigned int jsV_numbertouint32(double n);
short jsV_numbertoint16(double n);
unsigned short jsV_numbertouint16(double n);
-const char *jsV_numbertostring(js_State *J, double number);
+const char *jsV_numbertostring(js_State *J, char buf[32], double number);
double jsV_stringtonumber(js_State *J, const char *string);
/* jsproperty.c */
--- a/mujs.h
+++ b/mujs.h
@@ -114,6 +114,7 @@
void js_pushstring(js_State *J, const char *v);
void js_pushlstring(js_State *J, const char *v, unsigned int n);
void js_pushliteral(js_State *J, const char *v);
+void js_pushintern(js_State *J, const char *v);
void js_newobject(js_State *J);
void js_newarray(js_State *J);
@@ -144,6 +145,7 @@
int js_toboolean(js_State *J, int idx);
double js_tonumber(js_State *J, int idx);
const char *js_tostring(js_State *J, int idx);
+const char *js_tointern(js_State *J, int idx);
void *js_touserdata(js_State *J, int idx, const char *tag);
double js_tointeger(js_State *J, int idx);