shithub: libmujs

Download patch

ref: 68862386d80d02e24bbae650f11be0ec07e11195
parent: e39e0bdcd1bab3e9f07bab9b106fa7f9b97093e5
author: Tor Andersson <tor@ccxvii.net>
date: Mon Feb 10 13:00:13 EST 2014

Implement lightweight functions.

When a function does not use eval, arguments, with and has no inner
functions it does not need a closure. We can call it without creating
a variable object if we store local variables on the stack instead.

--- a/jscompile.c
+++ b/jscompile.c
@@ -102,6 +102,24 @@
 	return F->strlen++;
 }
 
+static void addlocal(JF, const char *name)
+{
+	if (F->varlen >= F->varcap) {
+		F->varcap = F->varcap ? F->varcap * 2 : 16;
+		F->vartab = realloc(F->vartab, F->varcap * sizeof *F->vartab);
+	}
+	F->vartab[F->varlen++] = name;
+}
+
+static int findlocal(JF, const char *name)
+{
+	int i;
+	for (i = 0; i < F->varlen; ++i)
+		if (!strcmp(F->vartab[i], name))
+			return i;
+	return -1;
+}
+
 static void emitfunction(JF, js_Function *fun)
 {
 	emit(J, F, OP_CLOSURE);
@@ -129,6 +147,20 @@
 	emitraw(J, F, addstring(J, F, str));
 }
 
+static void emitlocal(JF, int oploc, int opvar, const char *name)
+{
+	int i;
+	if (F->lightweight) {
+		i = findlocal(J, F, name);
+		if (i >= 0) {
+			emit(J, F, oploc);
+			emitraw(J, F, i + 1);
+			return;
+		}
+	}
+	emitstring(J, F, opvar, name);
+}
+
 static int here(JF)
 {
 	return F->codelen;
@@ -241,7 +273,7 @@
 	switch (lhs->type) {
 	case EXP_IDENTIFIER:
 		cexp(J, F, rhs);
-		emitstring(J, F, OP_SETVAR, lhs->string);
+		emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->string);
 		break;
 	case EXP_INDEX:
 		cexp(J, F, lhs->a);
@@ -267,7 +299,7 @@
 	if (stm->type == STM_FOR_IN_VAR) {
 		if (lhs->b)
 			jsC_error(J, lhs->b, "more than one loop variable in for-in statement");
-		emitstring(J, F, OP_SETVAR, lhs->a->a->string); /* list(var-init(ident)) */
+		emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->a->a->string); /* list(var-init(ident)) */
 		emit(J, F, OP_POP);
 		return;
 	}
@@ -274,7 +306,7 @@
 
 	switch (lhs->type) {
 	case EXP_IDENTIFIER:
-		emitstring(J, F, OP_SETVAR, lhs->string);
+		emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->string);
 		emit(J, F, OP_POP);
 		break;
 	case EXP_INDEX:
@@ -300,7 +332,7 @@
 {
 	switch (lhs->type) {
 	case EXP_IDENTIFIER:
-		emitstring(J, F, OP_GETVAR, lhs->string);
+		emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, lhs->string);
 		if (dup) emit(J, F, OP_DUP);
 		break;
 	case EXP_INDEX:
@@ -326,7 +358,7 @@
 {
 	switch (lhs->type) {
 	case EXP_IDENTIFIER:
-		emitstring(J, F, OP_SETVAR, lhs->string);
+		emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, lhs->string);
 		break;
 	case EXP_INDEX:
 		break;
@@ -351,7 +383,7 @@
 {
 	switch (exp->type) {
 	case EXP_IDENTIFIER:
-		emitstring(J, F, OP_DELVAR, exp->string);
+		emitlocal(J, F, OP_DELLOCAL, OP_DELVAR, exp->string);
 		break;
 	case EXP_INDEX:
 		cexp(J, F, exp->a);
@@ -430,7 +462,7 @@
 		break;
 
 	case EXP_IDENTIFIER:
-		emitstring(J, F, OP_GETVAR, exp->string);
+		emitlocal(J, F, OP_GETLOCAL, OP_GETVAR, exp->string);
 		break;
 
 	case EXP_INDEX:
@@ -818,7 +850,7 @@
 		js_Ast *var = list->a;
 		if (var->b) {
 			cexp(J, F, var->b);
-			emitstring(J, F, OP_SETVAR, var->a->string);
+			emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, var->a->string);
 			emit(J, F, OP_POP);
 		}
 		list = list->b;
@@ -1036,6 +1068,40 @@
 	}
 }
 
+/* Analyze */
+
+static void analyze(JF, js_Ast *node)
+{
+	if (isfun(node->type)) {
+		F->lightweight = 0;
+		return; /* don't scan inner functions */
+	}
+
+	if (node->type == STM_WITH) {
+		F->lightweight = 0;
+	}
+
+	if (node->type == EXP_IDENTIFIER) {
+		if (!strcmp(node->string, "arguments")) {
+			F->lightweight = 0;
+			F->arguments = 1;
+		} else if (!strcmp(node->string, "eval")) {
+			/* eval may only be used as a direct function call */
+			if (!node->parent || node->parent->type != EXP_CALL || node->parent->a != node)
+				js_evalerror(J, "%s:%d: illegal use of 'eval'", J->filename, node->line);
+			F->lightweight = 0;
+		}
+	}
+
+	if (node->type == EXP_VAR)
+		addlocal(J, F, node->a->string);
+
+	if (node->a) analyze(J, F, node->a);
+	if (node->b) analyze(J, F, node->b);
+	if (node->c) analyze(J, F, node->c);
+	if (node->d) analyze(J, F, node->d);
+}
+
 /* Declarations and programs */
 
 static int listlength(js_Ast *list)
@@ -1048,62 +1114,71 @@
 static void cparams(JF, js_Ast *list)
 {
 	F->numparams = listlength(list);
-	F->params = malloc(F->numparams * sizeof *F->params);
-	int i = 0;
 	while (list) {
-		F->params[i++] = list->a->string;
+		addlocal(J, F, list->a->string);
 		list = list->b;
 	}
 }
 
+static void cvardecs(JF)
+{
+	int i;
+	for (i = 0; i < F->varlen; ++i) {
+		emit(J, F, OP_UNDEF);
+		emitstring(J, F, OP_INITVAR, F->vartab[i]);
+	}
+}
+
 static void cfundecs(JF, js_Ast *list)
 {
 	while (list) {
 		js_Ast *stm = list->a;
 		if (stm->type == AST_FUNDEC) {
+			addlocal(J, F, stm->a->string);
 			emitfunction(J, F, newfun(J, stm->a, stm->b, stm->c, 0));
-			emitstring(J, F, OP_FUNDEC, stm->a->string);
+			emitstring(J, F, OP_INITVAR, stm->a->string);
 		}
 		list = list->b;
 	}
 }
 
-static void cvardecs(JF, js_Ast *node)
-{
-	if (node->type == EXP_VAR) {
-		emitstring(J, F, OP_VARDEC, node->a->string);
-	} else if (!isfun(node->type)) {
-		if (node->a) cvardecs(J, F, node->a);
-		if (node->b) cvardecs(J, F, node->b);
-		if (node->c) cvardecs(J, F, node->c);
-		if (node->d) cvardecs(J, F, node->d);
-	}
-}
-
 static void cfunbody(JF, js_Ast *name, js_Ast *params, js_Ast *body)
 {
+	F->lightweight = 1;
+	F->arguments = 0;
+
+	cparams(J, F, params);
+
 	if (name) {
 		F->name = name->string;
-		emitfunction(J, F, F);
-		emitstring(J, F, OP_FUNDEC, name->string);
+		addlocal(J, F, name->string);
 	} else {
 		F->name = "";
 	}
 
-	cparams(J, F, params);
+	if (body)
+		analyze(J, F, body);
 
-	if (F->script)
-		emit(J, F, OP_UNDEF);
-
-	if (body) {
+	if (!F->lightweight) {
+		cvardecs(J, F);
 		cfundecs(J, F, body);
-		cvardecs(J, F, body);
-		cstmlist(J, F, body);
 	}
 
+	if (name) {
+		if (F->lightweight)
+			emit(J, F, OP_CURRENT);
+		else
+			emitfunction(J, F, F);
+		emitlocal(J, F, OP_SETLOCAL, OP_SETVAR, name->string);
+		emit(J, F, OP_POP);
+	}
+
 	if (F->script) {
+		emit(J, F, OP_UNDEF);
+		cstmlist(J, F, body);
 		emit(J, F, OP_RETURN);
 	} else {
+		cstmlist(J, F, body);
 		emit(J, F, OP_UNDEF);
 		emit(J, F, OP_RETURN);
 	}
--- a/jscompile.h
+++ b/jscompile.h
@@ -34,10 +34,14 @@
 	OP_FALSE,
 	OP_THIS,
 	OP_GLOBAL,
+	OP_CURRENT,	/* currently executing function object */
 
-	OP_FUNDEC,	/* <closure> -S- */
-	OP_VARDEC,	/* -S- */
+	OP_INITLOCAL,	/* <value> -N- */
+	OP_GETLOCAL,	/* -N- <value> */
+	OP_SETLOCAL,	/* <value> -N- <value> */
+	OP_DELLOCAL,	/* -N- false */
 
+	OP_INITVAR,	/* <value> -S- */
 	OP_GETVAR,	/* -S- <value> */
 	OP_SETVAR,	/* <value> -S- <value> */
 	OP_DELVAR,	/* -S- <success> */
@@ -115,9 +119,9 @@
 {
 	const char *name;
 	int script;
-
+	int lightweight;
+	int arguments;
 	int numparams;
-	const char **params;
 
 	short *code;
 	int codecap, codelen;
@@ -130,6 +134,9 @@
 
 	const char **strtab;
 	int strcap, strlen;
+
+	const char **vartab;
+	int varcap, varlen;
 
 	const char *filename;
 	int line;
--- a/jsdump.c
+++ b/jsdump.c
@@ -716,11 +716,13 @@
 	short *end = F->code + F->codelen;
 	int i;
 
-	printf("function %p %s (", F, F->name);
-	for (i = 0; i < F->numparams; ++i)
-		printf("%s%s", i > 0 ? ", " : "", F->params[i]);
-	printf(")\n");
+	printf("function %p %s\n", F, F->name);
 	printf("\tsource %s:%d\n", F->filename, F->line);
+	printf("\tlightweight:%d\n", F->lightweight);
+	printf("\tparameters:%d\n", F->numparams);
+	printf("\targuments:%d\n", F->arguments);
+	for (i = 0; i < F->varlen; ++i)
+		printf("\tlocal %d %s\n", i + 1, F->vartab[i]);
 	for (i = 0; i < F->funlen; ++i)
 		printf("\tfunction %p %s\n", F->funtab[i], F->funtab[i]->name);
 	for (i = 0; i < F->strlen; ++i) {
@@ -753,8 +755,7 @@
 			p += 2;
 			break;
 
-		case OP_FUNDEC:
-		case OP_VARDEC:
+		case OP_INITVAR:
 		case OP_GETVAR:
 		case OP_SETVAR:
 		case OP_DELVAR:
@@ -767,6 +768,10 @@
 			ps(F->strtab[*p++]);
 			break;
 
+		case OP_INITLOCAL:
+		case OP_GETLOCAL:
+		case OP_SETLOCAL:
+		case OP_DELLOCAL:
 		case OP_NUMBER_N:
 		case OP_INITPROP_N:
 		case OP_CALL:
--- a/jsfunction.c
+++ b/jsfunction.c
@@ -63,7 +63,7 @@
 		n = strlen("function () { ... }");
 		n += strlen(F->name);
 		for (i = 0; i < F->numparams; ++i)
-			n += strlen(F->params[i]) + 1;
+			n += strlen(F->vartab[i]) + 1;
 		s = malloc(n);
 		strcpy(s, "function ");
 		strcat(s, F->name);
@@ -70,7 +70,7 @@
 		strcat(s, "(");
 		for (i = 0; i < F->numparams; ++i) {
 			if (i > 0) strcat(s, ",");
-			strcat(s, F->params[i]);
+			strcat(s, F->vartab[i]);
 		}
 		strcat(s, ") { ... }");
 		js_pushstring(J, s);
--- a/jsgc.c
+++ b/jsgc.c
@@ -14,10 +14,10 @@
 
 static void jsG_freefunction(js_State *J, js_Function *fun)
 {
-	free(fun->params);
 	free(fun->funtab);
 	free(fun->numtab);
 	free(fun->strtab);
+	free(fun->vartab);
 	free(fun->code);
 	free(fun);
 }
--- a/jsrun.c
+++ b/jsrun.c
@@ -753,6 +753,31 @@
 
 /* Function calls */
 
+static void jsR_calllwfunction(js_State *J, int n, js_Function *F, js_Environment *scope)
+{
+	js_Environment *saveE;
+	js_Value v;
+	int i;
+
+	saveE = J->E;
+
+	J->E = scope;
+
+	if (n > F->numparams) {
+		js_pop(J, F->numparams - n);
+		n = F->numparams;
+	}
+	for (i = n; i < F->varlen; ++i)
+		js_pushundefined(J);
+
+	jsR_run(J, F);
+	v = js_tovalue(J, -1);
+	TOP = --BOT; /* clear stack */
+	js_pushvalue(J, v);
+
+	J->E = saveE;
+}
+
 static void jsR_callfunction(js_State *J, int n, js_Function *F, js_Environment *scope)
 {
 	js_Environment *saveE;
@@ -764,10 +789,10 @@
 	J->E = jsR_newenvironment(J, jsV_newobject(J, JS_COBJECT, NULL), scope);
 	for (i = 0; i < F->numparams; ++i) {
 		if (i < n)
-			js_decvar(J, F->params[i], i + 1);
+			js_decvar(J, F->vartab[i], i + 1);
 		else {
 			js_pushundefined(J);
-			js_decvar(J, F->params[i], -1);
+			js_decvar(J, F->vartab[i], -1);
 			js_pop(J, 1);
 		}
 	}
@@ -822,9 +847,12 @@
 	savebot = BOT;
 	BOT = TOP - n - 1;
 
-	if (obj->type == JS_CFUNCTION)
-		jsR_callfunction(J, n, obj->u.f.function, obj->u.f.scope);
-	else if (obj->type == JS_CSCRIPT)
+	if (obj->type == JS_CFUNCTION) {
+		if (obj->u.f.function->lightweight)
+			jsR_calllwfunction(J, n, obj->u.f.function, obj->u.f.scope);
+		else
+			jsR_callfunction(J, n, obj->u.f.function, obj->u.f.scope);
+	} else if (obj->type == JS_CSCRIPT)
 		jsR_callscript(J, n, obj->u.f.function);
 	else if (obj->type == JS_CCFUNCTION)
 		jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.function);
@@ -992,14 +1020,27 @@
 
 		case OP_THIS: js_copy(J, 0); break;
 		case OP_GLOBAL: js_pushobject(J, J->G); break;
+		case OP_CURRENT: js_currentfunction(J); break;
 
-		case OP_FUNDEC:
-			js_decvar(J, ST[*pc++], -1);
-			js_pop(J, 1);
+		case OP_INITLOCAL:
+			STACK[BOT + *pc++] = STACK[--TOP];
 			break;
 
-		case OP_VARDEC:
-			js_pushundefined(J);
+		case OP_GETLOCAL:
+			CHECKSTACK(1);
+			STACK[TOP++] = STACK[BOT + *pc++];
+			break;
+
+		case OP_SETLOCAL:
+			STACK[BOT + *pc++] = STACK[TOP-1];
+			break;
+
+		case OP_DELLOCAL:
+			++pc;
+			js_pushboolean(J, 0);
+			break;
+
+		case OP_INITVAR:
 			js_decvar(J, ST[*pc++], -1);
 			js_pop(J, 1);
 			break;