shithub: libmujs

Download patch

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);