Browse Source

Async/await functionality (#464)

* Implemented async/await. See #460.
Dmitry Panov 2 years ago
parent
commit
33bff8fdda
24 changed files with 1690 additions and 656 deletions
  1. 11 0
      ast/node.go
  2. 48 29
      builtin_function.go
  3. 43 30
      builtin_promise.go
  4. 29 4
      compiler.go
  5. 38 5
      compiler_expr.go
  6. 11 14
      compiler_stmt.go
  7. 122 21
      compiler_test.go
  8. 8 0
      destruct.go
  9. 346 31
      func.go
  10. 29 18
      object.go
  11. 8 0
      object_dynamic.go
  12. 12 0
      object_lazy.go
  13. 130 47
      parser/expression.go
  14. 33 1
      parser/lexer.go
  15. 2 0
      parser/parser.go
  16. 2 0
      parser/scope.go
  17. 78 21
      parser/statement.go
  18. 22 0
      proxy.go
  19. 76 40
      runtime.go
  20. 158 2
      runtime_test.go
  21. 95 122
      tc39_test.go
  22. 11 2
      token/token_const.go
  23. 377 263
      vm.go
  24. 1 6
      vm_test.go

+ 11 - 0
ast/node.go

@@ -55,6 +55,11 @@ type (
 		_pattern()
 	}
 
+	AwaitExpression struct {
+		Await    file.Idx
+		Argument Expression
+	}
+
 	ArrayLiteral struct {
 		LeftBracket  file.Idx
 		RightBracket file.Idx
@@ -138,6 +143,8 @@ type (
 		Source        string
 
 		DeclarationList []*VariableDeclaration
+
+		Async bool
 	}
 
 	ClassLiteral struct {
@@ -164,6 +171,7 @@ type (
 		Body            ConciseBody
 		Source          string
 		DeclarationList []*VariableDeclaration
+		Async           bool
 	}
 
 	Identifier struct {
@@ -292,6 +300,7 @@ type (
 
 func (*ArrayLiteral) _expressionNode()          {}
 func (*AssignExpression) _expressionNode()      {}
+func (*AwaitExpression) _expressionNode()       {}
 func (*BadExpression) _expressionNode()         {}
 func (*BinaryExpression) _expressionNode()      {}
 func (*BooleanLiteral) _expressionNode()        {}
@@ -624,6 +633,7 @@ type Program struct {
 
 func (self *ArrayLiteral) Idx0() file.Idx          { return self.LeftBracket }
 func (self *ArrayPattern) Idx0() file.Idx          { return self.LeftBracket }
+func (self *AwaitExpression) Idx0() file.Idx       { return self.Await }
 func (self *ObjectPattern) Idx0() file.Idx         { return self.LeftBrace }
 func (self *AssignExpression) Idx0() file.Idx      { return self.Left.Idx0() }
 func (self *BadExpression) Idx0() file.Idx         { return self.From }
@@ -698,6 +708,7 @@ func (self *ForIntoExpression) Idx0() file.Idx { return self.Expression.Idx0() }
 func (self *ArrayLiteral) Idx1() file.Idx          { return self.RightBracket + 1 }
 func (self *ArrayPattern) Idx1() file.Idx          { return self.RightBracket + 1 }
 func (self *AssignExpression) Idx1() file.Idx      { return self.Right.Idx1() }
+func (self *AwaitExpression) Idx1() file.Idx       { return self.Argument.Idx1() }
 func (self *BadExpression) Idx1() file.Idx         { return self.To }
 func (self *BinaryExpression) Idx1() file.Idx      { return self.Right.Idx1() }
 func (self *BooleanLiteral) Idx1() file.Idx        { return file.Idx(int(self.Idx) + len(self.Literal)) }

+ 48 - 29
builtin_function.go

@@ -1,13 +1,16 @@
 package goja
 
 import (
-	"fmt"
 	"math"
 )
 
-func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
+func (r *Runtime) functionCtor(args []Value, proto *Object, async bool) *Object {
 	var sb valueStringBuilder
-	sb.WriteString(asciiString("(function anonymous("))
+	if async {
+		sb.WriteString(asciiString("(async function anonymous("))
+	} else {
+		sb.WriteString(asciiString("(function anonymous("))
+	}
 	if len(args) > 1 {
 		ar := args[:len(args)-1]
 		for i, arg := range ar {
@@ -28,39 +31,28 @@ func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
 	return ret
 }
 
-func nativeFuncString(f *nativeFuncObject) Value {
-	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
+func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
+	return r.functionCtor(args, proto, false)
+}
+
+func (r *Runtime) builtin_asyncFunction(args []Value, proto *Object) *Object {
+	return r.functionCtor(args, proto, true)
 }
 
 func (r *Runtime) functionproto_toString(call FunctionCall) Value {
 	obj := r.toObject(call.This)
-repeat:
+	if lazy, ok := obj.self.(*lazyObject); ok {
+		obj.self = lazy.create(obj)
+	}
 	switch f := obj.self.(type) {
-	case *funcObject:
-		return newStringValue(f.src)
-	case *classFuncObject:
-		return newStringValue(f.src)
-	case *methodFuncObject:
-		return newStringValue(f.src)
-	case *arrowFuncObject:
-		return newStringValue(f.src)
-	case *nativeFuncObject:
-		return nativeFuncString(f)
-	case *boundFuncObject:
-		return nativeFuncString(&f.nativeFuncObject)
-	case *wrappedFuncObject:
-		return nativeFuncString(&f.nativeFuncObject)
-	case *lazyObject:
-		obj.self = f.create(obj)
-		goto repeat
+	case funcObjectImpl:
+		return f.source()
 	case *proxyObject:
-	repeat2:
-		switch c := f.target.self.(type) {
-		case *classFuncObject, *methodFuncObject, *funcObject, *arrowFuncObject, *nativeFuncObject, *boundFuncObject:
+		if lazy, ok := f.target.self.(*lazyObject); ok {
+			f.target.self = lazy.create(f.target)
+		}
+		if _, ok := f.target.self.(funcObjectImpl); ok {
 			return asciiString("function () { [native code] }")
-		case *lazyObject:
-			f.target.self = c.create(obj)
-			goto repeat2
 		}
 	}
 	panic(r.NewTypeError("Function.prototype.toString requires that 'this' be a Function"))
@@ -219,3 +211,30 @@ func (r *Runtime) initFunction() {
 	r.global.Function = r.newNativeFuncConstruct(r.builtin_Function, "Function", r.global.FunctionPrototype, 1)
 	r.addToGlobal("Function", r.global.Function)
 }
+
+func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl {
+	o := &baseObject{
+		class:      classObject,
+		val:        val,
+		extensible: true,
+		prototype:  r.global.FunctionPrototype,
+	}
+	o.init()
+
+	o._putProp("constructor", r.global.AsyncFunction, true, false, true)
+
+	o._putSym(SymToStringTag, valueProp(asciiString(classAsyncFunction), false, false, true))
+
+	return o
+}
+
+func (r *Runtime) createAsyncFunction(val *Object) objectImpl {
+	o := r.newNativeFuncConstructObj(val, r.builtin_asyncFunction, "AsyncFunction", r.global.AsyncFunctionPrototype, 1)
+
+	return o
+}
+
+func (r *Runtime) initAsyncFunction() {
+	r.global.AsyncFunctionPrototype = r.newLazyObject(r.createAsyncFunctionProto)
+	r.global.AsyncFunction = r.newLazyObject(r.createAsyncFunction)
+}

+ 43 - 30
builtin_promise.go

@@ -38,9 +38,10 @@ type promiseCapability struct {
 }
 
 type promiseReaction struct {
-	capability *promiseCapability
-	typ        promiseReactionType
-	handler    *jobCallback
+	capability  *promiseCapability
+	typ         promiseReactionType
+	handler     *jobCallback
+	asyncRunner *asyncRunner
 }
 
 var typePromise = reflect.TypeOf((*Promise)(nil))
@@ -147,6 +148,24 @@ func (p *Promise) export(*objectExportCtx) interface{} {
 	return p
 }
 
+func (p *Promise) addReactions(fulfillReaction *promiseReaction, rejectReaction *promiseReaction) {
+	r := p.val.runtime
+	switch p.state {
+	case PromiseStatePending:
+		p.fulfillReactions = append(p.fulfillReactions, fulfillReaction)
+		p.rejectReactions = append(p.rejectReactions, rejectReaction)
+	case PromiseStateFulfilled:
+		r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result))
+	default:
+		reason := p.result
+		if !p.handled {
+			r.trackPromiseRejection(p, PromiseRejectionHandle)
+		}
+		r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason))
+	}
+	p.handled = true
+}
+
 func (r *Runtime) newPromiseResolveThenableJob(p *Promise, thenable Value, then *jobCallback) func() {
 	return func() {
 		resolve, reject := p.createResolvingFunctions()
@@ -297,20 +316,7 @@ func (r *Runtime) performPromiseThen(p *Promise, onFulfilled, onRejected Value,
 		typ:        promiseReactionReject,
 		handler:    onRejectedJobCallback,
 	}
-	switch p.state {
-	case PromiseStatePending:
-		p.fulfillReactions = append(p.fulfillReactions, fulfillReaction)
-		p.rejectReactions = append(p.rejectReactions, rejectReaction)
-	case PromiseStateFulfilled:
-		r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result))
-	default:
-		reason := p.result
-		if !p.handled {
-			r.trackPromiseRejection(p, PromiseRejectionHandle)
-		}
-		r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason))
-	}
-	p.handled = true
+	p.addReactions(fulfillReaction, rejectReaction)
 	if resultCapability == nil {
 		return _undefined
 	}
@@ -577,19 +583,19 @@ func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
 // In order to make use of this method you need an event loop such as the one in goja_nodejs (https://github.com/dop251/goja_nodejs)
 // where it can be used like this:
 //
-//	 loop := NewEventLoop()
-//	 loop.Start()
-//	 defer loop.Stop()
-//	 loop.RunOnLoop(func(vm *goja.Runtime) {
-//			p, resolve, _ := vm.NewPromise()
-//			vm.Set("p", p)
-//	     go func() {
-//	  		time.Sleep(500 * time.Millisecond)   // or perform any other blocking operation
-//				loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
-//					resolve(result)
-//				})
-//			}()
-//	 }
+//	loop := NewEventLoop()
+//	loop.Start()
+//	defer loop.Stop()
+//	loop.RunOnLoop(func(vm *goja.Runtime) {
+//	    p, resolve, _ := vm.NewPromise()
+//	    vm.Set("p", p)
+//	    go func() {
+//	        time.Sleep(500 * time.Millisecond)   // or perform any other blocking operation
+//	        loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
+//	            resolve(result)
+//	        })
+//	    }()
+//	}
 func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
 	p := r.newPromise(r.global.PromisePrototype)
 	resolveF, rejectF := p.createResolvingFunctions()
@@ -606,3 +612,10 @@ func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{
 func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) {
 	r.promiseRejectionTracker = tracker
 }
+
+// SetAsyncContextTracker registers a handler that allows to track async execution contexts. See AsyncContextTracker
+// documentation for more details. Setting it to nil disables the functionality.
+// This method (as Runtime in general) is not goroutine-safe.
+func (r *Runtime) SetAsyncContextTracker(tracker AsyncContextTracker) {
+	r.asyncContextTracker = tracker
+}

+ 29 - 4
compiler.go

@@ -420,10 +420,16 @@ func (p *Program) _dumpCode(indent string, logger func(format string, args ...in
 		switch f := ins.(type) {
 		case *newFunc:
 			prg = f.prg
+		case *newAsyncFunc:
+			prg = f.prg
 		case *newArrowFunc:
 			prg = f.prg
+		case *newAsyncArrowFunc:
+			prg = f.prg
 		case *newMethod:
 			prg = f.prg
+		case *newAsyncMethod:
+			prg = f.prg
 		case *newDerivedClass:
 			if f.initFields != nil {
 				dumpInitFields(f.initFields)
@@ -982,8 +988,6 @@ func (c *compiler) compile(in *ast.Program, strict, inGlobal bool, evalVm *vm) {
 		c.popScope()
 	}
 
-	c.p.code = append(c.p.code, halt)
-
 	scope.finaliseVarAlloc(0)
 }
 
@@ -1024,8 +1028,26 @@ func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) {
 	s := c.scope
 	if s.outer != nil {
 		unique := !s.isFunction() && !s.variable && s.strict
-		for _, decl := range funcs {
-			s.bindNameLexical(decl.Function.Name.Name, unique, int(decl.Function.Name.Idx1())-1)
+		if !unique {
+			hasNonStandard := false
+			for _, decl := range funcs {
+				if !decl.Function.Async {
+					s.bindNameLexical(decl.Function.Name.Name, false, int(decl.Function.Name.Idx1())-1)
+				} else {
+					hasNonStandard = true
+				}
+			}
+			if hasNonStandard {
+				for _, decl := range funcs {
+					if decl.Function.Async {
+						s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1)
+					}
+				}
+			}
+		} else {
+			for _, decl := range funcs {
+				s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1)
+			}
 		}
 	} else {
 		for _, decl := range funcs {
@@ -1222,6 +1244,9 @@ func (c *compiler) compileFunction(v *ast.FunctionDeclaration) {
 }
 
 func (c *compiler) compileStandaloneFunctionDecl(v *ast.FunctionDeclaration) {
+	if v.Function.Async {
+		c.throwSyntaxError(int(v.Idx0())-1, "Async functions can only be declared at top level or inside a block.")
+	}
 	if c.scope.strict {
 		c.throwSyntaxError(int(v.Idx0())-1, "In strict mode code, functions can only be declared at top level or inside a block.")
 	}

+ 38 - 5
compiler_expr.go

@@ -113,6 +113,11 @@ type compiledIdentifierExpr struct {
 	name unistring.String
 }
 
+type compiledAwaitExpression struct {
+	baseCompiledExpr
+	arg compiledExpr
+}
+
 type funcType uint8
 
 const (
@@ -137,6 +142,7 @@ type compiledFunctionLiteral struct {
 	homeObjOffset   uint32
 	typ             funcType
 	isExpr          bool
+	isAsync         bool
 }
 
 type compiledBracketExpr struct {
@@ -303,6 +309,12 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
 		}
 		r.init(c, v.Idx0())
 		return r
+	case *ast.AwaitExpression:
+		r := &compiledAwaitExpression{
+			arg: c.compileExpression(v.Argument),
+		}
+		r.init(c, v.Await)
+		return r
 	default:
 		c.assert(false, int(v.Idx0())-1, "Unknown expression type: %T", v)
 		panic("unreachable")
@@ -1720,11 +1732,23 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	p, name, length, strict := e.compile()
 	switch e.typ {
 	case funcArrow:
-		e.c.emit(&newArrowFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}})
+		if e.isAsync {
+			e.c.emit(&newAsyncArrowFunc{newArrowFunc: newArrowFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}}})
+		} else {
+			e.c.emit(&newArrowFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}})
+		}
 	case funcMethod, funcClsInit:
-		e.c.emit(&newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset})
+		if e.isAsync {
+			e.c.emit(&newAsyncMethod{newMethod: newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset}})
+		} else {
+			e.c.emit(&newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset})
+		}
 	case funcRegular:
-		e.c.emit(&newFunc{prg: p, length: length, name: name, source: e.source, strict: strict})
+		if e.isAsync {
+			e.c.emit(&newAsyncFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}})
+		} else {
+			e.c.emit(&newFunc{prg: p, length: length, name: name, source: e.source, strict: strict})
+		}
 	default:
 		e.c.throwSyntaxError(e.offset, "Unsupported func type: %v", e.typ)
 	}
