From 551e1509a8de17285ff60d10dc8754b98a961c53 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Mon, 14 Feb 2022 12:37:19 +0000 Subject: [PATCH] Added constant folding to variable assignments. Avoid using references where possible. (cherry picked from commit b09a6bfa842fbfe6dd40685e7d3ca4852a30a2ed) --- compiler.go | 13 +++-- compiler_expr.go | 71 +++++++++++------------ compiler_stmt.go | 64 ++++++++++++--------- compiler_test.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++ vm.go | 34 +++++------ vm_test.go | 17 ++++++ 6 files changed, 255 insertions(+), 89 deletions(-) diff --git a/compiler.go b/compiler.go index 863310df..e940292d 100644 --- a/compiler.go +++ b/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() { diff --git a/compiler_expr.go b/compiler_expr.go index 85344d44..6c7a53b8 100644 --- a/compiler_expr.go +++ b/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) } } diff --git a/compiler_stmt.go b/compiler_stmt.go index e658997d..5fc12253 100644 --- a/compiler_stmt.go +++ b/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) } diff --git a/compiler_test.go b/compiler_test.go index 4a25837e..85206c64 100644 --- a/compiler_test.go +++ b/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") diff --git a/vm.go b/vm.go index d4202471..6dcdd557 100644 --- a/vm.go +++ b/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 } diff --git a/vm_test.go b/vm_test.go index 56cbfb0e..dddec2bc 100644 --- a/vm_test.go +++ b/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,