Browse Source

Added constant folding to variable assignments. Avoid using references where possible.

Dmitry Panov 3 years ago
parent
commit
b09a6bfa84
6 changed files with 255 additions and 89 deletions
  1. 8 5
      compiler.go
  2. 32 39
      compiler_expr.go
  3. 36 28
      compiler_stmt.go
  4. 145 0
      compiler_test.go
  5. 17 17
      vm.go
  6. 17 0
      vm_test.go

+ 8 - 5
compiler.go

@@ -395,10 +395,8 @@ func (p *Program) addSrcMap(srcPos int) {
 func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics bool) {
 	noDynamics = true
 	toStash := false
-	for curScope := s; curScope != nil; curScope = curScope.outer {
-		if curScope.dynamic {
-			noDynamics = false
-		} else {
+	for curScope := s; ; curScope = curScope.outer {
+		if curScope.outer != nil {
 			if b, exists := curScope.boundNames[name]; exists {
 				if toStash && !b.inStash {
 					b.moveToStash()
@@ -406,6 +404,12 @@ func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics
 				binding = b
 				return
 			}
+		} else {
+			noDynamics = false
+			return
+		}
+		if curScope.dynamic {
+			noDynamics = false
 		}
 		if name == "arguments" && curScope.function && !curScope.arrow {
 			curScope.argsNeeded = true
@@ -416,7 +420,6 @@ func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics
 			toStash = true
 		}
 	}
-	return
 }
 
 func (s *scope) ensureBoundNamesCreated() {

+ 32 - 39
compiler_expr.go

@@ -395,11 +395,11 @@ func (e *compiledIdentifierExpr) emitGetterAndCallee() {
 func (e *compiledIdentifierExpr) emitVarSetter1(putOnStack bool, emitRight func(isRef bool)) {
 	e.addSrcMap()
 	c := e.c
-	if c.scope.strict {
-		c.checkIdentifierLName(e.name, e.offset)
-	}
 
 	if b, noDynamics := c.scope.lookupName(e.name); noDynamics {
+		if c.scope.strict {
+			c.checkIdentifierLName(e.name, e.offset)
+		}
 		emitRight(false)
 		if b != nil {
 			if putOnStack {
@@ -418,15 +418,7 @@ func (e *compiledIdentifierExpr) emitVarSetter1(putOnStack bool, emitRight func(
 			}
 		}
 	} else {
-		if b != nil {
-			b.emitResolveVar(c.scope.strict)
-		} else {
-			if c.scope.strict {
-				c.emit(resolveVar1Strict(e.name))
-			} else {
-				c.emit(resolveVar1(e.name))
-			}
-		}
+		c.emitVarRef(e.name, e.offset, b)
 		emitRight(true)
 		if putOnStack {
 			c.emit(putValue)
@@ -438,16 +430,15 @@ func (e *compiledIdentifierExpr) emitVarSetter1(putOnStack bool, emitRight func(
 
 func (e *compiledIdentifierExpr) emitVarSetter(valueExpr compiledExpr, putOnStack bool) {
 	e.emitVarSetter1(putOnStack, func(bool) {
-		e.c.emitExpr(valueExpr, true)
+		e.c.emitNamedOrConst(valueExpr, e.name)
 	})
 }
 
-func (c *compiler) emitVarRef(name unistring.String, offset int) {
+func (c *compiler) emitVarRef(name unistring.String, offset int, b *binding) {
 	if c.scope.strict {
 		c.checkIdentifierLName(name, offset)
 	}
 
-	b, _ := c.scope.lookupName(name)
 	if b != nil {
 		b.emitResolveVar(c.scope.strict)
 	} else {
@@ -460,7 +451,8 @@ func (c *compiler) emitVarRef(name unistring.String, offset int) {
 }
 
 func (e *compiledIdentifierExpr) emitRef() {
-	e.c.emitVarRef(e.name, e.offset)
+	b, _ := e.c.scope.lookupName(e.name)
+	e.c.emitVarRef(e.name, e.offset, b)
 }
 
 func (e *compiledIdentifierExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) {
@@ -773,13 +765,6 @@ func (e *deleteGlobalExpr) emitGetter(putOnStack bool) {
 func (e *compiledAssignExpr) emitGetter(putOnStack bool) {
 	switch e.operator {
 	case token.ASSIGN:
-		if fn, ok := e.right.(*compiledFunctionLiteral); ok {
-			if fn.name == nil {
-				if id, ok := e.left.(*compiledIdentifierExpr); ok {
-					fn.lhsName = id.name
-				}
-			}
-		}
 		e.left.emitSetter(e.right, putOnStack)
 	case token.PLUS:
 		e.left.emitUnary(nil, func() {
@@ -1065,14 +1050,14 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 					}
 				}, item.Initializer, item.Target.Idx0()).emitGetter(true)
 				e.c.emitPattern(pattern, func(target, init compiledExpr) {
-					e.c.emitPatternLexicalAssign(target, init, false)
+					e.c.emitPatternLexicalAssign(target, init)
 				}, false)
 			} else if item.Initializer != nil {
 				markGet := len(e.c.p.code)
 				e.c.emit(nil)
 				mark := len(e.c.p.code)
 				e.c.emit(nil)
-				e.c.compileExpression(item.Initializer).emitGetter(true)
+				e.c.emitExpr(e.c.compileExpression(item.Initializer), true)
 				if firstForwardRef == -1 && (s.isDynamic() || s.bindings[i].useCount() > 0) {
 					firstForwardRef = i
 				}
@@ -1100,7 +1085,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 					e.c.emit(createArgsRestStack(paramsCount))
 				}, rest.Idx0()),
 				func(target, init compiledExpr) {
-					e.c.emitPatternLexicalAssign(target, init, false)
+					e.c.emitPatternLexicalAssign(target, init)
 				})
 		}
 		if firstForwardRef != -1 {
@@ -1471,14 +1456,6 @@ func (c *compiler) emitConst(expr compiledExpr, putOnStack bool) {
 	}
 }
 
-func (c *compiler) emitExpr(expr compiledExpr, putOnStack bool) {
-	if expr.constant() {
-		c.emitConst(expr, putOnStack)
-	} else {
-		expr.emitGetter(putOnStack)
-	}
-}
-
 func (c *compiler) evalConst(expr compiledExpr) (Value, *Exception) {
 	if expr, ok := expr.(*compiledLiteral); ok {
 		return expr.val, nil
@@ -1825,7 +1802,7 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) {
 			}
 			if computed {
 				e.c.emit(_toPropertyKey{})
-				valueExpr.emitGetter(true)
+				e.c.emitExpr(valueExpr, true)
 				switch prop.Kind {
 				case ast.PropertyKindValue, ast.PropertyKindMethod:
 					if anonFn != nil {
@@ -1852,7 +1829,7 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) {
 				if anonFn != nil && !isProto {
 					anonFn.lhsName = key
 				}
-				valueExpr.emitGetter(true)
+				e.c.emitExpr(valueExpr, true)
 				switch prop.Kind {
 				case ast.PropertyKindValue:
 					if isProto {
@@ -1912,7 +1889,7 @@ func (e *compiledArrayLiteral) emitGetter(putOnStack bool) {
 			e.c.emit(pushArraySpread)
 		} else {
 			if v != nil {
-				e.c.compileExpression(v).emitGetter(true)
+				e.c.emitExpr(e.c.compileExpression(v), true)
 			} else {
 				e.c.emit(loadNil)
 			}
@@ -2195,6 +2172,14 @@ func (e *compiledArrayAssignmentPattern) emitGetter(putOnStack bool) {
 	}
 }
 
+func (c *compiler) emitExpr(expr compiledExpr, putOnStack bool) {
+	if expr.constant() {
+		c.emitConst(expr, putOnStack)
+	} else {
+		expr.emitGetter(putOnStack)
+	}
+}
+
 func (c *compiler) emitNamed(expr compiledExpr, name unistring.String) {
 	if en, ok := expr.(interface {
 		emitNamed(name unistring.String)
@@ -2205,6 +2190,14 @@ func (c *compiler) emitNamed(expr compiledExpr, name unistring.String) {
 	}
 }
 
+func (c *compiler) emitNamedOrConst(expr compiledExpr, name unistring.String) {
+	if expr.constant() {
+		c.emitConst(expr, true)
+	} else {
+		c.emitNamed(expr, name)
+	}
+}
+
 func (e *compiledFunctionLiteral) emitNamed(name unistring.String) {
 	e.lhsName = name
 	e.emitGetter(true)
@@ -2327,7 +2320,7 @@ func (e *compiledPatternInitExpr) emitGetter(putOnStack bool) {
 	if e.def != nil {
 		mark := len(e.c.p.code)
 		e.c.emit(nil)
-		e.def.emitGetter(true)
+		e.c.emitExpr(e.def, true)
 		e.c.p.code[mark] = jdef(len(e.c.p.code) - mark)
 	}
 }
@@ -2337,7 +2330,7 @@ func (e *compiledPatternInitExpr) emitNamed(name unistring.String) {
 	if e.def != nil {
 		mark := len(e.c.p.code)
 		e.c.emit(nil)
-		e.c.emitNamed(e.def, name)
+		e.c.emitNamedOrConst(e.def, name)
 		e.c.p.code[mark] = jdef(len(e.c.p.code) - mark)
 	}
 }

+ 36 - 28
compiler_stmt.go

@@ -9,7 +9,6 @@ import (
 )
 
 func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
-	// log.Printf("compileStatement(): %T", v)
 
 	switch v := v.(type) {
 	case *ast.BlockStatement:
@@ -158,7 +157,7 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
 			if pattern, ok := v.Catch.Parameter.(ast.Pattern); ok {
 				c.scope.bindings[0].emitGet()
 				c.emitPattern(pattern, func(target, init compiledExpr) {
-					c.emitPatternLexicalAssign(target, init, false)
+					c.emitPatternLexicalAssign(target, init)
 				}, false)
 			}
 			for _, decl := range funcs {
@@ -392,7 +391,7 @@ func (c *compiler) compileForInto(into ast.ForInto, needResult bool) (enter *ent
 			c.createLexicalBinding(target, into.IsConst)
 			c.emit(enumGet)
 			c.emitPattern(target, func(target, init compiledExpr) {
-				c.emitPatternLexicalAssign(target, init, into.IsConst)
+				c.emitPatternLexicalAssign(target, init)
 			}, false)
 		default:
 			c.throwSyntaxError(int(into.Idx)-1, "Unsupported ForBinding: %T", into.Target)
@@ -725,7 +724,7 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
 
 func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
 	if v.Argument != nil {
-		c.compileExpression(v.Argument).emitGetter(true)
+		c.emitExpr(c.compileExpression(v.Argument), true)
 	} else {
 		c.emit(loadUndef)
 	}
@@ -754,10 +753,17 @@ func (c *compiler) checkVarConflict(name unistring.String, offset int) {
 func (c *compiler) emitVarAssign(name unistring.String, offset int, init compiledExpr) {
 	c.checkVarConflict(name, offset)
 	if init != nil {
-		c.emitVarRef(name, offset)
-		c.emitNamed(init, name)
-		c.p.addSrcMap(offset)
-		c.emit(initValueP)
+		b, noDyn := c.scope.lookupName(name)
+		if noDyn {
+			c.emitNamedOrConst(init, name)
+			c.p.addSrcMap(offset)
+			b.emitInit()
+		} else {
+			c.emitVarRef(name, offset, b)
+			c.emitNamedOrConst(init, name)
+			c.p.addSrcMap(offset)
+			c.emit(initValueP)
+		}
 	}
 }
 
@@ -773,16 +779,16 @@ func (c *compiler) compileVarBinding(expr *ast.Binding) {
 	}
 }
 
-func (c *compiler) emitLexicalAssign(name unistring.String, offset int, init compiledExpr, isConst bool) {
+func (c *compiler) emitLexicalAssign(name unistring.String, offset int, init compiledExpr) {
 	b := c.scope.boundNames[name]
 	if b == nil {
 		panic("Lexical declaration for an unbound name")
 	}
 	if init != nil {
-		c.emitNamed(init, name)
+		c.emitNamedOrConst(init, name)
 		c.p.addSrcMap(offset)
 	} else {
-		if isConst {
+		if b.isConst {
 			c.throwSyntaxError(offset, "Missing initializer in const declaration")
 		}
 		c.emit(loadUndef)
@@ -799,29 +805,37 @@ func (c *compiler) emitPatternVarAssign(target, init compiledExpr) {
 	c.emitVarAssign(id.name, id.offset, init)
 }
 
-func (c *compiler) emitPatternLexicalAssign(target, init compiledExpr, isConst bool) {
+func (c *compiler) emitPatternLexicalAssign(target, init compiledExpr) {
 	id := target.(*compiledIdentifierExpr)
-	c.emitLexicalAssign(id.name, id.offset, init, isConst)
+	c.emitLexicalAssign(id.name, id.offset, init)
 }
 
 func (c *compiler) emitPatternAssign(target, init compiledExpr) {
-	target.emitRef()
 	if id, ok := target.(*compiledIdentifierExpr); ok {
-		c.emitNamed(init, id.name)
+		b, noDyn := c.scope.lookupName(id.name)
+		if noDyn {
+			c.emitNamedOrConst(init, id.name)
+			b.emitSetP()
+		} else {
+			c.emitVarRef(id.name, id.offset, b)
+			c.emitNamedOrConst(init, id.name)
+			c.emit(putValueP)
+		}
 	} else {
-		init.emitGetter(true)
+		target.emitRef()
+		c.emitExpr(init, true)
+		c.emit(putValueP)
 	}
-	c.emit(initValueP)
 }
 
-func (c *compiler) compileLexicalBinding(expr *ast.Binding, isConst bool) {
+func (c *compiler) compileLexicalBinding(expr *ast.Binding) {
 	switch target := expr.Target.(type) {
 	case *ast.Identifier:
-		c.emitLexicalAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer), isConst)
+		c.emitLexicalAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer))
 	case ast.Pattern:
 		c.compileExpression(expr.Initializer).emitGetter(true)
 		c.emitPattern(target, func(target, init compiledExpr) {
-			c.emitPatternLexicalAssign(target, init, isConst)
+			c.emitPatternLexicalAssign(target, init)
 		}, false)
 	default:
 		c.throwSyntaxError(int(target.Idx0()-1), "unsupported lexical binding target: %T", target)
@@ -835,9 +849,8 @@ func (c *compiler) compileVariableStatement(v *ast.VariableStatement) {
 }
 
 func (c *compiler) compileLexicalDeclaration(v *ast.LexicalDeclaration) {
-	isConst := v.Token == token.CONST
 	for _, e := range v.List {
-		c.compileLexicalBinding(e, isConst)
+		c.compileLexicalBinding(e)
 	}
 }
 
@@ -964,12 +977,7 @@ func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool)
 }
 
 func (c *compiler) compileExpressionStatement(v *ast.ExpressionStatement, needResult bool) {
-	expr := c.compileExpression(v.Expression)
-	if expr.constant() {
-		c.emitConst(expr, needResult)
-	} else {
-		expr.emitGetter(needResult)
-	}
+	c.emitExpr(c.compileExpression(v.Expression), needResult)
 	if needResult {
 		c.emit(saveResult)
 	}

+ 145 - 0
compiler_test.go

@@ -3351,6 +3351,31 @@ func TestLexicalDynamicScope(t *testing.T) {
 	testScript(SCRIPT, valueInt(3), t)
 }
 
+func TestLexicalDynamicScope1(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		const x = 1 * 4;
+		return (function() {
+			eval("");
+			return x;
+		})();
+	})();
+	`
+	testScript(SCRIPT, intToValue(4), t)
+}
+
+func TestLexicalDynamicScope2(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		const x = 1 + 3;
+		var y = 2 * 2;
+		eval("");
+		return x;
+	})();
+	`
+	testScript(SCRIPT, intToValue(4), t)
+}
+
 func TestNonStrictLet(t *testing.T) {
 	const SCRIPT = `
 	var let = 1;
@@ -3759,6 +3784,19 @@ func TestObjectAssignmentPattern(t *testing.T) {
 	testScriptWithTestLib(SCRIPT, _undefined, t)
 }
 
+func TestObjectAssignmentPatternNoDyn(t *testing.T) {
+	const SCRIPT = `
+	(function() {
+		let a, b, c;
+		({a, b, c=3} = {a: 1, b: 2});
+		assert.sameValue(a, 1, "a");
+		assert.sameValue(b, 2, "b");
+		assert.sameValue(c, 3, "c");
+	})();
+	`
+	testScriptWithTestLib(SCRIPT, _undefined, t)
+}
+
 func TestObjectAssignmentPatternNested(t *testing.T) {
 	const SCRIPT = `
 	let a, b, c, d;
@@ -4571,6 +4609,113 @@ func TestBadObjectKey(t *testing.T) {
 	}
 }
 
+func TestConstantFolding(t *testing.T) {
+	testValues := func(prg *Program, result Value, t *testing.T) {
+		if len(prg.values) != 1 || !prg.values[0].SameAs(result) {
+			prg.dumpCode(t.Logf)
+			t.Fatalf("values: %v", prg.values)
+		}
+	}
+	f := func(src string, result Value, t *testing.T) {
+		prg := MustCompile("test.js", src, false)
+		testValues(prg, result, t)
+		New().testPrg(prg, result, t)
+	}
+	ff := func(src string, result Value, t *testing.T) {
+		prg := MustCompile("test.js", src, false)
+		fl := prg.code[0].(*newFunc)
+		testValues(fl.prg, result, t)
+		New().testPrg(prg, result, t)
+	}
+
+	t.Run("lexical binding", func(t *testing.T) {
+		f("const x = 1 + 2; x", valueInt(3), t)
+	})
+	t.Run("var binding", func(t *testing.T) {
+		f("var x = 1 + 2; x", valueInt(3), t)
+	})
+	t.Run("assignment", func(t *testing.T) {
+		f("x = 1 + 2; x", valueInt(3), t)
+	})
+	t.Run("object pattern", func(t *testing.T) {
+		f("const {x = 1 + 2} = {}; x", valueInt(3), t)
+	})
+	t.Run("array pattern", func(t *testing.T) {
+		f("const [x = 1 + 2] = []; x", valueInt(3), t)
+	})
+	t.Run("object literal", func(t *testing.T) {
+		f("var o = {x: 1 + 2}; o.x", valueInt(3), t)
+	})
+	t.Run("array literal", func(t *testing.T) {
+		f("var a = [3, 3, 3, 1 + 2]; a[3]", valueInt(3), t)
+	})
+	t.Run("default function parameter", func(t *testing.T) {
+		ff("function f(arg = 1 + 2) {return arg}; f()", valueInt(3), t)
+	})
+	t.Run("return", func(t *testing.T) {
+		ff("function f() {return 1 + 2}; f()", valueInt(3), t)
+	})
+}
+
+func TestAssignBeforeInit(t *testing.T) {
+	const SCRIPT = `
+	assert.throws(ReferenceError, () => {
+		a = 1;
+		let a;
+	});
+
+	assert.throws(ReferenceError, () => {
+	    ({a, b} = {a: 1, b: 2});
+	    let a, b;
+	});
+
+	assert.throws(ReferenceError, () => {
+		(function() {
+			eval("");
+			({a} = {a: 1});
+		})();
+		let a;
+	});
+
+	assert.throws(ReferenceError, () => {
+	    const ctx = {x: 1};
+	    function t() {
+	        delete ctx.x;
+	        return 42;
+	    }
+	    with(ctx) {
+	        (function() {
+	            'use strict';
+	            ({x} = {x: t()});
+	        })();
+	    }
+	    return ctx.x;
+	});
+
+	assert.throws(ReferenceError, () => {
+	    const ctx = {x: 1};
+	    function t() {
+	        delete ctx.x;
+	        return 42;
+	    }
+	    with(ctx) {
+	        (function() {
+	            'use strict';
+				const src = {};
+				Object.defineProperty(src, "x", {
+					get() {
+						return t();
+					}
+				});
+	            ({x} = src);
+	        })();
+	    }
+	    return ctx.x;
+	});
+	`
+	testScriptWithTestLib(SCRIPT, _undefined, t)
+}
+
 /*
 func TestBabel(t *testing.T) {
 	src, err := ioutil.ReadFile("babel7.js")

+ 17 - 17
vm.go

@@ -97,7 +97,7 @@ func (r *stashRefLex) set(v Value) {
 }
 
 func (r *stashRefLex) init(v Value) {
-	r.set(v)
+	(*r.v)[r.idx] = v
 }
 
 type stashRefConst struct {
@@ -111,14 +111,11 @@ func (r *stashRefConst) set(v Value) {
 	}
 }
 
-func (r *stashRefConst) init(v Value) {
-	r.set(v)
-}
-
 type objRef struct {
-	base   objectImpl
-	name   unistring.String
-	strict bool
+	base    objectImpl
+	name    unistring.String
+	strict  bool
+	binding bool
 }
 
 func (r *objRef) get() Value {
@@ -126,7 +123,7 @@ func (r *objRef) get() Value {
 }
 
 func (r *objRef) set(v Value) {
-	if r.strict && !r.base.hasOwnPropertyStr(r.name) {
+	if r.strict && r.binding && !r.base.hasOwnPropertyStr(r.name) {
 		panic(referenceError(fmt.Sprintf("%s is not defined", r.name)))
 	}
 	r.base.setOwnStr(r.name, v, r.strict)
@@ -314,9 +311,10 @@ func (s *stash) getRefByName(name unistring.String, strict bool) ref {
 	if obj := s.obj; obj != nil {
 		if stashObjHas(obj, name) {
 			return &objRef{
-				base:   obj.self,
-				name:   name,
-				strict: strict,
+				base:    obj.self,
+				name:    name,
+				strict:  strict,
+				binding: true,
 			}
 		}
 	} else {
@@ -1943,8 +1941,9 @@ func (s resolveVar1) exec(vm *vm) {
 	}
 
 	ref = &objRef{
-		base: vm.r.globalObject.self,
-		name: name,
+		base:    vm.r.globalObject.self,
+		name:    name,
+		binding: true,
 	}
 
 end:
@@ -2023,9 +2022,10 @@ func (s resolveVar1Strict) exec(vm *vm) {
 
 	if vm.r.globalObject.self.hasPropertyStr(name) {
 		ref = &objRef{
-			base:   vm.r.globalObject.self,
-			name:   name,
-			strict: true,
+			base:    vm.r.globalObject.self,
+			name:    name,
+			binding: true,
+			strict:  true,
 		}
 		goto end
 	}

+ 17 - 0
vm_test.go

@@ -63,6 +63,23 @@ func TestEvalVar(t *testing.T) {
 	testScript(SCRIPT, valueTrue, t)
 }
 
+func TestResolveMixedStack1(t *testing.T) {
+	const SCRIPT = `
+	function test(arg) {
+		var a = 1;
+		var scope = {};
+		(function() {return arg})(); // move arguments to stash
+		with (scope) {
+			a++; // resolveMixedStack1 here
+			return a + arg;
+		}
+	}
+	test(40);
+	`
+
+	testScript(SCRIPT, valueInt(42), t)
+}
+
 func BenchmarkVmNOP2(b *testing.B) {
 	prg := []func(*vm){
 		//loadVal(0).exec,