@@ -1747,6 +1771,7 @@ func (c *compiler) compileFunctionLiteral(v *ast.FunctionLiteral, isExpr bool) *
 		isExpr:          isExpr,
 		typ:             funcRegular,
 		strict:          strictBody,
+		isAsync:         v.Async,
 	}
 	r.init(c, v.Idx0())
 	return r
@@ -2182,7 +2207,7 @@ func (e *compiledClassLiteral) compileFieldsAndStaticBlocks(elements []clsElemen
 			}
 		}
 	}
-	e.c.emit(halt)
+	//e.c.emit(halt)
 	if s.isDynamic() || thisBinding.useCount() > 0 {
 		if s.isDynamic() || thisBinding.inStash {
 			thisBinding.emitInitAt(1)
@@ -2261,6 +2286,7 @@ func (c *compiler) compileArrowFunctionLiteral(v *ast.ArrowFunctionLiteral) *com
 		isExpr:          true,
 		typ:             funcArrow,
 		strict:          strictBody,
+		isAsync:         v.Async,
 	}
 	r.init(c, v.Idx0())
 	return r
@@ -2432,7 +2458,6 @@ func (c *compiler) evalConst(expr compiledExpr) (Value, *Exception) {
 	}
 	savedPc := len(c.p.code)
 	expr.emitGetter(true)
-	c.emit(halt)
 	c.evalVM.pc = savedPc
 	ex := c.evalVM.runTry()
 	if createdPrg {
@@ -3518,3 +3543,11 @@ func (e *compiledOptional) emitGetter(putOnStack bool) {
 		e.c.emit(nil)
 	}
 }
+
+func (e *compiledAwaitExpression) emitGetter(putOnStack bool) {
+	e.arg.emitGetter(true)
+	e.c.emit(await{})
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}

+ 11 - 14
compiler_stmt.go

@@ -127,11 +127,10 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
 		c.emit(clearResult)
 	}
 	c.compileBlockStatement(v.Body, bodyNeedResult)
-	c.emit(halt)
-	lbl2 := len(c.p.code)
-	c.emit(nil)
 	var catchOffset int
 	if v.Catch != nil {
+		lbl2 := len(c.p.code) // jump over the catch block
+		c.emit(nil)
 		catchOffset = len(c.p.code) - lbl
 		if v.Catch.Parameter != nil {
 			c.block = &block{
@@ -183,23 +182,21 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
 			c.emit(pop)
 			c.compileBlockStatement(v.Catch.Body, bodyNeedResult)
 		}
-		c.emit(halt)
+		c.p.code[lbl2] = jump(len(c.p.code) - lbl2)
 	}
 	var finallyOffset int
 	if v.Finally != nil {
-		lbl1 := len(c.p.code)
-		c.emit(nil)
-		finallyOffset = len(c.p.code) - lbl
+		c.emit(enterFinally{})
+		finallyOffset = len(c.p.code) - lbl // finallyOffset should not include enterFinally
 		if bodyNeedResult && finallyBreaking != nil && lp == -1 {
 			c.emit(clearResult)
 		}
 		c.compileBlockStatement(v.Finally, false)
-		c.emit(halt, retFinally)
-
-		c.p.code[lbl1] = jump(len(c.p.code) - lbl1)
+		c.emit(leaveFinally{})
+	} else {
+		c.emit(leaveTry{})
 	}
 	c.p.code[lbl] = try{catchOffset: int32(catchOffset), finallyOffset: int32(finallyOffset)}
-	c.p.code[lbl2] = jump(len(c.p.code) - lbl2)
 	c.leaveBlock()
 }
 
@@ -639,7 +636,7 @@ L:
 			b.breaks = append(b.breaks, len(c.p.code))
 			c.emit(nil)
 		case blockTry:
-			c.emit(halt)
+			c.emit(leaveTry{})
 		case blockWith:
 			c.emit(leaveWith)
 		case blockLoopEnum:
@@ -663,7 +660,7 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 
 func (c *compiler) compileIfBody(s ast.Statement, needResult bool) {
 	if !c.scope.strict {
-		if s, ok := s.(*ast.FunctionDeclaration); ok {
+		if s, ok := s.(*ast.FunctionDeclaration); ok && !s.Function.Async {
 			c.compileFunction(s)
 			if needResult {
 				c.emit(clearResult)
@@ -742,7 +739,7 @@ func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
 	for b := c.block; b != nil; b = b.outer {
 		switch b.typ {
 		case blockTry:
-			c.emit(halt)
+			c.emit(leaveTry{})
 		case blockLoopEnum:
 			c.emit(enumPopClose)
 		}

+ 122 - 21
compiler_test.go

@@ -129,6 +129,19 @@ const TESTLIBX = `
 		}
 		return assert._isSameValue(a, b);
 	}
+
+	function assertStack(e, expected) {
+		const lines = e.stack.split('\n');
+		assert.sameValue(lines.length, expected.length + 2, "Stack lengths mismatch");
+		let lnum = 1;
+		for (const [file, func, line, col] of expected) {
+			const expLine = func === "" ?
+				"\tat " + file + ":" + line + ":" + col + "(" :
+				"\tat " + func + " (" + file + ":" + line + ":" + col + "(";
+			assert.sameValue(lines[lnum].substring(0, expLine.length), expLine, "line " + lnum);
+			lnum++;
+		}
+	}
 `
 
 var (
@@ -154,13 +167,14 @@ func testLibX() *Program {
 }
 
 func (r *Runtime) testPrg(p *Program, expectedResult Value, t *testing.T) {
+	p.dumpCode(t.Logf)
+	v, err := r.RunProgram(p)
+	if err != nil {
+		if ex, ok := err.(*Exception); ok {
+			t.Fatalf("Exception: %v", ex.String())
+		}
+	}
 	vm := r.vm
-	vm.prg = p
-	vm.pc = 0
-	vm.prg.dumpCode(t.Logf)
-	vm.result = _undefined
-	vm.run()
-	v := vm.result
 	t.Logf("stack size: %d", len(vm.stack))
 	t.Logf("stashAllocs: %d", vm.stashAllocs)
 
@@ -216,6 +230,65 @@ func testScriptWithTestLibX(script string, expectedResult Value, t *testing.T) {
 	New().testScriptWithTestLibX(script, expectedResult, t)
 }
 
+func (r *Runtime) testAsyncFunc(src string, expectedResult Value, t *testing.T) {
+	v, err := r.RunScript("test.js", "(async function test() {"+src+"\n})()")
+	if err != nil {
+		t.Fatal(err)
+	}
+	promise := v.Export().(*Promise)
+	switch s := promise.State(); s {
+	case PromiseStateFulfilled:
+		if res := promise.Result(); res == nil && expectedResult != nil || !res.SameAs(expectedResult) {
+			t.Fatalf("Result: %+v, expected: %+v", res, expectedResult)
+		}
+	case PromiseStateRejected:
+		res := promise.Result()
+		if resObj, ok := res.(*Object); ok {
+			if stack := resObj.Get("stack"); stack != nil {
+				t.Fatal(stack.String())
+			}
+		}
+		t.Fatal(res.String())
+	default:
+		t.Fatalf("Unexpected promise state: %v", s)
+	}
+}
+
+func (r *Runtime) testAsyncFuncWithTestLib(src string, expectedResult Value, t *testing.T) {
+	_, err := r.RunProgram(testLib())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	r.testAsyncFunc(src, expectedResult, t)
+}
+
+func (r *Runtime) testAsyncFuncWithTestLibX(src string, expectedResult Value, t *testing.T) {
+	_, err := r.RunProgram(testLib())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = r.RunProgram(testLibX())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	r.testAsyncFunc(src, expectedResult, t)
+}
+
+func testAsyncFunc(src string, expectedResult Value, t *testing.T) {
+	New().testAsyncFunc(src, expectedResult, t)
+}
+
+func testAsyncFuncWithTestLib(src string, expectedResult Value, t *testing.T) {
+	New().testAsyncFuncWithTestLib(src, expectedResult, t)
+}
+
+func testAsyncFuncWithTestLibX(src string, expectedResult Value, t *testing.T) {
+	New().testAsyncFuncWithTestLibX(src, expectedResult, t)
+}
+
 func TestEmptyProgram(t *testing.T) {
 	const SCRIPT = `
 	`
@@ -4481,7 +4554,7 @@ func TestDuplicateFunc(t *testing.T) {
 }
 
 func TestSrcLocations(t *testing.T) {
-	// Do not reformat, assertions depend on line and column numbers
+	// Do not reformat, assertions depend on the line and column numbers
 	const SCRIPT = `
 	let i = {
 		valueOf() {
@@ -4536,21 +4609,8 @@ func TestSrcLocations(t *testing.T) {
 						["test.js", "", 49, 4]
 						]);
 	}
-
-
-	function assertStack(e, expected) {
-		const lines = e.stack.split('\n');
-		let lnum = 1;
-		for (const [file, func, line, col] of expected) {
-			const expLine = func === "" ?
-				"\tat " + file + ":" + line + ":" + col + "(" :
-				"\tat " + func + " (" + file + ":" + line + ":" + col + "(";
-			assert.sameValue(lines[lnum].substring(0, expLine.length), expLine, "line " + lnum);
-			lnum++;
-		}
-	}
 	`
-	testScriptWithTestLib(SCRIPT, _undefined, t)
+	testScriptWithTestLibX(SCRIPT, _undefined, t)
 }
 
 func TestSrcLocationThrowLiteral(t *testing.T) {
@@ -5613,6 +5673,47 @@ func TestLexicalDeclInSwitch(t *testing.T) {
 	testScript(SCRIPT, _undefined, t)
 }
 
+func TestClassFieldSpecial(t *testing.T) {
+	const SCRIPT = `
+	class C {
+		get;
+		set;
+		async;
+		static;
+	}
+	`
+	testScript(SCRIPT, _undefined, t)
+}
+
+func TestClassMethodSpecial(t *testing.T) {
+	const SCRIPT = `
+	class C {
+		get() {}
+		set() {}
+		async() {}
+		static() {}
+	}
+	`
+	testScript(SCRIPT, _undefined, t)
+}
+
+func TestAsyncFunc(t *testing.T) {
+	const SCRIPT = `
+	async (x = true, y) => {};
+	async x => {};
+	let passed = false;
+	async function f() {
+		return true;
+	}
+	async function f1(arg = true) {
+		passed = await f();
+	}
+	await f1();
+	return passed;
+	`
+	testAsyncFunc(SCRIPT, valueTrue, t)
+}
+
 /*
 func TestBabel(t *testing.T) {
 	src, err := os.ReadFile("babel7.js")

+ 8 - 0
destruct.go

@@ -52,6 +52,10 @@ func (d *destructKeyedSource) className() string {
 	return d.w().className()
 }
 
+func (d *destructKeyedSource) typeOf() valueString {
+	return d.w().typeOf()
+}
+
 func (d *destructKeyedSource) getStr(p unistring.String, receiver Value) Value {
 	d.recordKey(stringValueFromRaw(p))
 	return d.w().getStr(p, receiver)
@@ -170,6 +174,10 @@ func (d *destructKeyedSource) assertCallable() (call func(FunctionCall) Value, o
 	return d.w().assertCallable()
 }
 
+func (d *destructKeyedSource) vmCall(vm *vm, n int) {
+	d.w().vmCall(vm, n)
+}
+
 func (d *destructKeyedSource) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	return d.w().assertConstructor()
 }

+ 346 - 31
func.go

@@ -1,11 +1,37 @@
 package goja
 
 import (
+	"fmt"
 	"reflect"
 
 	"github.com/dop251/goja/unistring"
 )
 
+type resultType uint8
+
+const (
+	resultNormal resultType = iota
+	resultYield
+	resultAwait
+)
+
+var (
+	resultAwaitMarker = NewSymbol("await")
+)
+
+// AsyncContextTracker is a handler that allows to track async function's execution context. Every time an async
+// function is suspended on 'await', Suspended() is called. The trackingObject it returns is remembered and
+// the next time just before the context is resumed, Resumed is called with the same trackingObject as argument.
+// To register it call Runtime.SetAsyncContextTracker().
+type AsyncContextTracker interface {
+	Suspended() (trackingObject interface{})
+	Resumed(trackingObject interface{})
+}
+
+type funcObjectImpl interface {
+	source() valueString
+}
+
 type baseFuncObject struct {
 	baseObject
 
@@ -27,6 +53,10 @@ type funcObject struct {
 	baseJsFuncObject
 }
 
+type asyncFuncObject struct {
+	baseJsFuncObject
+}
+
 type classFuncObject struct {
 	baseJsFuncObject
 	initFields   *Program
@@ -43,12 +73,20 @@ type methodFuncObject struct {
 	homeObject *Object
 }
 
+type asyncMethodFuncObject struct {
+	methodFuncObject
+}
+
 type arrowFuncObject struct {
 	baseJsFuncObject
 	funcObj   *Object
 	newTarget Value
 }
 
+type asyncArrowFuncObject struct {
+	arrowFuncObject
+}
+
 type nativeFuncObject struct {
 	baseFuncObject
 
@@ -66,6 +104,10 @@ type boundFuncObject struct {
 	wrapped *Object
 }
 
+func (f *nativeFuncObject) source() valueString {
+	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
+}
+
 func (f *nativeFuncObject) export(*objectExportCtx) interface{} {
 	return f.f
 }
@@ -161,6 +203,10 @@ func (f *baseFuncObject) createInstance(newTarget *Object) *Object {
 	return f.val.runtime.newBaseObject(proto, classObject).val
 }
 
+func (f *baseJsFuncObject) source() valueString {
+	return newStringValue(f.src)
+}
+
 func (f *baseJsFuncObject) construct(args []Value, newTarget *Object) *Object {
 	if newTarget == nil {
 		newTarget = f.val
@@ -193,6 +239,10 @@ func (f *classFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
 
+func (f *classFuncObject) vmCall(vm *vm, n int) {
+	f.Call(FunctionCall{})
+}
+
 func (f *classFuncObject) export(*objectExportCtx) interface{} {
 	return f.Call
 }
@@ -229,10 +279,12 @@ func (f *classFuncObject) _initFields(instance *Object) {
 		vm.sb = vm.sp
 		vm.push(instance)
 		vm.pc = 0
-		vm.run()
+		ex := vm.runTry()
 		vm.popCtx()
+		if ex != nil {
+			panic(ex)
+		}
 		vm.sp -= 2
-		vm.halt = false
 	}
 }
 
@@ -284,7 +336,7 @@ func (f *arrowFuncObject) Call(call FunctionCall) Value {
 	return f._call(call.Arguments, f.newTarget, nil)
 }
 
-func (f *baseJsFuncObject) _call(args []Value, newTarget, this Value) Value {
+func (f *baseJsFuncObject) __call(args []Value, newTarget, this Value) (Value, *Exception) {
 	vm := f.val.runtime.vm
 
 	vm.stack.expand(vm.sp + len(args) + 1)
@@ -301,27 +353,47 @@ func (f *baseJsFuncObject) _call(args []Value, newTarget, this Value) Value {
 		vm.sp++
 	}
 
-	pc := vm.pc
-	if pc != -1 {
-		vm.pc++ // fake "return address" so that captureStack() records the correct call location
+	vm.pushTryFrame(tryPanicMarker, -1)
+	defer vm.popTryFrame()
+
+	var needPop bool
+	if vm.prg != nil {
 		vm.pushCtx()
-		vm.callStack = append(vm.callStack, context{pc: -1}) // extra frame so that run() halts after ret
+		vm.callStack = append(vm.callStack, context{pc: -2}) // extra frame so that run() halts after ret
+		needPop = true
 	} else {
+		vm.pc = -2
 		vm.pushCtx()
 	}
+
 	vm.args = len(args)
 	vm.prg = f.prg
 	vm.stash = f.stash
 	vm.privEnv = f.privEnv
 	vm.newTarget = newTarget
 	vm.pc = 0
-	vm.run()
-	if pc != -1 {
+	for {
+		ex := vm.runTryInner()
+		if ex != nil {
+			return nil, ex
+		}
+		if vm.halted() {
+			break
+		}
+	}
+	if needPop {
 		vm.popCtx()
 	}
-	vm.pc = pc
-	vm.halt = false
-	return vm.pop()
+
+	return vm.pop(), nil
+}
+
+func (f *baseJsFuncObject) _call(args []Value, newTarget, this Value) Value {
+	res, ex := f.__call(args, newTarget, this)
+	if ex != nil {
+		panic(ex)
+	}
+	return res
 }
 
 func (f *baseJsFuncObject) call(call FunctionCall, newTarget Value) Value {
@@ -336,6 +408,10 @@ func (f *baseFuncObject) exportType() reflect.Type {
 	return reflectTypeFunc
 }
 
+func (f *baseFuncObject) typeOf() valueString {
+	return stringFunction
+}
+
 func (f *baseJsFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
@@ -344,10 +420,31 @@ func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) *
 	return f.construct
 }
 
+func (f *baseJsFuncObject) vmCall(vm *vm, n int) {
+	vm.pushCtx()
+	vm.args = n
+	vm.prg = f.prg
+	vm.stash = f.stash
+	vm.privEnv = f.privEnv
+	vm.pc = 0
+	vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = vm.stack[vm.sp-n-2], vm.stack[vm.sp-n-1]
+}
+
 func (f *arrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return f.Call, true
 }
 
+func (f *arrowFuncObject) vmCall(vm *vm, n int) {
+	vm.pushCtx()
+	vm.args = n
+	vm.prg = f.prg
+	vm.stash = f.stash
+	vm.privEnv = f.privEnv
+	vm.pc = 0
+	vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = nil, vm.stack[vm.sp-n-1]
+	vm.newTarget = f.newTarget
+}
+
 func (f *arrowFuncObject) export(*objectExportCtx) interface{} {
 	return f.Call
 }
@@ -404,41 +501,259 @@ func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 }
 
+func (f *nativeFuncObject) vmCall(vm *vm, n int) {
+	if f.f != nil {
+		vm.pushCtx()
+		vm.prg = nil
+		vm.funcName = nilSafe(f.getStr("name", nil)).string()
+		ret := f.f(FunctionCall{
+			Arguments: vm.stack[vm.sp-n : vm.sp],
+			This:      vm.stack[vm.sp-n-2],
+		})
+		if ret == nil {
+			ret = _undefined
+		}
+		vm.stack[vm.sp-n-2] = ret
+		vm.popCtx()
+	} else {
+		vm.stack[vm.sp-n-2] = _undefined
+	}
+	vm.sp -= n + 1
+	vm.pc++
+}
+
 func (f *nativeFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	return f.construct
 }
 
-/*func (f *boundFuncObject) getStr(p unistring.String, receiver Value) Value {
-	return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver)
+func (f *boundFuncObject) hasInstance(v Value) bool {
+	return instanceOfOperator(v, f.wrapped)
+}
+
+func (f *baseJsFuncObject) asyncCall(call FunctionCall, vmCall func(*vm, int)) Value {
+	vm := f.val.runtime.vm
+	args := call.Arguments
+	vm.stack.expand(vm.sp + len(args) + 1)
+	vm.stack[vm.sp] = call.This
+	vm.sp++
+	vm.stack[vm.sp] = f.val
+	vm.sp++
+	for _, arg := range args {
+		if arg != nil {
+			vm.stack[vm.sp] = arg
+		} else {
+			vm.stack[vm.sp] = _undefined
+		}
+		vm.sp++
+	}
+	ar := &asyncRunner{
+		f:      f.val,
+		vmCall: vmCall,
+	}
+	ar.start(len(args))
+	return ar.promiseCap.promise
+}
+
+func (f *asyncFuncObject) Call(call FunctionCall) Value {
+	return f.asyncCall(call, f.baseJsFuncObject.vmCall)
+}
+
+func (f *asyncFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
+	return f.Call, true
+}
+
+func (f *asyncArrowFuncObject) Call(call FunctionCall) Value {
+	return f.asyncCall(call, f.arrowFuncObject.vmCall)
+}
+
+func (f *asyncArrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
+	return f.Call, true
+}
+
+func (f *asyncArrowFuncObject) vmCall(vm *vm, n int) {
+	f.asyncVmCall(vm, n, f.arrowFuncObject.vmCall)
+}
+
+func (f *asyncMethodFuncObject) Call(call FunctionCall) Value {
+	return f.asyncCall(call, f.methodFuncObject.vmCall)
+}
+
+func (f *asyncMethodFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
+	return f.Call, true
+}
+
+func (f *asyncMethodFuncObject) vmCall(vm *vm, n int) {
+	f.asyncVmCall(vm, n, f.methodFuncObject.vmCall)
 }
 
-func (f *boundFuncObject) getOwnPropStr(name unistring.String) Value {
-	if name == "caller" || name == "arguments" {
-		return f.val.runtime.global.throwerProperty
+func (f *baseJsFuncObject) asyncVmCall(vm *vm, n int, vmCall func(*vm, int)) {
+	ar := &asyncRunner{
+		f:      f.val,
+		vmCall: vmCall,
 	}
+	ar.start(n)
+	vm.push(ar.promiseCap.promise)
+	vm.pc++
+}
 
-	return f.nativeFuncObject.getOwnPropStr(name)
+func (f *asyncFuncObject) vmCall(vm *vm, n int) {
+	f.asyncVmCall(vm, n, f.baseJsFuncObject.vmCall)
 }
 
-func (f *boundFuncObject) deleteStr(name unistring.String, throw bool) bool {
-	if name == "caller" || name == "arguments" {
-		return true
+type asyncRunner struct {
+	gen        generator
+	promiseCap *promiseCapability
+	f          *Object
+	vmCall     func(*vm, int)
+
+	trackingObj interface{}
+}
+
+func (ar *asyncRunner) onFulfilled(call FunctionCall) Value {
+	if tracker := ar.f.runtime.asyncContextTracker; tracker != nil {
+		tracker.Resumed(ar.trackingObj)
+		ar.trackingObj = nil
+	}
+	ar.gen.vm.curAsyncRunner = ar
+	defer func() {
+		ar.gen.vm.curAsyncRunner = nil
+	}()
+	arg := call.Argument(0)
+	res, resType, ex := ar.gen.next(arg)
+	ar.step(res, resType == resultNormal, ex)
+	return _undefined
+}
+
+func (ar *asyncRunner) onRejected(call FunctionCall) Value {
+	if tracker := ar.f.runtime.asyncContextTracker; tracker != nil {
+		tracker.Resumed(ar.trackingObj)
+		ar.trackingObj = nil
+	}
+	ar.gen.vm.curAsyncRunner = ar
+	defer func() {
+		ar.gen.vm.curAsyncRunner = nil
+	}()
+	reason := call.Argument(0)
+	res, resType, ex := ar.gen.nextThrow(reason)
+	ar.step(res, resType == resultNormal, ex)
+	return _undefined
+}
+
+func (ar *asyncRunner) step(res Value, done bool, ex *Exception) {
+	if ex != nil {
+		ar.promiseCap.reject(ex.val)
+		return
+	}
+	if done {
+		ar.promiseCap.resolve(res)
+	} else {
+		// await
+		r := ar.f.runtime
+		if tracker := r.asyncContextTracker; tracker != nil {
+			ar.trackingObj = tracker.Suspended()
+		}
+		promise := r.promiseResolve(r.global.Promise, res)
+		promise.self.(*Promise).addReactions(&promiseReaction{
+			typ:         promiseReactionFulfill,
+			handler:     &jobCallback{callback: ar.onFulfilled},
+			asyncRunner: ar,
+		}, &promiseReaction{
+			typ:         promiseReactionReject,
+			handler:     &jobCallback{callback: ar.onRejected},
+			asyncRunner: ar,
+		})
 	}
-	return f.nativeFuncObject.deleteStr(name, throw)
 }
 
-func (f *boundFuncObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
-	if name == "caller" || name == "arguments" {
-		panic(f.val.runtime.NewTypeError("'caller' and 'arguments' are restricted function properties and cannot be accessed in this context."))
+func (ar *asyncRunner) start(nArgs int) {
+	r := ar.f.runtime
+	ar.gen.vm = r.vm
+	ar.promiseCap = r.newPromiseCapability(r.global.Promise)
+	sp := r.vm.sp
+	ar.gen.enter()
+	ar.vmCall(r.vm, nArgs)
+	res, resType, ex := ar.gen.step()
+	ar.step(res, resType == resultNormal, ex)
+	if ex != nil {
+		r.vm.sp = sp - nArgs - 2
 	}
-	return f.nativeFuncObject.setOwnStr(name, val, throw)
+	r.vm.popTryFrame()
+	r.vm.popCtx()
 }
 
-func (f *boundFuncObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
-	return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw)
+type generator struct {
+	ctx execCtx
+	vm  *vm
+
+	tryStackLen, iterStackLen, refStackLen uint32
 }
-*/
 
-func (f *boundFuncObject) hasInstance(v Value) bool {
-	return instanceOfOperator(v, f.wrapped)
+func (g *generator) storeLengths() {
+	g.tryStackLen, g.iterStackLen, g.refStackLen = uint32(len(g.vm.tryStack)), uint32(len(g.vm.iterStack)), uint32(len(g.vm.refStack))
+}
+
+func (g *generator) enter() {
+	g.vm.pushCtx()
+	g.vm.pushTryFrame(tryPanicMarker, -1)
+	g.vm.prg, g.vm.funcName, g.vm.pc = nil, "", -2 // so that vm.run() halts after ret
+	g.storeLengths()
+}
+
+func (g *generator) step() (res Value, resultType resultType, ex *Exception) {
+	for {
+		ex = g.vm.runTryInner()
+		if ex != nil {
+			return
+		}
+		if g.vm.halted() {
+			break
+		}
+	}
+	res = g.vm.pop()
+	if res == resultAwaitMarker {
+		resultType = resultAwait
+		g.ctx = execCtx{}
+		g.vm.pc = -g.vm.pc + 1
+		res = g.vm.pop()
+		g.vm.suspend(&g.ctx, uint32(len(g.vm.tryStack))-g.tryStackLen,
+			uint32(len(g.vm.iterStack))-g.iterStackLen,
+			uint32(len(g.vm.refStack))-g.refStackLen)
+		g.vm.sp = g.vm.sb - 1
+		g.vm.callStack = g.vm.callStack[:len(g.vm.callStack)-1] // remove the frame with pc == -2, as ret would do
+	}
+	return
+}
+
+func (g *generator) enterNext() {
+	g.vm.pushCtx()
+	g.vm.pushTryFrame(tryPanicMarker, -1)
+	g.vm.callStack = append(g.vm.callStack, context{pc: -2}) // extra frame so that vm.run() halts after ret
+	g.storeLengths()
+	g.vm.resume(&g.ctx)
+}
+
+func (g *generator) next(v Value) (Value, resultType, *Exception) {
+	g.enterNext()
+	if v != nil {
+		g.vm.push(v)
+	}
+	res, done, ex := g.step()
+	g.vm.popTryFrame()
+	g.vm.popCtx()
+	return res, done, ex
+}
+
+func (g *generator) nextThrow(v Value) (Value, resultType, *Exception) {
+	g.enterNext()
+	ex := g.vm.handleThrow(v)
+	if ex != nil {
+		g.vm.popTryFrame()
+		g.vm.popCtx()
+		return nil, resultNormal, ex
+	}
+
+	res, resType, ex := g.step()
+	g.vm.popTryFrame()
+	g.vm.popCtx()
+	return res, resType, ex
 }

+ 29 - 18
object.go

@@ -10,24 +10,25 @@ import (
 )
 
 const (
-	classObject   = "Object"
-	classArray    = "Array"
-	classWeakSet  = "WeakSet"
-	classWeakMap  = "WeakMap"
-	classMap      = "Map"
-	classMath     = "Math"
-	classSet      = "Set"
-	classFunction = "Function"
-	classNumber   = "Number"
-	classString   = "String"
-	classBoolean  = "Boolean"
-	classError    = "Error"
-	classAggError = "AggregateError"
-	classRegExp   = "RegExp"
-	classDate     = "Date"
-	classJSON     = "JSON"
-	classGlobal   = "global"
-	classPromise  = "Promise"
+	classObject        = "Object"
+	classArray         = "Array"
+	classWeakSet       = "WeakSet"
+	classWeakMap       = "WeakMap"
+	classMap           = "Map"
+	classMath          = "Math"
+	classSet           = "Set"
+	classFunction      = "Function"
+	classAsyncFunction = "AsyncFunction"
+	classNumber        = "Number"
+	classString        = "String"
+	classBoolean       = "Boolean"
+	classError         = "Error"
+	classAggError      = "AggregateError"
+	classRegExp        = "RegExp"
+	classDate          = "Date"
+	classJSON          = "JSON"
+	classGlobal        = "global"
+	classPromise       = "Promise"
 
 	classArrayIterator        = "Array Iterator"
 	classMapIterator          = "Map Iterator"
@@ -148,6 +149,7 @@ type objectExportCtx struct {
 type objectImpl interface {
 	sortable
 	className() string
+	typeOf() valueString
 	getStr(p unistring.String, receiver Value) Value
 	getIdx(p valueInt, receiver Value) Value
 	getSym(p *Symbol, receiver Value) Value
@@ -184,6 +186,7 @@ type objectImpl interface {
 	toPrimitiveString() Value
 	toPrimitive() Value
 	assertCallable() (call func(FunctionCall) Value, ok bool)
+	vmCall(vm *vm, n int)
 	assertConstructor() func(args []Value, newTarget *Object) *Object
 	proto() *Object
 	setProto(proto *Object, throw bool) bool
@@ -277,6 +280,10 @@ func (o *baseObject) className() string {
 	return o.class
 }
 
+func (o *baseObject) typeOf() valueString {
+	return stringObjectC
+}
+
 func (o *baseObject) hasPropertyStr(name unistring.String) bool {
 	if o.val.self.hasOwnPropertyStr(name) {
 		return true
@@ -926,6 +933,10 @@ func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) {
 	return nil, false
 }
 
+func (o *baseObject) vmCall(vm *vm, n int) {
+	vm.r.typeErrorResult(true, "Not a function: %s", o.val.toString())
+}
+
 func (o *baseObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	return nil
 }

+ 8 - 0
object_dynamic.go

@@ -447,6 +447,10 @@ func (o *baseDynamicObject) assertCallable() (call func(FunctionCall) Value, ok
 	return nil, false
 }
 
+func (o *baseDynamicObject) vmCall(vm *vm, n int) {
+	panic(vm.r.NewTypeError("Dynamic object is not callable"))
+}
+
 func (*baseDynamicObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	return nil
 }
@@ -563,6 +567,10 @@ func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElement
 	panic(newTypeError("Dynamic objects cannot have private elements"))
 }
 
+func (o *baseDynamicObject) typeOf() valueString {
+	return stringObjectC
+}
+
 func (a *dynamicArray) sortLen() int {
 	return a.a.Len()
 }

+ 12 - 0
object_lazy.go

@@ -17,6 +17,12 @@ func (o *lazyObject) className() string {
 	return obj.className()
 }
 
+func (o *lazyObject) typeOf() valueString {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.typeOf()
+}
+
 func (o *lazyObject) getIdx(p valueInt, receiver Value) Value {
 	obj := o.create(o.val)
 	o.val.self = obj
@@ -187,6 +193,12 @@ func (o *lazyObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
 	return obj.assertCallable()
 }
 
+func (o *lazyObject) vmCall(vm *vm, n int) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	obj.vmCall(vm, n)
+}
+
 func (o *lazyObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	obj := o.create(o.val)
 	o.val.self = obj

+ 130 - 47
parser/expression.go

@@ -87,13 +87,17 @@ func (self *_parser) parsePrimaryExpression() ast.Expression {
 		}
 	case token.SUPER:
 		return self.parseSuperProperty()
+	case token.ASYNC:
+		if f := self.parseMaybeAsyncFunction(false); f != nil {
+			return f
+		}
 	case token.FUNCTION:
-		return self.parseFunction(false)
+		return self.parseFunction(false, false, idx)
 	case token.CLASS:
 		return self.parseClass(false)
 	}
 
-	if isBindingId(self.token, parsedLiteral) {
+	if self.isBindingId(self.token) {
 		self.next()
 		return &ast.Identifier{
 			Name: parsedLiteral,
@@ -144,10 +148,10 @@ func (self *_parser) parseSuperProperty() ast.Expression {
 	}
 }
 
-func (self *_parser) reinterpretSequenceAsArrowFuncParams(seq *ast.SequenceExpression) *ast.ParameterList {
+func (self *_parser) reinterpretSequenceAsArrowFuncParams(list []ast.Expression) *ast.ParameterList {
 	firstRestIdx := -1
-	params := make([]*ast.Binding, 0, len(seq.Sequence))
-	for i, item := range seq.Sequence {
+	params := make([]*ast.Binding, 0, len(list))
+	for i, item := range list {
 		if _, ok := item.(*ast.SpreadElement); ok {
 			if firstRestIdx == -1 {
 				firstRestIdx = i
@@ -155,14 +159,14 @@ func (self *_parser) reinterpretSequenceAsArrowFuncParams(seq *ast.SequenceExpre
 			}
 		}
 		if firstRestIdx != -1 {
-			self.error(seq.Sequence[firstRestIdx].Idx0(), "Rest parameter must be last formal parameter")
+			self.error(list[firstRestIdx].Idx0(), "Rest parameter must be last formal parameter")
 			return &ast.ParameterList{}
 		}
 		params = append(params, self.reinterpretAsBinding(item))
 	}
 	var rest ast.Expression
 	if firstRestIdx != -1 {
-		rest = self.reinterpretAsBindingRestElement(seq.Sequence[firstRestIdx])
+		rest = self.reinterpretAsBindingRestElement(list[firstRestIdx])
 	}
 	return &ast.ParameterList{
 		List: params,
@@ -253,24 +257,22 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
 	}
 }
 
-func isBindingId(tok token.Token, parsedLiteral unistring.String) bool {
+func (self *_parser) isBindingId(tok token.Token) bool {
 	if tok == token.IDENTIFIER {
 		return true
 	}
-	if token.IsId(tok) {
-		switch parsedLiteral {
-		case "yield", "await":
-			return true
-		}
-		if token.IsUnreservedWord(tok) {
-			return true
-		}
+
+	if tok == token.AWAIT {
+		return !self.scope.allowAwait
+	}
+	if token.IsUnreservedWord(tok) {
+		return true
 	}
 	return false
 }
 
 func (self *_parser) tokenToBindingId() {
-	if isBindingId(self.token, self.parsedLiteral) {
+	if self.isBindingId(self.token) {
 		self.token = token.IDENTIFIER
 	}
 }
@@ -297,7 +299,7 @@ func (self *_parser) parseBindingTarget() (target ast.BindingTarget) {
 	return
 }
 
-func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression {
+func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) *ast.Binding {
 	node := &ast.Binding{
 		Target: self.parseBindingTarget(),
 	}
@@ -401,22 +403,13 @@ func (self *_parser) parseObjectProperty() ast.Property {
 	if token.IsId(tkn) || tkn == token.STRING || tkn == token.ILLEGAL {
 		switch {
 		case self.token == token.LEFT_PARENTHESIS:
-			parameterList := self.parseFunctionParameterList()
-
-			node := &ast.FunctionLiteral{
-				Function:      keyStartIdx,
-				ParameterList: parameterList,
-			}
-			node.Body, node.DeclarationList = self.parseFunctionBlock()
-			node.Source = self.slice(keyStartIdx, node.Body.Idx1())
-
 			return &ast.PropertyKeyed{
 				Key:   value,
 				Kind:  ast.PropertyKindMethod,
-				Value: node,
+				Value: self.parseMethodDefinition(keyStartIdx, ast.PropertyKindMethod, false),
 			}
 		case self.token == token.COMMA || self.token == token.RIGHT_BRACE || self.token == token.ASSIGN: // shorthand property
-			if isBindingId(tkn, parsedLiteral) {
+			if self.isBindingId(tkn) {
 				var initializer ast.Expression
 				if self.token == token.ASSIGN {
 					// allow the initializer syntax here in case the object literal
@@ -434,14 +427,18 @@ func (self *_parser) parseObjectProperty() ast.Property {
 			} else {
 				self.errorUnexpectedToken(self.token)
 			}
-		case (literal == "get" || literal == "set") && self.token != token.COLON:
+		case (literal == "get" || literal == "set" || tkn == token.ASYNC) && self.token != token.COLON:
 			_, _, keyValue, _ := self.parseObjectPropertyKey()
 			if keyValue == nil {
 				return nil
 			}
 
 			var kind ast.PropertyKind
-			if literal == "get" {
+			var async bool
+			if tkn == token.ASYNC {
+				async = true
+				kind = ast.PropertyKindMethod
+			} else if literal == "get" {
 				kind = ast.PropertyKindGet
 			} else {
 				kind = ast.PropertyKindSet
@@ -450,7 +447,7 @@ func (self *_parser) parseObjectProperty() ast.Property {
 			return &ast.PropertyKeyed{
 				Key:   keyValue,
 				Kind:  kind,
-				Value: self.parseMethodDefinition(keyStartIdx, kind),
+				Value: self.parseMethodDefinition(keyStartIdx, kind, async),
 			}
 		}
 	}
@@ -464,8 +461,14 @@ func (self *_parser) parseObjectProperty() ast.Property {
 	}
 }
 
-func (self *_parser) parseMethodDefinition(keyStartIdx file.Idx, kind ast.PropertyKind) *ast.FunctionLiteral {
+func (self *_parser) parseMethodDefinition(keyStartIdx file.Idx, kind ast.PropertyKind, async bool) *ast.FunctionLiteral {
 	idx1 := self.idx
+	if async != self.scope.allowAwait {
+		self.scope.allowAwait = async
+		defer func() {
+			self.scope.allowAwait = !async
+		}()
+	}
 	parameterList := self.parseFunctionParameterList()
 	switch kind {
 	case ast.PropertyKindGet:
@@ -480,8 +483,9 @@ func (self *_parser) parseMethodDefinition(keyStartIdx file.Idx, kind ast.Proper
 	node := &ast.FunctionLiteral{
 		Function:      keyStartIdx,
 		ParameterList: parameterList,
+		Async:         async,
 	}
-	node.Body, node.DeclarationList = self.parseFunctionBlock()
+	node.Body, node.DeclarationList = self.parseFunctionBlock(async, async)
 	node.Source = self.slice(keyStartIdx, node.Body.Idx1())
 	return node
 }
@@ -834,6 +838,22 @@ func (self *_parser) parseUnaryExpression() ast.Expression {
 			Idx:      idx,
 			Operand:  operand,
 		}
+	case token.AWAIT:
+		if self.scope.allowAwait {
+			idx := self.idx
+			self.next()
+			if !self.scope.inAsync {
+				self.errorUnexpectedToken(token.AWAIT)
+				return &ast.BadExpression{
+					From: idx,
+					To:   self.idx,
+				}
+			}
+			return &ast.AwaitExpression{
+				Await:    idx,
+				Argument: self.parseUnaryExpression(),
+			}
+		}
 	}
 
 	return self.parsePostfixExpression()
@@ -1132,14 +1152,68 @@ func (self *_parser) parseConditionalExpression() ast.Expression {
 	return left
 }
 
+func (self *_parser) parseArrowFunction(start file.Idx, paramList *ast.ParameterList, async bool) ast.Expression {
+	self.expect(token.ARROW)
+	node := &ast.ArrowFunctionLiteral{
+		Start:         start,
+		ParameterList: paramList,
+		Async:         async,
+	}
+	node.Body, node.DeclarationList = self.parseArrowFunctionBody(async)
+	node.Source = self.slice(start, node.Body.Idx1())
+	return node
+}
+
+func (self *_parser) parseSingleArgArrowFunction(start file.Idx, async bool) ast.Expression {
+	if async != self.scope.allowAwait {
+		self.scope.allowAwait = async
+		defer func() {
+			self.scope.allowAwait = !async
+		}()
+	}
+	self.tokenToBindingId()
+	if self.token != token.IDENTIFIER {
+		self.errorUnexpectedToken(self.token)
+		self.next()
+		return &ast.BadExpression{
+			From: start,
+			To:   self.idx,
+		}
+	}
+
+	id := self.parseIdentifier()
+
+	paramList := &ast.ParameterList{
+		Opening: id.Idx,
+		Closing: id.Idx1(),
+		List: []*ast.Binding{{
+			Target: id,
+		}},
+	}
+
+	return self.parseArrowFunction(start, paramList, async)
+}
+
 func (self *_parser) parseAssignmentExpression() ast.Expression {
 	start := self.idx
 	parenthesis := false
+	async := false
 	var state parserState
-	if self.token == token.LEFT_PARENTHESIS {
+	switch self.token {
+	case token.LEFT_PARENTHESIS:
 		self.mark(&state)
 		parenthesis = true
-	} else {
+	case token.ASYNC:
+		tok := self.peek()
+		if self.isBindingId(tok) {
+			// async x => ...
+			self.next()
+			return self.parseSingleArgArrowFunction(start, true)
+		} else if tok == token.LEFT_PARENTHESIS {
+			self.mark(&state)
+			async = true
+		}
+	default:
 		self.tokenToBindingId()
 	}
 	left := self.parseConditionalExpression()
@@ -1183,23 +1257,30 @@ func (self *_parser) parseAssignmentExpression() ast.Expression {
 			}
 		} else if parenthesis {
 			if seq, ok := left.(*ast.SequenceExpression); ok && len(self.errors) == 0 {
-				paramList = self.reinterpretSequenceAsArrowFuncParams(seq)
+				paramList = self.reinterpretSequenceAsArrowFuncParams(seq.Sequence)
 			} else {
 				self.restore(&state)
 				paramList = self.parseFunctionParameterList()
 			}
-		} else {
+		} else if async {
+			// async (x, y) => ...
+			if !self.scope.allowAwait {
+				self.scope.allowAwait = true
+				defer func() {
+					self.scope.allowAwait = false
+				}()
+			}
+			if _, ok := left.(*ast.CallExpression); ok {
+				self.restore(&state)
+				self.next() // skip "async"
+				paramList = self.parseFunctionParameterList()
+			}
+		}
+		if paramList == nil {
 			self.error(left.Idx0(), "Malformed arrow function parameter list")
 			return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()}
 		}
-		self.expect(token.ARROW)
-		node := &ast.ArrowFunctionLiteral{
-			Start:         start,
-			ParameterList: paramList,
-		}
-		node.Body, node.DeclarationList = self.parseArrowFunctionBody()
-		node.Source = self.slice(node.Start, node.Body.Idx1())
-		return node
+		return self.parseArrowFunction(start, paramList, async)
 	}
 
 	if operator != 0 {
@@ -1490,7 +1571,9 @@ func (self *_parser) reinterpretAsDestructBindingTarget(item ast.Expression) ast
 	case *ast.ObjectLiteral:
 		return self.reinterpretAsObjectBindingPattern(item)
 	case *ast.Identifier:
-		return item
+		if !self.scope.allowAwait || item.Name != "await" {
+			return item
+		}
 	}
 	self.error(item.Idx0(), "Invalid destructuring binding target")
 	return &ast.BadExpression{From: item.Idx0(), To: item.Idx1()}

+ 33 - 1
parser/lexer.go

@@ -245,7 +245,7 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
 				tkn, strict = token.IsKeyword(string(parsedLiteral))
 				if hasEscape {
 					self.insertSemicolon = true
-					if tkn == 0 || token.IsUnreservedWord(tkn) {
+					if tkn == 0 || self.isBindingId(tkn) {
 						tkn = token.IDENTIFIER
 					} else {
 						tkn = token.ESCAPED_RESERVED_WORD
@@ -274,6 +274,13 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
 					self.insertSemicolon = true
 					return
 
+				case token.ASYNC:
+					// async only has special meaning if not followed by a LineTerminator
+					if self.skipWhiteSpaceCheckLineTerminator() {
+						self.insertSemicolon = true
+						tkn = token.IDENTIFIER
+					}
+					return
 				default:
 					return
 
@@ -570,6 +577,31 @@ func (self *_parser) skipMultiLineComment() (hasLineTerminator bool) {
 	return
 }
 
+func (self *_parser) skipWhiteSpaceCheckLineTerminator() bool {
+	for {
+		switch self.chr {
+		case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
+			self.read()
+			continue
+		case '\r':
+			if self._peek() == '\n' {
+				self.read()
+			}
+			fallthrough
+		case '\u2028', '\u2029', '\n':
+			return true
+		}
+		if self.chr >= utf8.RuneSelf {
+			if unicode.IsSpace(self.chr) {
+				self.read()
+				continue
+			}
+		}
+		break
+	}
+	return false
+}
+
 func (self *_parser) skipWhiteSpace() {
 	for {
 		switch self.chr {

+ 2 - 0
parser/parser.go

@@ -209,6 +209,8 @@ func (self *_parser) slice(idx0, idx1 file.Idx) string {
 }
 
 func (self *_parser) parse() (*ast.Program, error) {
+	self.openScope()
+	defer self.closeScope()
 	self.next()
 	program := self.parseProgram()
 	if false {

+ 2 - 0
parser/scope.go

@@ -12,6 +12,8 @@ type _scope struct {
 	inIteration     bool
 	inSwitch        bool
 	inFunction      bool
+	inAsync         bool
+	allowAwait      bool
 	declarationList []*ast.VariableDeclaration
 
 	labels []unistring.String

+ 78 - 21
parser/statement.go

@@ -73,9 +73,15 @@ func (self *_parser) parseStatement() ast.Statement {
 		self.insertSemicolon = true
 	case token.CONST:
 		return self.parseLexicalDeclaration(self.token)
+	case token.ASYNC:
+		if f := self.parseMaybeAsyncFunction(true); f != nil {
+			return &ast.FunctionDeclaration{
+				Function: f,
+			}
+		}
 	case token.FUNCTION:
 		return &ast.FunctionDeclaration{
-			Function: self.parseFunction(true),
+			Function: self.parseFunction(true, false, self.idx),
 		}
 	case token.CLASS:
 		return &ast.ClassDeclaration{
@@ -167,7 +173,10 @@ func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
 			rest = self.reinterpretAsDestructBindingTarget(self.parseAssignmentExpression())
 			break
 		}
-		self.parseVariableDeclaration(&list)
+		item := self.parseVariableDeclaration(&list)
+		if _, ok := item.Initializer.(*ast.AwaitExpression); ok {
+			self.error(item.Idx0(), "Illegal await-expression in formal parameters of async function")
+		}
 		if self.token != token.RIGHT_PARENTHESIS {
 			self.expect(token.COMMA)
 		}
@@ -182,10 +191,30 @@ func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
 	}
 }
 
-func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
+func (self *_parser) parseMaybeAsyncFunction(declaration bool) *ast.FunctionLiteral {
+	if self.peek() == token.FUNCTION {
+		idx := self.idx
+		self.next()
+		return self.parseFunction(declaration, true, idx)
+	}
+	return nil
+}
+
+func (self *_parser) parseFunction(declaration, async bool, start file.Idx) *ast.FunctionLiteral {
 
 	node := &ast.FunctionLiteral{
-		Function: self.expect(token.FUNCTION),
+		Function: start,
+		Async:    async,
+	}
+	self.expect(token.FUNCTION)
+
+	if !declaration {
+		if async != self.scope.allowAwait {
+			self.scope.allowAwait = async
+			defer func() {
+				self.scope.allowAwait = !async
+			}()
+		}
 	}
 
 	self.tokenToBindingId()
@@ -197,30 +226,49 @@ func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
 		self.expect(token.IDENTIFIER)
 	}
 	node.Name = name
+
+	if declaration {
+		if async != self.scope.allowAwait {
+			self.scope.allowAwait = async
+			defer func() {
+				self.scope.allowAwait = !async
+			}()
+		}
+	}
+
 	node.ParameterList = self.parseFunctionParameterList()
-	node.Body, node.DeclarationList = self.parseFunctionBlock()
+	node.Body, node.DeclarationList = self.parseFunctionBlock(async, async)
 	node.Source = self.slice(node.Idx0(), node.Idx1())
 
 	return node
 }
 
-func (self *_parser) parseFunctionBlock() (body *ast.BlockStatement, declarationList []*ast.VariableDeclaration) {
+func (self *_parser) parseFunctionBlock(async, allowAwait bool) (body *ast.BlockStatement, declarationList []*ast.VariableDeclaration) {
 	self.openScope()
-	inFunction := self.scope.inFunction
 	self.scope.inFunction = true
-	defer func() {
-		self.scope.inFunction = inFunction
-		self.closeScope()
-	}()
+	self.scope.inAsync = async
+	self.scope.allowAwait = allowAwait
+	defer self.closeScope()
 	body = self.parseBlockStatement()
 	declarationList = self.scope.declarationList
 	return
 }
 
-func (self *_parser) parseArrowFunctionBody() (ast.ConciseBody, []*ast.VariableDeclaration) {
+func (self *_parser) parseArrowFunctionBody(async bool) (ast.ConciseBody, []*ast.VariableDeclaration) {
 	if self.token == token.LEFT_BRACE {
-		return self.parseFunctionBlock()
+		return self.parseFunctionBlock(async, async)
+	}
+	if async != self.scope.inAsync || async != self.scope.allowAwait {
+		inAsync := self.scope.inAsync
+		allowAwait := self.scope.allowAwait
+		self.scope.inAsync = async
+		self.scope.allowAwait = async
+		defer func() {
+			self.scope.inAsync = inAsync
+			self.scope.allowAwait = allowAwait
+		}()
 	}
+
 	return &ast.ExpressionBody{
 		Expression: self.parseAssignmentExpression(),
 	}, nil
@@ -270,7 +318,7 @@ func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
 					b := &ast.ClassStaticBlock{
 						Static: start,
 					}
-					b.Block, b.DeclarationList = self.parseFunctionBlock()
+					b.Block, b.DeclarationList = self.parseFunctionBlock(false, true)
 					b.Source = self.slice(b.Block.LeftBrace, b.Block.Idx1())
 					node.Body = append(node.Body, b)
 					continue
@@ -280,9 +328,10 @@ func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
 		}
 
 		var kind ast.PropertyKind
+		var async bool
 		methodBodyStart := self.idx
 		if self.literal == "get" || self.literal == "set" {
-			if self.peek() != token.LEFT_PARENTHESIS {
+			if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS {
 				if self.literal == "get" {
 					kind = ast.PropertyKindGet
 				} else {
@@ -290,6 +339,12 @@ func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
 				}
 				self.next()
 			}
+		} else if self.token == token.ASYNC {
+			if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS {
+				async = true
+				kind = ast.PropertyKindMethod
+				self.next()
+			}
 		}
 
 		_, keyName, value, tkn := self.parseObjectPropertyKey()
@@ -309,9 +364,13 @@ func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
 
 		if kind != "" {
 			// method
-			if keyName == "constructor" {
-				if !computed && !static && kind != ast.PropertyKindMethod {
-					self.error(value.Idx0(), "Class constructor may not be an accessor")
+			if keyName == "constructor" && !computed {
+				if !static {
+					if kind != ast.PropertyKindMethod {
+						self.error(value.Idx0(), "Class constructor may not be an accessor")
+					} else if async {
+						self.error(value.Idx0(), "Class constructor may not be an async method")
+					}
 				} else if private {
 					self.error(value.Idx0(), "Class constructor may not be a private method")
 				}
@@ -320,7 +379,7 @@ func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
 				Idx:      start,
 				Key:      value,
 				Kind:     kind,
-				Body:     self.parseMethodDefinition(methodBodyStart, kind),
+				Body:     self.parseMethodDefinition(methodBodyStart, kind, async),
 				Static:   static,
 				Computed: computed,
 			}
@@ -791,8 +850,6 @@ func (self *_parser) parseSourceElements() (body []ast.Statement) {
 }
 
 func (self *_parser) parseProgram() *ast.Program {
-	self.openScope()
-	defer self.closeScope()
 	prg := &ast.Program{
 		Body:            self.parseSourceElements(),
 		DeclarationList: self.scope.declarationList,

+ 22 - 0
proxy.go

@@ -865,6 +865,20 @@ func (p *proxyObject) assertCallable() (call func(FunctionCall) Value, ok bool)
 	return nil, false
 }
 
+func (p *proxyObject) vmCall(vm *vm, n int) {
+	vm.pushCtx()
+	vm.prg = nil
+	vm.funcName = "proxy"
+	ret := p.apply(FunctionCall{This: vm.stack[vm.sp-n-2], Arguments: vm.stack[vm.sp-n : vm.sp]})
+	if ret == nil {
+		ret = _undefined
+	}
+	vm.stack[vm.sp-n-2] = ret
+	vm.popCtx()
+	vm.sp -= n + 1
+	vm.pc++
+}
+
 func (p *proxyObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 	if p.ctor != nil {
 		return p.construct
@@ -1036,6 +1050,14 @@ func (p *proxyObject) className() string {
 	return classObject
 }
 
+func (p *proxyObject) typeOf() valueString {
+	if p.call == nil {
+		return stringObjectC
+	}
+
+	return stringFunction
+}
+
 func (p *proxyObject) exportType() reflect.Type {
 	return proxyType
 }

+ 76 - 40
runtime.go

@@ -60,6 +60,8 @@ type global struct {
 	Proxy    *Object
 	Promise  *Object
 
+	AsyncFunction *Object
+
 	ArrayBuffer       *Object
 	DataView          *Object
 	TypedArray        *Object
@@ -108,6 +110,8 @@ type global struct {
 	SetPrototype         *Object
 	PromisePrototype     *Object
 
+	AsyncFunctionPrototype *Object
+
 	IteratorPrototype             *Object
 	ArrayIteratorPrototype        *Object
 	MapIteratorPrototype          *Object
@@ -187,6 +191,7 @@ type Runtime struct {
 	jobQueue []func()
 
 	promiseRejectionTracker PromiseRejectionTracker
+	asyncContextTracker     AsyncContextTracker
 }
 
 type StackFrame struct {
@@ -417,6 +422,7 @@ func (r *Runtime) init() {
 
 	r.initObject()
 	r.initFunction()
+	r.initAsyncFunction()
 	r.initArray()
 	r.initString()
 	r.initGlobalObject()
@@ -482,7 +488,11 @@ func (r *Runtime) newError(typ *Object, format string, args ...interface{}) Valu
 }
 
 func (r *Runtime) throwReferenceError(name unistring.String) {
-	panic(r.newError(r.global.ReferenceError, "%s is not defined", name))
+	panic(r.newReferenceError(name))
+}
+
+func (r *Runtime) newReferenceError(name unistring.String) Value {
+	return r.newError(r.global.ReferenceError, "%s is not defined", name)
 }
 
 func (r *Runtime) newSyntaxError(msg string, offset int) Value {
@@ -558,15 +568,19 @@ func (r *Runtime) NewGoError(err error) *Object {
 }
 
 func (r *Runtime) newFunc(name unistring.String, length int, strict bool) (f *funcObject) {
-	v := &Object{runtime: r}
-
 	f = &funcObject{}
-	f.class = classFunction
-	f.val = v
-	f.extensible = true
-	f.strict = strict
-	v.self = f
-	f.prototype = r.global.FunctionPrototype
+	r.initBaseJsFunction(&f.baseJsFuncObject, strict)
+	f.val.self = f
+	f.init(name, intToValue(int64(length)))
+	return
+}
+
+func (r *Runtime) newAsyncFunc(name unistring.String, length int, strict bool) (f *asyncFuncObject) {
+	f = &asyncFuncObject{}
+	r.initBaseJsFunction(&f.baseJsFuncObject, strict)
+	f.class = classAsyncFunction
+	f.prototype = r.global.AsyncFunctionPrototype
+	f.val.self = f
 	f.init(name, intToValue(int64(length)))
 	return
 }
@@ -586,34 +600,51 @@ func (r *Runtime) newClassFunc(name unistring.String, length int, proto *Object,
 	return
 }
 
-func (r *Runtime) newMethod(name unistring.String, length int, strict bool) (f *methodFuncObject) {
+func (r *Runtime) initBaseJsFunction(f *baseJsFuncObject, strict bool) {
 	v := &Object{runtime: r}
 
-	f = &methodFuncObject{}
 	f.class = classFunction
 	f.val = v
 	f.extensible = true
 	f.strict = strict
-	v.self = f
 	f.prototype = r.global.FunctionPrototype
+}
+
+func (r *Runtime) newMethod(name unistring.String, length int, strict bool) (f *methodFuncObject) {
+	f = &methodFuncObject{}
+	r.initBaseJsFunction(&f.baseJsFuncObject, strict)
+	f.val.self = f
 	f.init(name, intToValue(int64(length)))
 	return
 }
 
-func (r *Runtime) newArrowFunc(name unistring.String, length int, strict bool) (f *arrowFuncObject) {
-	v := &Object{runtime: r}
+func (r *Runtime) newAsyncMethod(name unistring.String, length int, strict bool) (f *asyncMethodFuncObject) {
+	f = &asyncMethodFuncObject{}
+	r.initBaseJsFunction(&f.baseJsFuncObject, strict)
+	f.val.self = f
+	f.init(name, intToValue(int64(length)))
+	return
+}
 
-	f = &arrowFuncObject{}
-	f.class = classFunction
-	f.val = v
-	f.extensible = true
-	f.strict = strict
+func (r *Runtime) initArrowFunc(f *arrowFuncObject, strict bool) {
+	r.initBaseJsFunction(&f.baseJsFuncObject, strict)
+	f.newTarget = r.vm.newTarget
+}
 
-	vm := r.vm
+func (r *Runtime) newArrowFunc(name unistring.String, length int, strict bool) (f *arrowFuncObject) {
+	f = &arrowFuncObject{}
+	r.initArrowFunc(f, strict)
+	f.val.self = f
+	f.init(name, intToValue(int64(length)))
+	return
+}
 
-	f.newTarget = vm.newTarget
-	v.self = f
-	f.prototype = r.global.FunctionPrototype
+func (r *Runtime) newAsyncArrowFunc(name unistring.String, length int, strict bool) (f *asyncArrowFuncObject) {
+	f = &asyncArrowFuncObject{}
+	r.initArrowFunc(&f.arrowFuncObject, strict)
+	f.class = classAsyncFunction
+	f.prototype = r.global.AsyncFunctionPrototype
+	f.val.self = f
 	f.init(name, intToValue(int64(length)))
 	return
 }
@@ -919,10 +950,12 @@ func (r *Runtime) eval(srcVal valueString, direct, strict bool) Value {
 	vm.push(funcObj)
 	vm.sb = vm.sp
 	vm.push(nil) // this
-	vm.run()
+	ex := vm.runTry()
 	retval := vm.result
 	vm.popCtx()
-	vm.halt = false
+	if ex != nil {
+		panic(ex)
+	}
 	vm.sp -= 2
 	return retval
 }
@@ -1381,11 +1414,18 @@ func (r *Runtime) RunScript(name, src string) (Value, error) {
 
 // RunProgram executes a pre-compiled (see Compile()) code in the global context.
 func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
+	vm := r.vm
+	recursive := len(vm.callStack) > 0
 	defer func() {
+		if recursive {
+			vm.popCtx()
+		} else {
+			vm.callStack = vm.callStack[:len(vm.callStack)-1]
+		}
 		if x := recover(); x != nil {
 			if ex, ok := x.(*uncatchableException); ok {
 				err = ex.err
-				if len(r.vm.callStack) == 0 {
+				if len(vm.callStack) == 0 {
 					r.leaveAbrupt()
 				}
 			} else {
@@ -1393,13 +1433,12 @@ func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
 			}
 		}
 	}()
-	vm := r.vm
-	recursive := false
-	if len(vm.callStack) > 0 {
-		recursive = true
+	if recursive {
 		vm.pushCtx()
 		vm.stash = &r.global.stash
 		vm.sb = vm.sp - 1
+	} else {
+		vm.callStack = append(vm.callStack, context{})
 	}
 	vm.prg = p
 	vm.pc = 0
@@ -1411,11 +1450,8 @@ func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
 		err = ex
 	}
 	if recursive {
-		vm.popCtx()
-		vm.halt = false
 		vm.clearStack()
 	} else {
-		vm.stack = nil
 		vm.prg = nil
 		vm.funcName = ""
 		r.leave()
@@ -2414,9 +2450,10 @@ func (r *Runtime) runWrapped(f func()) (err error) {
 	if ex != nil {
 		err = ex
 	}
-	r.vm.clearStack()
 	if len(r.vm.callStack) == 0 {
 		r.leave()
+	} else {
+		r.vm.clearStack()
 	}
 	return
 }
@@ -2692,16 +2729,15 @@ func (r *Runtime) getHash() *maphash.Hash {
 
 // called when the top level function returns normally (i.e. control is passed outside the Runtime).
 func (r *Runtime) leave() {
-	for {
-		jobs := r.jobQueue
-		r.jobQueue = nil
-		if len(jobs) == 0 {
-			break
-		}
+	var jobs []func()
+	for len(r.jobQueue) > 0 {
+		jobs, r.jobQueue = r.jobQueue, jobs[:0]
 		for _, job := range jobs {
 			job()
 		}
 	}
+	r.jobQueue = nil
+	r.vm.stack = nil
 }
 
 // called when the top level function returns (i.e. control is passed outside the Runtime) but it was due to an interrupt

+ 158 - 2
runtime_test.go

@@ -2201,7 +2201,7 @@ func TestStacktraceLocationThrowFromCatch(t *testing.T) {
 	if len(stack) != 2 {
 		t.Fatalf("Unexpected stack len: %v", stack)
 	}
-	if frame := stack[0]; frame.funcName != "main" || frame.pc != 30 {
+	if frame := stack[0]; frame.funcName != "main" || frame.pc != 29 {
 		t.Fatalf("Unexpected stack frame 0: %#v", frame)
 	}
 	if frame := stack[1]; frame.funcName != "" || frame.pc != 7 {
@@ -2235,7 +2235,7 @@ func TestStacktraceLocationThrowFromGo(t *testing.T) {
 	if frame := stack[0]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowFromGo.func1") {
 		t.Fatalf("Unexpected stack frame 0: %#v", frame)
 	}
-	if frame := stack[1]; frame.funcName != "callee" || frame.pc != 1 {
+	if frame := stack[1]; frame.funcName != "callee" || frame.pc != 2 {
 		t.Fatalf("Unexpected stack frame 1: %#v", frame)
 	}
 	if frame := stack[2]; frame.funcName != "main" || frame.pc != 6 {
@@ -2500,6 +2500,142 @@ func TestErrorFormatSymbols(t *testing.T) {
 	}
 }
 
+func TestPanicPassthrough(t *testing.T) {
+	const panicString = "Test panic"
+	r := New()
+	r.Set("f", func() {
+		panic(panicString)
+	})
+	defer func() {
+		if x := recover(); x != nil {
+			if x != panicString {
+				t.Fatalf("Wrong panic value: %v", x)
+			}
+			if len(r.vm.callStack) > 0 {
+				t.Fatal("vm.callStack is not empty")
+			}
+		} else {
+			t.Fatal("No panic")
+		}
+	}()
+	_, _ = r.RunString("f()")
+	t.Fatal("Should not reach here")
+}
+
+func TestSuspendResumeRelStackLen(t *testing.T) {
+	const SCRIPT = `
+	async function f2() {
+		throw new Error("test");
+	}
+
+	async function f1() {
+		let a = [1];
+		for (let i of a) {
+			try {
+				await f2();
+			} catch {
+				return true;
+			}
+		}
+	}
+
+	async function f() {
+		let a = [1];
+		for (let i of a) {
+			return await f1();
+		}
+	}
+	return f();
+	`
+	testAsyncFunc(SCRIPT, valueTrue, t)
+}
+
+func TestNestedTopLevelConstructorCall(t *testing.T) {
+	r := New()
+	c := func(call ConstructorCall, rt *Runtime) *Object {
+		if _, err := rt.RunString("(5)"); err != nil {
+			panic(err)
+		}
+		return nil
+	}
+	if err := r.Set("C", c); err != nil {
+		panic(err)
+	}
+	if _, err := r.RunString("new C()"); err != nil {
+		panic(err)
+	}
+}
+
+func TestNestedTopLevelConstructorPanicAsync(t *testing.T) {
+	r := New()
+	c := func(call ConstructorCall, rt *Runtime) *Object {
+		c, ok := AssertFunction(rt.ToValue(func() {}))
+		if !ok {
+			panic("wat")
+		}
+		if _, err := c(Undefined()); err != nil {
+			panic(err)
+		}
+		return nil
+	}
+	if err := r.Set("C", c); err != nil {
+		panic(err)
+	}
+	if _, err := r.RunString("new C()"); err != nil {
+		panic(err)
+	}
+}
+
+func TestAsyncFuncThrow(t *testing.T) {
+	const SCRIPT = `
+	class TestError extends Error {
+	}
+
+	async function f() {
+		throw new TestError();
+	}
+
+	async function f1() {
+		try {
+			await f();
+		} catch (e) {
+			assert.sameValue(e.constructor.name, TestError.name);
+			return;
+		}
+		throw new Error("No exception was thrown");
+	}
+	await f1();
+	return undefined;
+	`
+	testAsyncFuncWithTestLib(SCRIPT, _undefined, t)
+}
+
+func TestAsyncStacktrace(t *testing.T) {
+	// Do not reformat, assertions depend on the line and column numbers
+	const SCRIPT = `
+	let ex;
+	async function foo(x) {
+	  await bar(x);
+	}
+
+	async function bar(x) {
+	  await x;
+	  throw new Error("Let's have a look...");
+	}
+
+	try {
+		await foo(1);
+	} catch (e) {
+		assertStack(e, [
+			["test.js", "bar", 9, 10],
+			["test.js", "foo", 4, 13],
+			["test.js", "test", 13, 12],
+		]);
+	}
+	`
+	testAsyncFuncWithTestLibX(SCRIPT, _undefined, t)
+}
+
 /*
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)
@@ -2551,6 +2687,26 @@ func BenchmarkCallNative(b *testing.B) {
 	}
 }
 
+func BenchmarkCallJS(b *testing.B) {
+	vm := New()
+	_, err := vm.RunString(`
+	function f() {
+		return 42;
+	}
+	`)
+
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	prg := MustCompile("test.js", "f(null)", true)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		vm.RunProgram(prg)
+	}
+}
+
 func BenchmarkMainLoop(b *testing.B) {
 	vm := New()
 

+ 95 - 122
tc39_test.go

@@ -124,103 +124,73 @@ var (
 		"test/language/literals/regexp/S7.8.5_A2.4_T2.js":            true,
 
 		// generators
-		"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js":                                                        true,
-		"test/language/statements/switch/scope-lex-generator.js":                                                                      true,
-		"test/language/expressions/in/rhs-yield-present.js":                                                                           true,
-		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-yield-expression.js":                                true,
-		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-generator-function-declaration.js":                  true,
-		"test/built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js":                                     true,
-		"test/built-ins/Object/seal/seal-generatorfunction.js":                                                                        true,
-		"test/language/statements/class/syntax/class-declaration-computed-method-generator-definition.js":                             true,
-		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-yield-expression.js":                true,
-		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-generator-function-declaration.js":  true,
-		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-yield-expression.js":                        true,
-		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-generator-function-declaration.js":          true,
-		"test/language/statements/class/cpn-class-decl-computed-property-name-from-yield-expression.js":                               true,
-		"test/language/statements/class/cpn-class-decl-computed-property-name-from-generator-function-declaration.js":                 true,
-		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-yield-expression.js":                     true,
-		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-generator-function-declaration.js":       true,
-		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-yield-expression.js":                       true,
-		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-generator-function-declaration.js":         true,
-		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-yield-expression.js":                              true,
-		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-generator-function-declaration.js":                true,
-		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-yield-expression.js":                    true,
-		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-yield-expression.js":               true,
-		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-generator-function-declaration.js":      true,
-		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-generator-function-declaration.js": true,
-		"test/language/statements/class/static-init-arguments-methods.js":                                                             true,
-		"test/language/statements/class/static-init-arguments-functions.js":                                                           true,
-		"test/language/expressions/object/method-definition/static-init-await-reference-generator.js":                                 true,
-		"test/language/expressions/generators/static-init-await-binding.js":                                                           true,
-		"test/language/expressions/generators/static-init-await-reference.js":                                                         true,
-		"test/language/expressions/optional-chaining/member-expression.js":                                                            true,
-		"test/language/expressions/class/elements/private-generator-method-name.js":                                                   true,
-		"test/language/statements/class/elements/private-generator-method-name.js":                                                    true,
-		"test/language/expressions/in/private-field-rhs-yield-present.js":                                                             true,
-		"test/language/expressions/class/elements/private-static-generator-method-name.js":                                            true,
-		"test/language/expressions/class/elements/private-static-async-generator-method-name.js":                                      true,
-		"test/language/computed-property-names/class/static/generator-prototype.js":                                                   true,
-		"test/language/computed-property-names/class/method/constructor-can-be-generator.js":                                          true,
-		"test/language/computed-property-names/class/static/generator-constructor.js":                                                 true,
-		"test/language/computed-property-names/class/method/generator.js":                                                             true,
-		"test/language/computed-property-names/object/method/generator.js":                                                            true,
-		"test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js":                     true,
-		"test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js":                      true,
-
-		// async
-		"test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js":      true,
-		"test/language/statements/switch/scope-lex-async-generator.js":                                                                 true,
-		"test/language/statements/switch/scope-lex-async-function.js":                                                                  true,
-		"test/language/statements/for-of/head-lhs-async-invalid.js":                                                                    true,
-		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-async-arrow-function-expression.js":                  true,
-		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-await-expression.js":                                 true,
-		"test/language/statements/async-function/evaluation-body.js":                                                                   true,
-		"test/language/expressions/object/method-definition/object-method-returns-promise.js":                                          true,
-		"test/language/expressions/object/method-definition/async-super-call-param.js":                                                 true,
-		"test/language/expressions/object/method-definition/async-super-call-body.js":                                                  true,
-		"test/built-ins/Object/seal/seal-asyncgeneratorfunction.js":                                                                    true,
-		"test/built-ins/Object/seal/seal-asyncfunction.js":                                                                             true,
-		"test/built-ins/Object/seal/seal-asyncarrowfunction.js":                                                                        true,
-		"test/language/statements/for/head-init-async-of.js":                                                                           true,
-		"test/language/reserved-words/await-module.js":                                                                                 true,
-		"test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js":                                          true,
-		"test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js":                           true,
-		"test/language/expressions/optional-chaining/member-expression-async-this.js":                                                  true,
-		"test/language/expressions/optional-chaining/member-expression-async-literal.js":                                               true,
-		"test/language/expressions/optional-chaining/member-expression-async-identifier.js":                                            true,
-		"test/language/expressions/optional-chaining/iteration-statement-for-await-of.js":                                              true,
-		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-async-arrow-function-expression.js":  true,
-		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-async-arrow-function-expression.js":          true,
-		"test/language/statements/class/cpn-class-decl-computed-property-name-from-async-arrow-function-expression.js":                 true,
-		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-async-arrow-function-expression.js":       true,
-		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-async-arrow-function-expression.js":         true,
-		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-async-arrow-function-expression.js":                true,
-		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-async-arrow-function-expression.js":      true,
-		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-async-arrow-function-expression.js": true,
-		"test/language/statements/let/static-init-await-binding-invalid.js":                                                            true,
-		"test/language/statements/labeled/static-init-invalid-await.js":                                                                true,
-		"test/language/statements/variable/dstr/obj-ptrn-elem-id-static-init-await-invalid.js":                                         true,
-		"test/language/statements/variable/static-init-await-binding-invalid.js":                                                       true,
-		"test/language/statements/variable/dstr/ary-ptrn-elem-id-static-init-await-invalid.js":                                         true,
-		"test/language/statements/try/static-init-await-binding-invalid.js":                                                            true,
-		"test/language/statements/function/static-init-await-binding-invalid.js":                                                       true,
-		"test/language/statements/const/static-init-await-binding-invalid.js":                                                          true,
-		"test/language/statements/class/static-init-await-binding-invalid.js":                                                          true,
-		"test/language/identifier-resolution/static-init-invalid-await.js":                                                             true,
-		"test/language/expressions/class/static-init-await-binding.js":                                                                 true,
-		"test/language/expressions/class/heritage-async-arrow-function.js":                                                             true,
-		"test/language/expressions/arrow-function/static-init-await-reference.js":                                                      true,
-		"test/language/expressions/arrow-function/static-init-await-binding.js":                                                        true,
-		"test/language/expressions/object/method-definition/static-init-await-binding-generator.js":                                    true,
-		"test/language/expressions/object/identifier-shorthand-static-init-await-invalid.js":                                           true,
-		"test/language/expressions/class/heritage-arrow-function.js":                                                                   true,
-		"test/language/expressions/class/elements/private-async-generator-method-name.js":                                              true,
-		"test/language/expressions/class/elements/private-async-method-name.js":                                                        true,
-		"test/language/statements/class/elements/private-async-generator-method-name.js":                                               true,
-		"test/language/statements/class/elements/private-async-method-name.js":                                                         true,
-		"test/language/expressions/in/private-field-rhs-await-present.js":                                                              true,
-		"test/language/expressions/class/elements/private-static-async-method-name.js":                                                 true,
-		"test/language/comments/hashbang/function-constructor.js":                                                                      true,
+		"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js":                                                                        true,
+		"test/language/statements/switch/scope-lex-generator.js":                                                                                      true,
+		"test/language/expressions/in/rhs-yield-present.js":                                                                                           true,
+		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-yield-expression.js":                                                true,
+		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-generator-function-declaration.js":                                  true,
+		"test/built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js":                                                     true,
+		"test/built-ins/Object/seal/seal-generatorfunction.js":                                                                                        true,
+		"test/language/statements/class/syntax/class-declaration-computed-method-generator-definition.js":                                             true,
+		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-yield-expression.js":                                true,
+		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-generator-function-declaration.js":                  true,
+		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-yield-expression.js":                                        true,
+		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-generator-function-declaration.js":                          true,
+		"test/language/statements/class/cpn-class-decl-computed-property-name-from-yield-expression.js":                                               true,
+		"test/language/statements/class/cpn-class-decl-computed-property-name-from-generator-function-declaration.js":                                 true,
+		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-yield-expression.js":                                     true,
+		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-generator-function-declaration.js":                       true,
+		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-yield-expression.js":                                       true,
+		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-generator-function-declaration.js":                         true,
+		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-yield-expression.js":                                              true,
+		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-generator-function-declaration.js":                                true,
+		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-yield-expression.js":                                    true,
+		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-yield-expression.js":                               true,
+		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-generator-function-declaration.js":                      true,
+		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-generator-function-declaration.js":                 true,
+		"test/language/statements/class/static-init-arguments-methods.js":                                                                             true,
+		"test/language/statements/class/static-init-arguments-functions.js":                                                                           true,
+		"test/language/expressions/object/method-definition/static-init-await-reference-generator.js":                                                 true,
+		"test/language/expressions/generators/static-init-await-binding.js":                                                                           true,
+		"test/language/expressions/generators/static-init-await-reference.js":                                                                         true,
+		"test/language/expressions/optional-chaining/member-expression.js":                                                                            true,
+		"test/language/expressions/class/elements/private-generator-method-name.js":                                                                   true,
+		"test/language/statements/class/elements/private-generator-method-name.js":                                                                    true,
+		"test/language/expressions/in/private-field-rhs-yield-present.js":                                                                             true,
+		"test/language/expressions/class/elements/private-static-generator-method-name.js":                                                            true,
+		"test/language/computed-property-names/class/static/generator-prototype.js":                                                                   true,
+		"test/language/computed-property-names/class/method/constructor-can-be-generator.js":                                                          true,
+		"test/language/computed-property-names/class/static/generator-constructor.js":                                                                 true,
+		"test/language/computed-property-names/class/method/generator.js":                                                                             true,
+		"test/language/computed-property-names/object/method/generator.js":                                                                            true,
+		"test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js":                                     true,
+		"test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js":                                      true,
+		"test/language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js":                     true,
+		"test/language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js":                        true,
+		"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":              true,
+		"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js":                  true,
+		"test/language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js":                    true,
+		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js":            true,
+		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js":        true,
+		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js":      true,
+		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":  true,
+		"test/language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js":                         true,
+		"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js":                   true,
+		"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":               true,
+		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js":           true,
+		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js":       true,
+		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
+		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js":     true,
+		"test/built-ins/Object/seal/seal-asyncgeneratorfunction.js":                                                                                   true,
+		"test/language/statements/switch/scope-lex-async-generator.js":                                                                                true,
+		"test/language/statements/class/elements/private-async-generator-method-name.js":                                                              true,
+		"test/language/expressions/object/method-definition/static-init-await-binding-generator.js":                                                   true,
+		"test/language/expressions/class/elements/private-async-generator-method-name.js":                                                             true,
+		"test/language/comments/hashbang/function-constructor.js":                                                                                     true,
+		"test/language/expressions/async-generator/name.js":                                                                                           true,
+
+		// async iterator
+		"test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true,
 
 		// legacy number literals
 		"test/language/literals/numeric/non-octal-decimal-integer.js": true,
@@ -260,7 +230,6 @@ var (
 	featuresBlackList = []string{
 		"async-iteration",
 		"Symbol.asyncIterator",
-		"async-functions",
 		"BigInt",
 		"generators",
 		"String.prototype.replaceAll",
@@ -304,31 +273,35 @@ func init() {
 		"test/language/identifiers/start-unicode-14.",
 		"test/language/identifiers/part-unicode-14.",
 
-		// async
-		"test/language/eval-code/direct/async-",
-		"test/language/expressions/async-",
-		"test/language/expressions/await/",
-		"test/language/statements/async-function/",
+		// generators and async generators (harness/hidden-constructors.js)
 		"test/built-ins/Async",
-		"test/language/statements/class/elements/private-static-async-",
-		"test/language/statements/class/elements/wrapped-in-sc-rs-static-async-",
-		"test/language/expressions/class/elements/wrapped-in-sc-rs-static-async-",
-		"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-",
-		"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-",
-		"test/language/statements/class/elements/after-same-line-method-rs-static-async-",
-		"test/language/expressions/class/elements/after-same-line-method-rs-static-async-",
-		"test/language/statements/class/elements/new-sc-line-method-rs-static-async-",
-		"test/language/expressions/class/elements/new-sc-line-method-rs-static-async-",
-		"test/language/statements/class/elements/new-no-sc-line-method-rs-static-async-",
-		"test/language/expressions/class/elements/new-no-sc-line-method-rs-static-async-",
-		"test/language/statements/class/elements/same-line-method-rs-static-async-",
-		"test/language/expressions/class/elements/same-line-method-rs-static-async-",
-		"test/language/statements/class/elements/regular-definitions-rs-static-async-",
-		"test/language/expressions/class/elements/regular-definitions-rs-static-async-",
-		"test/language/statements/class/elements/multiple-stacked-definitions-rs-static-async-",
-		"test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-",
-		"test/language/statements/class/elements/multiple-definitions-rs-static-async-",
-		"test/language/expressions/class/elements/multiple-definitions-rs-static-async-",
+
+		// async generators
+		"test/language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-",
+		"test/language/statements/class/elements/same-line-method-rs-static-async-generator-",
+		"test/language/statements/class/elements/regular-definitions-rs-static-async-generator-",
+		"test/language/statements/class/elements/private-static-async-generator-",
+		"test/language/statements/class/elements/new-sc-line-method-rs-static-async-generator-",
+		"test/language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
+		"test/language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-",
+		"test/language/statements/class/elements/multiple-definitions-rs-static-async-generator-",
+		"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
+		"test/language/statements/class/elements/after-same-line-method-rs-static-async-generator-",
+		"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
+
+		"test/language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-",
+		"test/language/expressions/class/elements/same-line-method-rs-static-async-generator-",
+		"test/language/expressions/class/elements/regular-definitions-rs-static-async-generator-",
+		"test/language/expressions/class/elements/private-static-async-generator-",
+		"test/language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-",
+		"test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
+		"test/language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-",
+		"test/language/expressions/class/elements/multiple-definitions-rs-static-async-generator-",
+		"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
+		"test/language/expressions/class/elements/after-same-line-method-rs-static-async-generator-",
+		"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
+
+		"test/language/eval-code/direct/async-gen-",
 
 		// generators
 		"test/language/eval-code/direct/gen-",

+ 11 - 2
token/token_const.go

@@ -128,6 +128,9 @@ const (
 
 	LET
 	STATIC
+	ASYNC
+	AWAIT
+	YIELD
 )
 
 var token2string = [...]string{
@@ -210,6 +213,9 @@ var token2string = [...]string{
 	CASE:                        "case",
 	VOID:                        "void",
 	WITH:                        "with",
+	ASYNC:                       "async",
+	AWAIT:                       "await",
+	YIELD:                       "yield",
 	CONST:                       "const",
 	WHILE:                       "while",
 	BREAK:                       "break",
@@ -268,6 +274,9 @@ var keywordTable = map[string]_keyword{
 	"with": {
 		token: WITH,
 	},
+	"async": {
+		token: ASYNC,
+	},
 	"while": {
 		token: WHILE,
 	},
@@ -374,10 +383,10 @@ var keywordTable = map[string]_keyword{
 		strict: true,
 	},
 	"await": {
-		token: KEYWORD,
+		token: AWAIT,
 	},
 	"yield": {
-		token: KEYWORD,
+		token: YIELD,
 	},
 	"false": {
 		token: BOOLEAN,

File diff suppressed because it is too large
+ 377 - 263
vm.go


+ 1 - 6
vm_test.go

@@ -33,7 +33,6 @@ func TestVM1(t *testing.T) {
 			setElem,
 			pop,
 			loadDynamic("v"),
-			halt,
 		},
 	}
 
@@ -96,7 +95,6 @@ func BenchmarkVmNOP2(b *testing.B) {
 		//loadVal(1).exec,
 		//add.exec,
 		jump(1).exec,
-		halt.exec,
 	}
 
 	r := &Runtime{}
@@ -108,9 +106,8 @@ func BenchmarkVmNOP2(b *testing.B) {
 	}
 
 	for i := 0; i < b.N; i++ {
-		vm.halt = false
 		vm.pc = 0
-		for !vm.halt {
+		for !vm.halted() {
 			prg[vm.pc](vm)
 		}
 		//vm.sp--
@@ -133,7 +130,6 @@ func BenchmarkVmNOP(b *testing.B) {
 		code: []instruction{
 			jump(1),
 			//jump(1),
-			halt,
 		},
 	}
 
@@ -159,7 +155,6 @@ func BenchmarkVm1(b *testing.B) {
 			loadVal(0),
 			loadVal(1),
 			add,
-			halt,
 		},
 	}
 

Some files were not shown because too many files changed in this diff