package goja

import (
	"fmt"
	"math"
	"math/big"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/projectdiscovery/goja/unistring"
)

const (
	maxInt = 1 << 53

	tryPanicMarker = -2
)

type valueStack []Value

type stash struct {
	values    []Value
	extraArgs []Value
	names     map[unistring.String]uint32
	obj       *Object

	outer *stash

	// If this is a top-level function stash, sets the type of the function. If set, dynamic var declarations
	// created by direct eval go here.
	funcType funcType
}

type context struct {
	prg       *Program
	stash     *stash
	privEnv   *privateEnv
	newTarget Value
	result    Value
	pc, sb    int
	args      int
}

type tryFrame struct {
	// holds an uncaught exception for the 'finally' block
	exception *Exception

	callStackLen, iterLen, refLen uint32

	sp      int32
	stash   *stash
	privEnv *privateEnv

	catchPos, finallyPos, finallyRet int32
}

type execCtx struct {
	context
	stack     []Value
	tryStack  []tryFrame
	iterStack []iterStackItem
	refStack  []ref
}

func (vm *vm) suspend(ectx *execCtx, tryStackLen, iterStackLen, refStackLen uint32) {
	vm.saveCtx(&ectx.context)
	ectx.stack = append(ectx.stack[:0], vm.stack[vm.sb-1:vm.sp]...)
	if len(vm.tryStack) > int(tryStackLen) {
		ectx.tryStack = append(ectx.tryStack[:0], vm.tryStack[tryStackLen:]...)
		vm.tryStack = vm.tryStack[:tryStackLen]
		sp := int32(vm.sb - 1)
		for i := range ectx.tryStack {
			tf := &ectx.tryStack[i]
			tf.iterLen -= iterStackLen
			tf.refLen -= refStackLen
			tf.sp -= sp
		}
	}
	if len(vm.iterStack) > int(iterStackLen) {
		ectx.iterStack = append(ectx.iterStack[:0], vm.iterStack[iterStackLen:]...)
		vm.iterStack = vm.iterStack[:iterStackLen]
	}
	if len(vm.refStack) > int(refStackLen) {
		ectx.refStack = append(ectx.refStack[:0], vm.refStack[refStackLen:]...)
		vm.refStack = vm.refStack[:refStackLen]
	}
}

func (vm *vm) resume(ctx *execCtx) {
	vm.restoreCtx(&ctx.context)
	sp := vm.sp
	vm.sb = sp + 1
	vm.stack.expand(sp + len(ctx.stack))
	copy(vm.stack[sp:], ctx.stack)
	vm.sp += len(ctx.stack)
	for i := range ctx.tryStack {
		tf := &ctx.tryStack[i]
		tf.callStackLen = uint32(len(vm.callStack))
		tf.iterLen += uint32(len(vm.iterStack))
		tf.refLen += uint32(len(vm.refStack))
		tf.sp += int32(sp)
	}
	vm.tryStack = append(vm.tryStack, ctx.tryStack...)
	vm.iterStack = append(vm.iterStack, ctx.iterStack...)
	vm.refStack = append(vm.refStack, ctx.refStack...)
}

type iterStackItem struct {
	val  Value
	f    iterNextFunc
	iter *iteratorRecord
}

type ref interface {
	get() Value
	set(Value)
	init(Value)
	refname() unistring.String
}

type stashRef struct {
	n   unistring.String
	v   *[]Value
	idx int
}

func (r *stashRef) get() Value {
	return nilSafe((*r.v)[r.idx])
}

func (r *stashRef) set(v Value) {
	(*r.v)[r.idx] = v
}

func (r *stashRef) init(v Value) {
	r.set(v)
}

func (r *stashRef) refname() unistring.String {
	return r.n
}

type thisRef struct {
	v   *[]Value
	idx int
}

func (r *thisRef) get() Value {
	v := (*r.v)[r.idx]
	if v == nil {
		panic(referenceError("Must call super constructor in derived class before accessing 'this'"))
	}

	return v
}

func (r *thisRef) set(v Value) {
	ptr := &(*r.v)[r.idx]
	if *ptr != nil {
		panic(referenceError("Super constructor may only be called once"))
	}
	*ptr = v
}

func (r *thisRef) init(v Value) {
	r.set(v)
}

func (r *thisRef) refname() unistring.String {
	return thisBindingName
}

type stashRefLex struct {
	stashRef
}

func (r *stashRefLex) get() Value {
	v := (*r.v)[r.idx]
	if v == nil {
		panic(errAccessBeforeInit)
	}
	return v
}

func (r *stashRefLex) set(v Value) {
	p := &(*r.v)[r.idx]
	if *p == nil {
		panic(errAccessBeforeInit)
	}
	*p = v
}

func (r *stashRefLex) init(v Value) {
	(*r.v)[r.idx] = v
}

type stashRefConst struct {
	stashRefLex
	strictConst bool
}

func (r *stashRefConst) set(v Value) {
	if r.strictConst {
		panic(errAssignToConst)
	}
}

type objRef struct {
	base   *Object
	name   Value
	this   Value
	strict bool

	nameConverted bool
}

func (r *objRef) getKey() Value {
	if !r.nameConverted {
		r.name = toPropertyKey(r.name)
		r.nameConverted = true
	}
	return r.name
}

func (r *objRef) get() Value {
	return r.base.get(r.getKey(), r.this)
}

func (r *objRef) set(v Value) {
	key := r.getKey()
	if r.this != nil {
		r.base.set(key, v, r.this, r.strict)
	} else {
		r.base.setOwn(key, v, r.strict)
	}
}

func (r *objRef) init(v Value) {
	if r.this != nil {
		r.base.set(r.getKey(), v, r.this, r.strict)
	} else {
		r.base.setOwn(r.getKey(), v, r.strict)
	}
}

func (r *objRef) refname() unistring.String {
	return r.getKey().string()
}

type objStrRef struct {
	base    *Object
	name    unistring.String
	this    Value
	strict  bool
	binding bool
}

func (r *objStrRef) get() Value {
	if v := r.base.self.getStr(r.name, r.this); v != nil {
		return v
	}
	if r.binding {
		rt := r.base.runtime
		panic(rt.newReferenceError(r.name))
	}
	return _undefined
}

func (r *objStrRef) set(v Value) {
	if r.strict && r.binding && !r.base.self.hasOwnPropertyStr(r.name) {
		panic(referenceError(fmt.Sprintf("%s is not defined", r.name)))
	}
	if r.this != nil {
		r.base.setStr(r.name, v, r.this, r.strict)
	} else {
		r.base.self.setOwnStr(r.name, v, r.strict)
	}
}

func (r *objStrRef) init(v Value) {
	if r.this != nil {
		r.base.setStr(r.name, v, r.this, r.strict)
	} else {
		r.base.self.setOwnStr(r.name, v, r.strict)
	}
}

func (r *objStrRef) refname() unistring.String {
	return r.name
}

type privateRefRes struct {
	base *Object
	name *resolvedPrivateName
}

func (p *privateRefRes) get() Value {
	return (*getPrivatePropRes)(p.name)._get(p.base, p.base.runtime.vm)
}

func (p *privateRefRes) set(value Value) {
	(*setPrivatePropRes)(p.name)._set(p.base, value, p.base.runtime.vm)
}

func (p *privateRefRes) init(value Value) {
	panic("not supported")
}

func (p *privateRefRes) refname() unistring.String {
	return p.name.string()
}

type privateRefId struct {
	base *Object
	id   *privateId
}

func (p *privateRefId) get() Value {
	return p.base.runtime.vm.getPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod)
}

func (p *privateRefId) set(value Value) {
	p.base.runtime.vm.setPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod, value)
}

func (p *privateRefId) init(value Value) {
	panic("not supported")
}

func (p *privateRefId) refname() unistring.String {
	return p.id.string()
}

type unresolvedRef struct {
	runtime *Runtime
	name    unistring.String
}

func (r *unresolvedRef) get() Value {
	r.runtime.throwReferenceError(r.name)
	panic("Unreachable")
}

func (r *unresolvedRef) set(Value) {
	r.get()
}

func (r *unresolvedRef) init(Value) {
	r.get()
}

func (r *unresolvedRef) refname() unistring.String {
	return r.name
}

type vm struct {
	r            *Runtime
	prg          *Program
	pc           int
	stack        valueStack
	sp, sb, args int

	stash     *stash
	privEnv   *privateEnv
	callStack []context
	iterStack []iterStackItem
	refStack  []ref
	tryStack  []tryFrame
	newTarget Value
	result    Value

	maxCallStackSize int

	stashAllocs int

	interrupted   uint32
	interruptVal  interface{}
	interruptLock sync.Mutex

	curAsyncRunner *asyncRunner

	profTracker *profTracker
}

type instruction interface {
	exec(*vm)
}

func intToValue(i int64) Value {
	if idx := 256 + i; idx >= 0 && idx < 256 {
		return intCache[idx]
	}
	if i >= -maxInt && i <= maxInt {
		return valueInt(i)
	}
	return valueFloat(i)
}

func floatToInt(f float64) (result int64, ok bool) {
	if (f != 0 || !math.Signbit(f)) && !math.IsInf(f, 0) && f == math.Trunc(f) && f >= -maxInt && f <= maxInt {
		return int64(f), true
	}
	return 0, false
}

func floatToValue(f float64) (result Value) {
	if i, ok := floatToInt(f); ok {
		return intToValue(i)
	}
	switch {
	case f == 0:
		return _negativeZero
	case math.IsNaN(f):
		return _NaN
	case math.IsInf(f, 1):
		return _positiveInf
	case math.IsInf(f, -1):
		return _negativeInf
	}
	return valueFloat(f)
}

func toNumeric(value Value) Value {
	switch v := value.(type) {
	case valueInt, *valueBigInt:
		return v
	case valueFloat:
		return floatToValue(float64(v))
	case *Object:
		primValue := v.toPrimitiveNumber()
		if bigint, ok := primValue.(*valueBigInt); ok {
			return bigint
		}
		return primValue.ToNumber()
	}
	return value.ToNumber()
}

func (s *valueStack) expand(idx int) {
	if idx < len(*s) {
		return
	}
	idx++
	if idx < cap(*s) {
		*s = (*s)[:idx]
	} else {
		var newCap int
		if idx < 1024 {
			newCap = idx * 2
		} else {
			newCap = (idx + 1025) &^ 1023
		}
		n := make([]Value, idx, newCap)
		copy(n, *s)
		*s = n
	}
}

func stashObjHas(obj *Object, name unistring.String) bool {
	if obj.self.hasPropertyStr(name) {
		if unscopables, ok := obj.self.getSym(SymUnscopables, nil).(*Object); ok {
			if b := unscopables.self.getStr(name, nil); b != nil {
				return !b.ToBoolean()
			}
		}
		return true
	}
	return false
}

func (s *stash) isVariable() bool {
	return s.funcType != funcNone
}

func (s *stash) initByIdx(idx uint32, v Value) {
	if s.obj != nil {
		panic("Attempt to init by idx into an object scope")
	}
	s.values[idx] = v
}

func (s *stash) initByName(name unistring.String, v Value) {
	if idx, exists := s.names[name]; exists {
		s.values[idx&^maskTyp] = v
	} else {
		panic(referenceError(fmt.Sprintf("%s is not defined", name)))
	}
}

func (s *stash) getByIdx(idx uint32) Value {
	return s.values[idx]
}

func (s *stash) getByName(name unistring.String) (v Value, exists bool) {
	if s.obj != nil {
		if stashObjHas(s.obj, name) {
			return nilSafe(s.obj.self.getStr(name, nil)), true
		}
		return nil, false
	}
	if idx, exists := s.names[name]; exists {
		v := s.values[idx&^maskTyp]
		if v == nil {
			if idx&maskVar == 0 {
				panic(errAccessBeforeInit)
			} else {
				v = _undefined
			}
		}
		return v, true
	}
	return nil, false
}

func (s *stash) getRefByName(name unistring.String, strict bool) ref {
	if obj := s.obj; obj != nil {
		if stashObjHas(obj, name) {
			return &objStrRef{
				base:    obj,
				name:    name,
				strict:  strict,
				binding: true,
			}
		}
	} else {
		if idx, exists := s.names[name]; exists {
			if idx&maskVar == 0 {
				if idx&maskConst == 0 {
					return &stashRefLex{
						stashRef: stashRef{
							n:   name,
							v:   &s.values,
							idx: int(idx &^ maskTyp),
						},
					}
				} else {
					return &stashRefConst{
						stashRefLex: stashRefLex{
							stashRef: stashRef{
								n:   name,
								v:   &s.values,
								idx: int(idx &^ maskTyp),
							},
						},
						strictConst: strict || (idx&maskStrict != 0),
					}
				}
			} else {
				return &stashRef{
					n:   name,
					v:   &s.values,
					idx: int(idx &^ maskTyp),
				}
			}
		}
	}
	return nil
}

func (s *stash) createBinding(name unistring.String, deletable bool) {
	if s.names == nil {
		s.names = make(map[unistring.String]uint32)
	}
	if _, exists := s.names[name]; !exists {
		idx := uint32(len(s.names)) | maskVar
		if deletable {
			idx |= maskDeletable
		}
		s.names[name] = idx
		s.values = append(s.values, _undefined)
	}
}

func (s *stash) createLexBinding(name unistring.String, isConst bool) {
	if s.names == nil {
		s.names = make(map[unistring.String]uint32)
	}
	if _, exists := s.names[name]; !exists {
		idx := uint32(len(s.names))
		if isConst {
			idx |= maskConst | maskStrict
		}
		s.names[name] = idx
		s.values = append(s.values, nil)
	}
}

func (s *stash) deleteBinding(name unistring.String) {
	delete(s.names, name)
}

func (vm *vm) newStash() {
	vm.stash = &stash{
		outer: vm.stash,
	}
	vm.stashAllocs++
}

func (vm *vm) init() {
	vm.sb = -1
	vm.stash = &vm.r.global.stash
	vm.maxCallStackSize = math.MaxInt32
}

func (vm *vm) halted() bool {
	pc := vm.pc
	return pc < 0 || pc >= len(vm.prg.code)
}

func (vm *vm) run() {
	if vm.profTracker != nil && !vm.runWithProfiler() {
		return
	}
	count := 0
	interrupted := false
	for {
		if count == 0 {
			if atomic.LoadInt32(&globalProfiler.enabled) == 1 && !vm.runWithProfiler() {
				return
			}
			count = 100
		} else {
			count--
		}
		if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted {
			break
		}
		pc := vm.pc
		if pc < 0 || pc >= len(vm.prg.code) {
			break
		}
		vm.prg.code[pc].exec(vm)
	}

	if interrupted {
		vm.interruptLock.Lock()
		v := &InterruptedError{
			iface: vm.interruptVal,
		}
		v.stack = vm.captureStack(nil, 0)
		vm.interruptLock.Unlock()
		panic(v)
	}
}

func (vm *vm) runWithProfiler() bool {
	pt := vm.profTracker
	if pt == nil {
		pt = globalProfiler.p.registerVm()
		vm.profTracker = pt
		defer func() {
			atomic.StoreInt32(&vm.profTracker.finished, 1)
			vm.profTracker = nil
		}()
	}
	interrupted := false
	for {
		if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted {
			return true
		}
		pc := vm.pc
		if pc < 0 || pc >= len(vm.prg.code) {
			break
		}
		vm.prg.code[pc].exec(vm)
		req := atomic.LoadInt32(&pt.req)
		if req == profReqStop {
			return true
		}
		if req == profReqDoSample {
			pt.stop = time.Now()

			pt.numFrames = len(vm.r.CaptureCallStack(len(pt.frames), pt.frames[:0]))
			pt.frames[0].pc = pc
			atomic.StoreInt32(&pt.req, profReqSampleReady)
		}
	}

	return false
}

func (vm *vm) Interrupt(v interface{}) {
	vm.interruptLock.Lock()
	vm.interruptVal = v
	atomic.StoreUint32(&vm.interrupted, 1)
	vm.interruptLock.Unlock()
}

func (vm *vm) ClearInterrupt() {
	atomic.StoreUint32(&vm.interrupted, 0)
}

func getFuncName(stack []Value, sb int) unistring.String {
	if sb > 0 {
		if f, ok := stack[sb-1].(*Object); ok {
			if _, isProxy := f.self.(*proxyObject); isProxy {
				return "proxy"
			}
			return nilSafe(f.self.getStr("name", nil)).string()
		}
	}
	return ""
}

func (vm *vm) captureStack(stack []StackFrame, ctxOffset int) []StackFrame {
	// Unroll the context stack
	if vm.prg != nil || vm.sb > 0 {
		var funcName unistring.String
		if vm.prg != nil {
			funcName = vm.prg.funcName
		} else {
			funcName = getFuncName(vm.stack, vm.sb)
		}
		stack = append(stack, StackFrame{prg: vm.prg, pc: vm.pc, funcName: funcName})
	}
	for i := len(vm.callStack) - 1; i > ctxOffset-1; i-- {
		frame := &vm.callStack[i]
		if frame.prg != nil || frame.sb > 0 {
			var funcName unistring.String
			if prg := frame.prg; prg != nil {
				funcName = prg.funcName
			} else {
				funcName = getFuncName(vm.stack, frame.sb)
			}
			stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: frame.pc, funcName: funcName})
		}
	}
	if ctxOffset == 0 && vm.curAsyncRunner != nil {
		stack = vm.captureAsyncStack(stack, vm.curAsyncRunner)
	}
	return stack
}

func (vm *vm) captureAsyncStack(stack []StackFrame, runner *asyncRunner) []StackFrame {
	if promise, _ := runner.promiseCap.promise.self.(*Promise); promise != nil {
		if len(promise.fulfillReactions) == 1 {
			if r := promise.fulfillReactions[0].asyncRunner; r != nil {
				ctx := &r.gen.ctx
				if ctx.prg != nil || ctx.sb > 0 {
					var funcName unistring.String
					if prg := ctx.prg; prg != nil {
						funcName = prg.funcName
					} else {
						funcName = getFuncName(ctx.stack, 1)
					}
					stack = append(stack, StackFrame{prg: ctx.prg, pc: ctx.pc, funcName: funcName})
				}
				stack = vm.captureAsyncStack(stack, r)
			}
		}
	}

	return stack
}

func (vm *vm) pushTryFrame(catchPos, finallyPos int32) {
	vm.tryStack = append(vm.tryStack, tryFrame{
		callStackLen: uint32(len(vm.callStack)),
		iterLen:      uint32(len(vm.iterStack)),
		refLen:       uint32(len(vm.refStack)),
		sp:           int32(vm.sp),
		stash:        vm.stash,
		privEnv:      vm.privEnv,
		catchPos:     catchPos,
		finallyPos:   finallyPos,
		finallyRet:   -1,
	})
}

func (vm *vm) popTryFrame() {
	vm.tryStack = vm.tryStack[:len(vm.tryStack)-1]
}

func (vm *vm) restoreStacks(iterLen, refLen uint32) (ex *Exception) {
	// Restore other stacks
	iterTail := vm.iterStack[iterLen:]
	for i := len(iterTail) - 1; i >= 0; i-- {
		if iter := iterTail[i].iter; iter != nil {
			ex1 := vm.try(func() {
				iter.returnIter()
			})
			if ex1 != nil && ex == nil {
				ex = ex1
			}
		}
		iterTail[i] = iterStackItem{}
	}
	vm.iterStack = vm.iterStack[:iterLen]
	refTail := vm.refStack[refLen:]
	for i := range refTail {
		refTail[i] = nil
	}
	vm.refStack = vm.refStack[:refLen]
	return
}

func (vm *vm) handleThrow(arg interface{}) *Exception {
	ex := vm.exceptionFromValue(arg)
	for len(vm.tryStack) > 0 {
		tf := &vm.tryStack[len(vm.tryStack)-1]
		if tf.catchPos == -1 && tf.finallyPos == -1 || ex == nil && tf.catchPos != tryPanicMarker {
			tf.exception = nil
			vm.popTryFrame()
			continue
		}
		if int(tf.callStackLen) < len(vm.callStack) {
			ctx := &vm.callStack[tf.callStackLen]
			vm.prg, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args =
				ctx.prg, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args
			vm.callStack = vm.callStack[:tf.callStackLen]
		}
		vm.sp = int(tf.sp)
		vm.stash = tf.stash
		vm.privEnv = tf.privEnv
		_ = vm.restoreStacks(tf.iterLen, tf.refLen)

		if tf.catchPos == tryPanicMarker {
			break
		}

		if tf.catchPos >= 0 {
			// exception is caught
			vm.push(ex.val)
			vm.pc = int(tf.catchPos)
			tf.catchPos = -1
			return nil
		}
		if tf.finallyPos >= 0 {
			// no 'catch' block, but there is a 'finally' block
			tf.exception = ex
			vm.pc = int(tf.finallyPos)
			tf.finallyPos = -1
			tf.finallyRet = -1
			return nil
		}
	}
	if ex == nil {
		panic(arg)
	}
	return ex
}

// Calls to this method must be made from the run() loop and must be the last statement before 'return'.
// In all other cases exceptions must be thrown using panic().
func (vm *vm) throw(v interface{}) {
	if ex := vm.handleThrow(v); ex != nil {
		panic(ex)
	}
}

func (vm *vm) try(f func()) (ex *Exception) {
	vm.pushTryFrame(tryPanicMarker, -1)
	defer vm.popTryFrame()

	defer func() {
		if x := recover(); x != nil {
			ex = vm.handleThrow(x)
		}
	}()

	f()
	return
}

func (vm *vm) runTry() (ex *Exception) {
	vm.pushTryFrame(tryPanicMarker, -1)
	defer vm.popTryFrame()

	for {
		ex = vm.runTryInner()
		if ex != nil || vm.halted() {
			return
		}
	}
}

func (vm *vm) runTryInner() (ex *Exception) {
	defer func() {
		if x := recover(); x != nil {
			ex = vm.handleThrow(x)
		}
	}()

	vm.run()
	return
}

func (vm *vm) push(v Value) {
	vm.stack.expand(vm.sp)
	vm.stack[vm.sp] = v
	vm.sp++
}

func (vm *vm) pop() Value {
	vm.sp--
	return vm.stack[vm.sp]
}

func (vm *vm) peek() Value {
	return vm.stack[vm.sp-1]
}

func (vm *vm) saveCtx(ctx *context) {
	ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args =
		vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args
}

func (vm *vm) pushCtx() {
	if len(vm.callStack) > vm.maxCallStackSize {
		ex := &StackOverflowError{}
		ex.stack = vm.captureStack(nil, 0)
		panic(ex)
	}
	vm.callStack = append(vm.callStack, context{})
	ctx := &vm.callStack[len(vm.callStack)-1]
	vm.saveCtx(ctx)
}

func (vm *vm) restoreCtx(ctx *context) {
	vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args =
		ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args
}

func (vm *vm) popCtx() {
	l := len(vm.callStack) - 1
	ctx := &vm.callStack[l]
	vm.restoreCtx(ctx)

	if ctx.prg != nil {
		*ctx = context{}
	}

	vm.callStack = vm.callStack[:l]
}

func (vm *vm) toCallee(v Value) *Object {
	if obj, ok := v.(*Object); ok {
		return obj
	}
	switch unresolved := v.(type) {
	case valueUnresolved:
		unresolved.throw()
		panic("Unreachable")
	case memberUnresolved:
		panic(vm.r.NewTypeError("Object has no member '%s'", unresolved.ref))
	}
	panic(vm.r.NewTypeError("Value is not an object: %s", v.toString()))
}

type loadVal struct {
	v Value
}

func (l loadVal) exec(vm *vm) {
	vm.push(l.v)
	vm.pc++
}

type _loadUndef struct{}

var loadUndef _loadUndef

func (_loadUndef) exec(vm *vm) {
	vm.push(_undefined)
	vm.pc++
}

type _loadNil struct{}

var loadNil _loadNil

func (_loadNil) exec(vm *vm) {
	vm.push(nil)
	vm.pc++
}

type _saveResult struct{}

var saveResult _saveResult

func (_saveResult) exec(vm *vm) {
	vm.sp--
	vm.result = vm.stack[vm.sp]
	vm.pc++
}

type _loadResult struct{}

var loadResult _loadResult

func (_loadResult) exec(vm *vm) {
	vm.push(vm.result)
	vm.pc++
}

type _clearResult struct{}

var clearResult _clearResult

func (_clearResult) exec(vm *vm) {
	vm.result = _undefined
	vm.pc++
}

type _loadGlobalObject struct{}

var loadGlobalObject _loadGlobalObject

func (_loadGlobalObject) exec(vm *vm) {
	vm.push(vm.r.globalObject)
	vm.pc++
}

type loadStack int

func (l loadStack) exec(vm *vm) {
	// l > 0 -- var<l-1>
	// l == 0 -- this

	if l > 0 {
		vm.push(nilSafe(vm.stack[vm.sb+vm.args+int(l)]))
	} else {
		vm.push(vm.stack[vm.sb])
	}
	vm.pc++
}

type loadStack1 int

func (l loadStack1) exec(vm *vm) {
	// args are in stash
	// l > 0 -- var<l-1>
	// l == 0 -- this

	if l > 0 {
		vm.push(nilSafe(vm.stack[vm.sb+int(l)]))
	} else {
		vm.push(vm.stack[vm.sb])
	}
	vm.pc++
}

type loadStackLex int

func (l loadStackLex) exec(vm *vm) {
	// l < 0 -- arg<-l-1>
	// l > 0 -- var<l-1>
	// l == 0 -- this
	var p *Value
	if l <= 0 {
		arg := int(-l)
		if arg > vm.args {
			vm.push(_undefined)
			vm.pc++
			return
		} else {
			p = &vm.stack[vm.sb+arg]
		}
	} else {
		p = &vm.stack[vm.sb+vm.args+int(l)]
	}
	if *p == nil {
		vm.throw(errAccessBeforeInit)
		return
	}
	vm.push(*p)
	vm.pc++
}

type loadStack1Lex int

func (l loadStack1Lex) exec(vm *vm) {
	p := &vm.stack[vm.sb+int(l)]
	if *p == nil {
		vm.throw(errAccessBeforeInit)
		return
	}
	vm.push(*p)
	vm.pc++
}

type _loadCallee struct{}

var loadCallee _loadCallee

func (_loadCallee) exec(vm *vm) {
	vm.push(vm.stack[vm.sb-1])
	vm.pc++
}

func (vm *vm) storeStack(s int) {
	// l > 0 -- var<l-1>

	if s > 0 {
		vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1]
	} else {
		panic("Illegal stack var index")
	}
	vm.pc++
}

func (vm *vm) storeStack1(s int) {
	// args are in stash
	// l > 0 -- var<l-1>

	if s > 0 {
		vm.stack[vm.sb+s] = vm.stack[vm.sp-1]
	} else {
		panic("Illegal stack var index")
	}
	vm.pc++
}

func (vm *vm) storeStackLex(s int) {
	// l < 0 -- arg<-l-1>
	// l > 0 -- var<l-1>
	var p *Value
	if s < 0 {
		p = &vm.stack[vm.sb-s]
	} else {
		p = &vm.stack[vm.sb+vm.args+s]
	}

	if *p != nil {
		*p = vm.stack[vm.sp-1]
	} else {
		panic(errAccessBeforeInit)
	}
	vm.pc++
}

func (vm *vm) storeStack1Lex(s int) {
	// args are in stash
	// s > 0 -- var<l-1>
	if s <= 0 {
		panic("Illegal stack var index")
	}
	p := &vm.stack[vm.sb+s]
	if *p != nil {
		*p = vm.stack[vm.sp-1]
	} else {
		panic(errAccessBeforeInit)
	}
	vm.pc++
}

func (vm *vm) initStack(s int) {
	if s <= 0 {
		vm.stack[vm.sb-s] = vm.stack[vm.sp-1]
	} else {
		vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1]
	}
	vm.pc++
}

func (vm *vm) initStack1(s int) {
	if s <= 0 {
		panic("Illegal stack var index")
	}
	vm.stack[vm.sb+s] = vm.stack[vm.sp-1]
	vm.pc++
}

type storeStack int

func (s storeStack) exec(vm *vm) {
	vm.storeStack(int(s))
}

type storeStack1 int

func (s storeStack1) exec(vm *vm) {
	vm.storeStack1(int(s))
}

type storeStackLex int

func (s storeStackLex) exec(vm *vm) {
	vm.storeStackLex(int(s))
}

type storeStack1Lex int

func (s storeStack1Lex) exec(vm *vm) {
	vm.storeStack1Lex(int(s))
}

type initStack int

func (s initStack) exec(vm *vm) {
	vm.initStack(int(s))
}

type initStackP int

func (s initStackP) exec(vm *vm) {
	vm.initStack(int(s))
	vm.sp--
}

type initStack1 int

func (s initStack1) exec(vm *vm) {
	vm.initStack1(int(s))
}

type initStack1P int

func (s initStack1P) exec(vm *vm) {
	vm.initStack1(int(s))
	vm.sp--
}

type storeStackP int

func (s storeStackP) exec(vm *vm) {
	vm.storeStack(int(s))
	vm.sp--
}

type storeStack1P int

func (s storeStack1P) exec(vm *vm) {
	vm.storeStack1(int(s))
	vm.sp--
}

type storeStackLexP int

func (s storeStackLexP) exec(vm *vm) {
	vm.storeStackLex(int(s))
	vm.sp--
}

type storeStack1LexP int

func (s storeStack1LexP) exec(vm *vm) {
	vm.storeStack1Lex(int(s))
	vm.sp--
}

type _toNumber struct{}

var toNumber _toNumber

func (_toNumber) exec(vm *vm) {
	vm.stack[vm.sp-1] = toNumeric(vm.stack[vm.sp-1])
	vm.pc++
}

type _add struct{}

var add _add

func (_add) exec(vm *vm) {
	right := vm.stack[vm.sp-1]
	left := vm.stack[vm.sp-2]

	if o, ok := left.(*Object); ok {
		left = o.toPrimitive()
	}

	if o, ok := right.(*Object); ok {
		right = o.toPrimitive()
	}

	var ret Value

	leftString, isLeftString := left.(String)
	rightString, isRightString := right.(String)

	if isLeftString || isRightString {
		if !isLeftString {
			leftString = left.toString()
		}
		if !isRightString {
			rightString = right.toString()
		}
		ret = leftString.Concat(rightString)
	} else {
		switch left := left.(type) {
		case valueInt:
			switch right := right.(type) {
			case valueInt:
				ret = intToValue(int64(left) + int64(right))
			case *valueBigInt:
				panic(errMixBigIntType)
			default:
				ret = floatToValue(float64(left) + right.ToFloat())
			}
		case *valueBigInt:
			if right, ok := right.(*valueBigInt); ok {
				ret = (*valueBigInt)(new(big.Int).Add((*big.Int)(left), (*big.Int)(right)))
			} else {
				panic(errMixBigIntType)
			}
		default:
			if _, ok := right.(*valueBigInt); ok {
				panic(errMixBigIntType)
			}
			ret = floatToValue(left.ToFloat() + right.ToFloat())
		}
	}

	vm.stack[vm.sp-2] = ret
	vm.sp--
	vm.pc++
}

type _sub struct{}

var sub _sub

func (_sub) exec(vm *vm) {
	right := vm.stack[vm.sp-1]
	left := vm.stack[vm.sp-2]

	left = toNumeric(left)
	right = toNumeric(right)

	var result Value

	switch left := left.(type) {
	case valueInt:
		switch right := right.(type) {
		case valueInt:
			result = intToValue(int64(left) - int64(right))
			goto end
		case *valueBigInt:
			panic(errMixBigIntType)
		}
	case valueFloat:
		if _, ok := right.(*valueBigInt); ok {
			panic(errMixBigIntType)
		}
	case *valueBigInt:
		if right, ok := right.(*valueBigInt); ok {
			result = (*valueBigInt)(new(big.Int).Sub((*big.Int)(left), (*big.Int)(right)))
			goto end
		}
		panic(errMixBigIntType)
	}

	result = floatToValue(left.ToFloat() - right.ToFloat())
end:
	vm.sp--
	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _mul struct{}

var mul _mul

func (_mul) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])

	var result Value

	switch left := left.(type) {
	case valueInt:
		switch right := right.(type) {
		case valueInt:
			if left == 0 && right == -1 || left == -1 && right == 0 {
				result = _negativeZero
				goto end
			}
			res := left * right
			// check for overflow
			if left == 0 || right == 0 || res/left == right {
				result = intToValue(int64(res))
				goto end
			}
		case *valueBigInt:
			panic(errMixBigIntType)
		}
	case valueFloat:
		if _, ok := right.(*valueBigInt); ok {
			panic(errMixBigIntType)
		}
	case *valueBigInt:
		if right, ok := right.(*valueBigInt); ok {
			result = (*valueBigInt)(new(big.Int).Mul((*big.Int)(left), (*big.Int)(right)))
			goto end
		}
		panic(errMixBigIntType)
	}

	result = floatToValue(left.ToFloat() * right.ToFloat())

end:
	vm.sp--
	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _exp struct{}

var exp _exp

func (_exp) exec(vm *vm) {
	vm.sp--
	x := vm.stack[vm.sp-1]
	y := vm.stack[vm.sp]

	x = toNumeric(x)
	y = toNumeric(y)

	var result Value
	if x, ok := x.(*valueBigInt); ok {
		if y, ok := y.(*valueBigInt); ok {
			if (*big.Int)(y).Cmp(big.NewInt(0)) < 0 {
				panic(vm.r.newError(vm.r.getRangeError(), "exponent must be positive"))
			}
			result = (*valueBigInt)(new(big.Int).Exp((*big.Int)(x), (*big.Int)(y), nil))
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := y.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = pow(x, y)
end:
	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _div struct{}

var div _div

func (_div) exec(vm *vm) {
	leftValue := toNumeric(vm.stack[vm.sp-2])
	rightValue := toNumeric(vm.stack[vm.sp-1])

	var (
		result      Value
		left, right float64
	)

	if left, ok := leftValue.(*valueBigInt); ok {
		if right, ok := rightValue.(*valueBigInt); ok {
			if (*big.Int)(right).Cmp(big.NewInt(0)) == 0 {
				panic(vm.r.newError(vm.r.getRangeError(), "Division by zero"))
			}
			if (*big.Int)(left).CmpAbs((*big.Int)(right)) < 0 {
				result = (*valueBigInt)(big.NewInt(0))
			} else {
				i, _ := new(big.Int).QuoRem((*big.Int)(left), (*big.Int)(right), big.NewInt(0))
				result = (*valueBigInt)(i)
			}
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := rightValue.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}
	left, right = leftValue.ToFloat(), rightValue.ToFloat()

	if math.IsNaN(left) || math.IsNaN(right) {
		result = _NaN
		goto end
	}
	if math.IsInf(left, 0) && math.IsInf(right, 0) {
		result = _NaN
		goto end
	}
	if left == 0 && right == 0 {
		result = _NaN
		goto end
	}

	if math.IsInf(left, 0) {
		if math.Signbit(left) == math.Signbit(right) {
			result = _positiveInf
			goto end
		} else {
			result = _negativeInf
			goto end
		}
	}
	if math.IsInf(right, 0) {
		if math.Signbit(left) == math.Signbit(right) {
			result = _positiveZero
			goto end
		} else {
			result = _negativeZero
			goto end
		}
	}
	if right == 0 {
		if math.Signbit(left) == math.Signbit(right) {
			result = _positiveInf
			goto end
		} else {
			result = _negativeInf
			goto end
		}
	}

	result = floatToValue(left / right)

end:
	vm.sp--
	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _mod struct{}

var mod _mod

func (_mod) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])

	var result Value

	switch left := left.(type) {
	case valueInt:
		switch right := right.(type) {
		case valueInt:
			if right == 0 {
				result = _NaN
				goto end
			}
			r := left % right
			if r == 0 && left < 0 {
				result = _negativeZero
			} else {
				result = intToValue(int64(left % right))
			}
			goto end
		case *valueBigInt:
			panic(errMixBigIntType)
		}
	case valueFloat:
		if _, ok := right.(*valueBigInt); ok {
			panic(errMixBigIntType)
		}
	case *valueBigInt:
		if right, ok := right.(*valueBigInt); ok {
			switch {
			case (*big.Int)(right).Cmp(big.NewInt(0)) == 0:
				panic(vm.r.newError(vm.r.getRangeError(), "Division by zero"))
			case (*big.Int)(left).Cmp(big.NewInt(0)) < 0:
				abs := new(big.Int).Abs((*big.Int)(left))
				v := new(big.Int).Mod(abs, (*big.Int)(right))
				result = (*valueBigInt)(v.Neg(v))
			default:
				result = (*valueBigInt)(new(big.Int).Mod((*big.Int)(left), (*big.Int)(right)))
			}
			goto end
		}
		panic(errMixBigIntType)
	}

	result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat()))
end:
	vm.sp--
	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _neg struct{}

var neg _neg

func (_neg) exec(vm *vm) {
	operand := vm.stack[vm.sp-1]

	var result Value

	switch n := toNumeric(operand).(type) {
	case *valueBigInt:
		result = (*valueBigInt)(new(big.Int).Neg((*big.Int)(n)))
	case valueInt:
		if n == 0 {
			result = _negativeZero
		} else {
			result = -n
		}
	default:
		f := operand.ToFloat()
		if !math.IsNaN(f) {
			f = -f
		}
		result = valueFloat(f)
	}

	vm.stack[vm.sp-1] = result
	vm.pc++
}

type _plus struct{}

var plus _plus

func (_plus) exec(vm *vm) {
	vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber()
	vm.pc++
}

type _inc struct{}

var inc _inc

func (_inc) exec(vm *vm) {
	v := vm.stack[vm.sp-1]

	switch n := v.(type) {
	case *valueBigInt:
		v = (*valueBigInt)(new(big.Int).Add((*big.Int)(n), big.NewInt(1)))
	case valueInt:
		v = intToValue(int64(n + 1))
	default:
		v = valueFloat(n.ToFloat() + 1)
	}

	vm.stack[vm.sp-1] = v
	vm.pc++
}

type _dec struct{}

var dec _dec

func (_dec) exec(vm *vm) {
	v := vm.stack[vm.sp-1]

	switch n := v.(type) {
	case *valueBigInt:
		v = (*valueBigInt)(new(big.Int).Sub((*big.Int)(n), big.NewInt(1)))
	case valueInt:
		v = intToValue(int64(n - 1))
	default:
		v = valueFloat(n.ToFloat() - 1)
	}

	vm.stack[vm.sp-1] = v
	vm.pc++
}

type _and struct{}

var and _and

func (_and) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])
	var result Value

	if left, ok := left.(*valueBigInt); ok {
		if right, ok := right.(*valueBigInt); ok {
			result = (*valueBigInt)(new(big.Int).And((*big.Int)(left), (*big.Int)(right)))
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := right.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = intToValue(int64(toInt32(left) & toInt32(right)))
end:
	vm.stack[vm.sp-2] = result
	vm.sp--
	vm.pc++
}

type _or struct{}

var or _or

func (_or) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])
	var result Value

	if left, ok := left.(*valueBigInt); ok {
		if right, ok := right.(*valueBigInt); ok {
			result = (*valueBigInt)(new(big.Int).Or((*big.Int)(left), (*big.Int)(right)))
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := right.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = intToValue(int64(toInt32(left) | toInt32(right)))
end:
	vm.stack[vm.sp-2] = result
	vm.sp--
	vm.pc++
}

type _xor struct{}

var xor _xor

func (_xor) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])
	var result Value

	if left, ok := left.(*valueBigInt); ok {
		if right, ok := right.(*valueBigInt); ok {
			result = (*valueBigInt)(new(big.Int).Xor((*big.Int)(left), (*big.Int)(right)))
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := right.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = intToValue(int64(toInt32(left) ^ toInt32(right)))
end:
	vm.stack[vm.sp-2] = result
	vm.sp--
	vm.pc++
}

type _bnot struct{}

var bnot _bnot

func (_bnot) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	switch n := toNumeric(v).(type) {
	case *valueBigInt:
		v = (*valueBigInt)(new(big.Int).Not((*big.Int)(n)))
	default:
		v = intToValue(int64(^toInt32(n)))
	}
	vm.stack[vm.sp-1] = v
	vm.pc++
}

type _sal struct{}

var sal _sal

func (_sal) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])
	var result Value

	if left, ok := left.(*valueBigInt); ok {
		if right, ok := right.(*valueBigInt); ok {
			n := uint((*big.Int)(right).Uint64())
			if (*big.Int)(right).Sign() < 0 {
				result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n))
			} else {
				result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n))
			}
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := right.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = intToValue(int64(toInt32(left) << (toUint32(right) & 0x1F)))
end:
	vm.stack[vm.sp-2] = result
	vm.sp--
	vm.pc++
}

type _sar struct{}

var sar _sar

func (_sar) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])
	var result Value

	if left, ok := left.(*valueBigInt); ok {
		if right, ok := right.(*valueBigInt); ok {
			n := uint((*big.Int)(right).Uint64())
			if (*big.Int)(right).Sign() < 0 {
				result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n))
			} else {
				result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n))
			}
			goto end
		}
		panic(errMixBigIntType)
	} else if _, ok := right.(*valueBigInt); ok {
		panic(errMixBigIntType)
	}

	result = intToValue(int64(toInt32(left) >> (toUint32(right) & 0x1F)))
end:
	vm.stack[vm.sp-2] = result
	vm.sp--
	vm.pc++
}

type _shr struct{}

var shr _shr

func (_shr) exec(vm *vm) {
	left := toNumeric(vm.stack[vm.sp-2])
	right := toNumeric(vm.stack[vm.sp-1])

	if _, ok := left.(*valueBigInt); ok {
		_ = toNumeric(right)
		panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead"))
	} else if _, ok := right.(*valueBigInt); ok {
		panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead"))
	}

	vm.stack[vm.sp-2] = intToValue(int64(toUint32(left) >> (toUint32(right) & 0x1F)))
	vm.sp--
	vm.pc++
}

type jump int32

func (j jump) exec(vm *vm) {
	vm.pc += int(j)
}

type _toPropertyKey struct{}

func (_toPropertyKey) exec(vm *vm) {
	p := vm.sp - 1
	vm.stack[p] = toPropertyKey(vm.stack[p])
	vm.pc++
}

type _toString struct{}

func (_toString) exec(vm *vm) {
	p := vm.sp - 1
	vm.stack[p] = vm.stack[p].toString()
	vm.pc++
}

type _getElemRef struct{}

var getElemRef _getElemRef

func (_getElemRef) exec(vm *vm) {
	obj := vm.stack[vm.sp-2].ToObject(vm.r)
	propName := vm.stack[vm.sp-1]
	vm.refStack = append(vm.refStack, &objRef{
		base: obj,
		name: propName,
	})
	vm.sp -= 2
	vm.pc++
}

type _getElemRefRecv struct{}

var getElemRefRecv _getElemRefRecv

func (_getElemRefRecv) exec(vm *vm) {
	obj := vm.stack[vm.sp-1].ToObject(vm.r)
	propName := vm.stack[vm.sp-2]
	vm.refStack = append(vm.refStack, &objRef{
		base: obj,
		name: propName,
		this: vm.stack[vm.sp-3],
	})
	vm.sp -= 3
	vm.pc++
}

type _getElemRefStrict struct{}

var getElemRefStrict _getElemRefStrict

func (_getElemRefStrict) exec(vm *vm) {
	obj := vm.stack[vm.sp-2].ToObject(vm.r)
	propName := vm.stack[vm.sp-1]
	vm.refStack = append(vm.refStack, &objRef{
		base:   obj,
		name:   propName,
		strict: true,
	})
	vm.sp -= 2
	vm.pc++
}

type _getElemRefRecvStrict struct{}

var getElemRefRecvStrict _getElemRefRecvStrict

func (_getElemRefRecvStrict) exec(vm *vm) {
	obj := vm.stack[vm.sp-1].ToObject(vm.r)
	propName := vm.stack[vm.sp-2]
	vm.refStack = append(vm.refStack, &objRef{
		base:   obj,
		name:   propName,
		this:   vm.stack[vm.sp-3],
		strict: true,
	})
	vm.sp -= 3
	vm.pc++
}

type _setElem struct{}

var setElem _setElem

func (_setElem) exec(vm *vm) {
	obj := vm.stack[vm.sp-3].ToObject(vm.r)
	propName := toPropertyKey(vm.stack[vm.sp-2])
	val := vm.stack[vm.sp-1]

	obj.setOwn(propName, val, false)

	vm.sp -= 2
	vm.stack[vm.sp-1] = val
	vm.pc++
}

type _setElem1 struct{}

var setElem1 _setElem1

func (_setElem1) exec(vm *vm) {
	obj := vm.stack[vm.sp-3].ToObject(vm.r)
	propName := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]

	obj.setOwn(propName, val, true)

	vm.sp -= 2
	vm.pc++
}

type _setElem1Named struct{}

var setElem1Named _setElem1Named

func (_setElem1Named) exec(vm *vm) {
	receiver := vm.stack[vm.sp-3]
	base := receiver.ToObject(vm.r)
	propName := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        funcName("", propName),
		Configurable: FLAG_TRUE,
	}, true)
	base.set(propName, val, receiver, true)

	vm.sp -= 2
	vm.pc++
}

type defineMethod struct {
	enumerable bool
}

func (d *defineMethod) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-3])
	propName := vm.stack[vm.sp-2]
	method := vm.r.toObject(vm.stack[vm.sp-1])
	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        funcName("", propName),
		Configurable: FLAG_TRUE,
	}, true)
	obj.defineOwnProperty(propName, PropertyDescriptor{
		Value:        method,
		Writable:     FLAG_TRUE,
		Configurable: FLAG_TRUE,
		Enumerable:   ToFlag(d.enumerable),
	}, true)

	vm.sp -= 2
	vm.pc++
}

type _setElemP struct{}

var setElemP _setElemP

func (_setElemP) exec(vm *vm) {
	obj := vm.stack[vm.sp-3].ToObject(vm.r)
	propName := toPropertyKey(vm.stack[vm.sp-2])
	val := vm.stack[vm.sp-1]

	obj.setOwn(propName, val, false)

	vm.sp -= 3
	vm.pc++
}

type _setElemStrict struct{}

var setElemStrict _setElemStrict

func (_setElemStrict) exec(vm *vm) {
	propName := toPropertyKey(vm.stack[vm.sp-2])
	receiver := vm.stack[vm.sp-3]
	val := vm.stack[vm.sp-1]
	if receiverObj, ok := receiver.(*Object); ok {
		receiverObj.setOwn(propName, val, true)
	} else {
		base := receiver.ToObject(vm.r)
		base.set(propName, val, receiver, true)
	}

	vm.sp -= 2
	vm.stack[vm.sp-1] = val
	vm.pc++
}

type _setElemRecv struct{}

var setElemRecv _setElemRecv

func (_setElemRecv) exec(vm *vm) {
	receiver := vm.stack[vm.sp-4]
	propName := toPropertyKey(vm.stack[vm.sp-3])
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	if obj, ok := o.(*Object); ok {
		obj.set(propName, val, receiver, false)
	} else {
		base := o.ToObject(vm.r)
		base.set(propName, val, receiver, false)
	}

	vm.sp -= 3
	vm.stack[vm.sp-1] = val
	vm.pc++
}

type _setElemRecvStrict struct{}

var setElemRecvStrict _setElemRecvStrict

func (_setElemRecvStrict) exec(vm *vm) {
	receiver := vm.stack[vm.sp-4]
	propName := toPropertyKey(vm.stack[vm.sp-3])
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	if obj, ok := o.(*Object); ok {
		obj.set(propName, val, receiver, true)
	} else {
		base := o.ToObject(vm.r)
		base.set(propName, val, receiver, true)
	}

	vm.sp -= 3
	vm.stack[vm.sp-1] = val
	vm.pc++
}

type _setElemStrictP struct{}

var setElemStrictP _setElemStrictP

func (_setElemStrictP) exec(vm *vm) {
	propName := toPropertyKey(vm.stack[vm.sp-2])
	receiver := vm.stack[vm.sp-3]
	val := vm.stack[vm.sp-1]
	if receiverObj, ok := receiver.(*Object); ok {
		receiverObj.setOwn(propName, val, true)
	} else {
		base := receiver.ToObject(vm.r)
		base.set(propName, val, receiver, true)
	}

	vm.sp -= 3
	vm.pc++
}

type _setElemRecvP struct{}

var setElemRecvP _setElemRecvP

func (_setElemRecvP) exec(vm *vm) {
	receiver := vm.stack[vm.sp-4]
	propName := toPropertyKey(vm.stack[vm.sp-3])
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	if obj, ok := o.(*Object); ok {
		obj.set(propName, val, receiver, false)
	} else {
		base := o.ToObject(vm.r)
		base.set(propName, val, receiver, false)
	}

	vm.sp -= 4
	vm.pc++
}

type _setElemRecvStrictP struct{}

var setElemRecvStrictP _setElemRecvStrictP

func (_setElemRecvStrictP) exec(vm *vm) {
	receiver := vm.stack[vm.sp-4]
	propName := toPropertyKey(vm.stack[vm.sp-3])
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	if obj, ok := o.(*Object); ok {
		obj.set(propName, val, receiver, true)
	} else {
		base := o.ToObject(vm.r)
		base.set(propName, val, receiver, true)
	}

	vm.sp -= 4
	vm.pc++
}

type _deleteElem struct{}

var deleteElem _deleteElem

func (_deleteElem) exec(vm *vm) {
	obj := vm.stack[vm.sp-2].ToObject(vm.r)
	propName := toPropertyKey(vm.stack[vm.sp-1])
	if obj.delete(propName, false) {
		vm.stack[vm.sp-2] = valueTrue
	} else {
		vm.stack[vm.sp-2] = valueFalse
	}
	vm.sp--
	vm.pc++
}

type _deleteElemStrict struct{}

var deleteElemStrict _deleteElemStrict

func (_deleteElemStrict) exec(vm *vm) {
	obj := vm.stack[vm.sp-2].ToObject(vm.r)
	propName := toPropertyKey(vm.stack[vm.sp-1])
	obj.delete(propName, true)
	vm.stack[vm.sp-2] = valueTrue
	vm.sp--
	vm.pc++
}

type deleteProp unistring.String

func (d deleteProp) exec(vm *vm) {
	obj := vm.stack[vm.sp-1].ToObject(vm.r)
	if obj.self.deleteStr(unistring.String(d), false) {
		vm.stack[vm.sp-1] = valueTrue
	} else {
		vm.stack[vm.sp-1] = valueFalse
	}
	vm.pc++
}

type deletePropStrict unistring.String

func (d deletePropStrict) exec(vm *vm) {
	obj := vm.stack[vm.sp-1].ToObject(vm.r)
	obj.self.deleteStr(unistring.String(d), true)
	vm.stack[vm.sp-1] = valueTrue
	vm.pc++
}

type getPropRef unistring.String

func (p getPropRef) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &objStrRef{
		base: vm.stack[vm.sp-1].ToObject(vm.r),
		name: unistring.String(p),
	})
	vm.sp--
	vm.pc++
}

type getPropRefRecv unistring.String

func (p getPropRefRecv) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &objStrRef{
		this: vm.stack[vm.sp-2],
		base: vm.stack[vm.sp-1].ToObject(vm.r),
		name: unistring.String(p),
	})
	vm.sp -= 2
	vm.pc++
}

type getPropRefStrict unistring.String

func (p getPropRefStrict) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &objStrRef{
		base:   vm.stack[vm.sp-1].ToObject(vm.r),
		name:   unistring.String(p),
		strict: true,
	})
	vm.sp--
	vm.pc++
}

type getPropRefRecvStrict unistring.String

func (p getPropRefRecvStrict) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &objStrRef{
		this:   vm.stack[vm.sp-2],
		base:   vm.stack[vm.sp-1].ToObject(vm.r),
		name:   unistring.String(p),
		strict: true,
	})
	vm.sp -= 2
	vm.pc++
}

type setProp unistring.String

func (p setProp) exec(vm *vm) {
	val := vm.stack[vm.sp-1]
	vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false)
	vm.stack[vm.sp-2] = val
	vm.sp--
	vm.pc++
}

type setPropP unistring.String

func (p setPropP) exec(vm *vm) {
	val := vm.stack[vm.sp-1]
	vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false)
	vm.sp -= 2
	vm.pc++
}

type setPropStrict unistring.String

func (p setPropStrict) exec(vm *vm) {
	receiver := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if receiverObj, ok := receiver.(*Object); ok {
		receiverObj.self.setOwnStr(propName, val, true)
	} else {
		base := receiver.ToObject(vm.r)
		base.setStr(propName, val, receiver, true)
	}

	vm.stack[vm.sp-2] = val
	vm.sp--
	vm.pc++
}

type setPropRecv unistring.String

func (p setPropRecv) exec(vm *vm) {
	receiver := vm.stack[vm.sp-3]
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if obj, ok := o.(*Object); ok {
		obj.setStr(propName, val, receiver, false)
	} else {
		base := o.ToObject(vm.r)
		base.setStr(propName, val, receiver, false)
	}

	vm.stack[vm.sp-3] = val
	vm.sp -= 2
	vm.pc++
}

type setPropRecvStrict unistring.String

func (p setPropRecvStrict) exec(vm *vm) {
	receiver := vm.stack[vm.sp-3]
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if obj, ok := o.(*Object); ok {
		obj.setStr(propName, val, receiver, true)
	} else {
		base := o.ToObject(vm.r)
		base.setStr(propName, val, receiver, true)
	}

	vm.stack[vm.sp-3] = val
	vm.sp -= 2
	vm.pc++
}

type setPropRecvP unistring.String

func (p setPropRecvP) exec(vm *vm) {
	receiver := vm.stack[vm.sp-3]
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if obj, ok := o.(*Object); ok {
		obj.setStr(propName, val, receiver, false)
	} else {
		base := o.ToObject(vm.r)
		base.setStr(propName, val, receiver, false)
	}

	vm.sp -= 3
	vm.pc++
}

type setPropRecvStrictP unistring.String

func (p setPropRecvStrictP) exec(vm *vm) {
	receiver := vm.stack[vm.sp-3]
	o := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if obj, ok := o.(*Object); ok {
		obj.setStr(propName, val, receiver, true)
	} else {
		base := o.ToObject(vm.r)
		base.setStr(propName, val, receiver, true)
	}

	vm.sp -= 3
	vm.pc++
}

type setPropStrictP unistring.String

func (p setPropStrictP) exec(vm *vm) {
	receiver := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	propName := unistring.String(p)
	if receiverObj, ok := receiver.(*Object); ok {
		receiverObj.self.setOwnStr(propName, val, true)
	} else {
		base := receiver.ToObject(vm.r)
		base.setStr(propName, val, receiver, true)
	}

	vm.sp -= 2
	vm.pc++
}

type putProp unistring.String

func (p putProp) exec(vm *vm) {
	vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(unistring.String(p), vm.stack[vm.sp-1], true, true, true)

	vm.sp--
	vm.pc++
}

// used in class declarations instead of putProp because DefineProperty must be observable by Proxy
type definePropKeyed unistring.String

func (p definePropKeyed) exec(vm *vm) {
	vm.r.toObject(vm.stack[vm.sp-2]).self.defineOwnPropertyStr(unistring.String(p), PropertyDescriptor{
		Value:        vm.stack[vm.sp-1],
		Writable:     FLAG_TRUE,
		Configurable: FLAG_TRUE,
		Enumerable:   FLAG_TRUE,
	}, true)

	vm.sp--
	vm.pc++
}

type defineProp struct{}

func (defineProp) exec(vm *vm) {
	vm.r.toObject(vm.stack[vm.sp-3]).defineOwnProperty(vm.stack[vm.sp-2], PropertyDescriptor{
		Value:        vm.stack[vm.sp-1],
		Writable:     FLAG_TRUE,
		Configurable: FLAG_TRUE,
		Enumerable:   FLAG_TRUE,
	}, true)

	vm.sp -= 2
	vm.pc++
}

type defineMethodKeyed struct {
	key        unistring.String
	enumerable bool
}

func (d *defineMethodKeyed) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-2])
	method := vm.r.toObject(vm.stack[vm.sp-1])

	obj.self.defineOwnPropertyStr(d.key, PropertyDescriptor{
		Value:        method,
		Writable:     FLAG_TRUE,
		Configurable: FLAG_TRUE,
		Enumerable:   ToFlag(d.enumerable),
	}, true)

	vm.sp--
	vm.pc++
}

type _setProto struct{}

var setProto _setProto

func (_setProto) exec(vm *vm) {
	vm.r.setObjectProto(vm.stack[vm.sp-2], vm.stack[vm.sp-1])

	vm.sp--
	vm.pc++
}

type defineGetterKeyed struct {
	key        unistring.String
	enumerable bool
}

func (s *defineGetterKeyed) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-2])
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)
	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        asciiString("get ").Concat(stringValueFromRaw(s.key)),
		Configurable: FLAG_TRUE,
	}, true)
	descr := PropertyDescriptor{
		Getter:       val,
		Configurable: FLAG_TRUE,
		Enumerable:   ToFlag(s.enumerable),
	}

	obj.self.defineOwnPropertyStr(s.key, descr, true)

	vm.sp--
	vm.pc++
}

type defineSetterKeyed struct {
	key        unistring.String
	enumerable bool
}

func (s *defineSetterKeyed) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-2])
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)
	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        asciiString("set ").Concat(stringValueFromRaw(s.key)),
		Configurable: FLAG_TRUE,
	}, true)

	descr := PropertyDescriptor{
		Setter:       val,
		Configurable: FLAG_TRUE,
		Enumerable:   ToFlag(s.enumerable),
	}

	obj.self.defineOwnPropertyStr(s.key, descr, true)

	vm.sp--
	vm.pc++
}

type defineGetter struct {
	enumerable bool
}

func (s *defineGetter) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-3])
	propName := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)
	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        funcName("get ", propName),
		Configurable: FLAG_TRUE,
	}, true)

	descr := PropertyDescriptor{
		Getter:       val,
		Configurable: FLAG_TRUE,
		Enumerable:   ToFlag(s.enumerable),
	}

	obj.defineOwnProperty(propName, descr, true)

	vm.sp -= 2
	vm.pc++
}

type defineSetter struct {
	enumerable bool
}

func (s *defineSetter) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-3])
	propName := vm.stack[vm.sp-2]
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)

	method.self.defineOwnPropertyStr("name", PropertyDescriptor{
		Value:        funcName("set ", propName),
		Configurable: FLAG_TRUE,
	}, true)

	descr := PropertyDescriptor{
		Setter:       val,
		Configurable: FLAG_TRUE,
		Enumerable:   FLAG_TRUE,
	}

	obj.defineOwnProperty(propName, descr, true)

	vm.sp -= 2
	vm.pc++
}

type getProp unistring.String

func (g getProp) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
		return
	}
	vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(unistring.String(g), v))

	vm.pc++
}

type getPropRecv unistring.String

func (g getPropRecv) exec(vm *vm) {
	recv := vm.stack[vm.sp-2]
	v := vm.stack[vm.sp-1]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
		return
	}
	vm.stack[vm.sp-2] = nilSafe(obj.self.getStr(unistring.String(g), recv))
	vm.sp--
	vm.pc++
}

type getPropRecvCallee unistring.String

func (g getPropRecvCallee) exec(vm *vm) {
	recv := vm.stack[vm.sp-2]
	v := vm.stack[vm.sp-1]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g))
		return
	}

	n := unistring.String(g)
	prop := obj.self.getStr(n, recv)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}}
	}

	vm.stack[vm.sp-1] = prop
	vm.pc++
}

type getPropCallee unistring.String

func (g getPropCallee) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	obj := v.baseObject(vm.r)
	n := unistring.String(g)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", n))
		return
	}
	prop := obj.self.getStr(n, v)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}}
	}
	vm.push(prop)

	vm.pc++
}

type _getElem struct{}

var getElem _getElem

func (_getElem) exec(vm *vm) {
	v := vm.stack[vm.sp-2]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1]))
		return
	}
	propName := toPropertyKey(vm.stack[vm.sp-1])

	vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v))

	vm.sp--
	vm.pc++
}

type _getElemRecv struct{}

var getElemRecv _getElemRecv

func (_getElemRecv) exec(vm *vm) {
	recv := vm.stack[vm.sp-3]
	v := vm.stack[vm.sp-1]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-2]))
		return
	}
	propName := toPropertyKey(vm.stack[vm.sp-2])

	vm.stack[vm.sp-3] = nilSafe(obj.get(propName, recv))

	vm.sp -= 2
	vm.pc++
}

type _getKey struct{}

var getKey _getKey

func (_getKey) exec(vm *vm) {
	v := vm.stack[vm.sp-2]
	obj := v.baseObject(vm.r)
	propName := vm.stack[vm.sp-1]
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String()))
		return
	}

	vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v))

	vm.sp--
	vm.pc++
}

type _getElemCallee struct{}

var getElemCallee _getElemCallee

func (_getElemCallee) exec(vm *vm) {
	v := vm.stack[vm.sp-2]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1]))
		return
	}

	propName := toPropertyKey(vm.stack[vm.sp-1])
	prop := obj.get(propName, v)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}}
	}
	vm.stack[vm.sp-1] = prop

	vm.pc++
}

type _getElemRecvCallee struct{}

var getElemRecvCallee _getElemRecvCallee

func (_getElemRecvCallee) exec(vm *vm) {
	recv := vm.stack[vm.sp-3]
	v := vm.stack[vm.sp-2]
	obj := v.baseObject(vm.r)
	if obj == nil {
		vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1]))
		return
	}

	propName := toPropertyKey(vm.stack[vm.sp-1])
	prop := obj.get(propName, recv)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}}
	}
	vm.stack[vm.sp-2] = prop
	vm.sp--

	vm.pc++
}

type _dup struct{}

var dup _dup

func (_dup) exec(vm *vm) {
	vm.push(vm.stack[vm.sp-1])
	vm.pc++
}

type dupN uint32

func (d dupN) exec(vm *vm) {
	vm.push(vm.stack[vm.sp-1-int(d)])
	vm.pc++
}

type rdupN uint32

func (d rdupN) exec(vm *vm) {
	vm.stack[vm.sp-1-int(d)] = vm.stack[vm.sp-1]
	vm.pc++
}

type dupLast uint32

func (d dupLast) exec(vm *vm) {
	e := vm.sp + int(d)
	vm.stack.expand(e)
	copy(vm.stack[vm.sp:e], vm.stack[vm.sp-int(d):])
	vm.sp = e
	vm.pc++
}

type _newObject struct{}

var newObject _newObject

func (_newObject) exec(vm *vm) {
	vm.push(vm.r.NewObject())
	vm.pc++
}

type newArray uint32

func (l newArray) exec(vm *vm) {
	values := make([]Value, 0, l)
	vm.push(vm.r.newArrayValues(values))
	vm.pc++
}

type _pushArrayItem struct{}

var pushArrayItem _pushArrayItem

func (_pushArrayItem) exec(vm *vm) {
	arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject)
	if arr.length < math.MaxUint32 {
		arr.length++
	} else {
		vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length"))
		return
	}
	val := vm.stack[vm.sp-1]
	arr.values = append(arr.values, val)
	if val != nil {
		arr.objCount++
	}
	vm.sp--
	vm.pc++
}

type _pushArraySpread struct{}

var pushArraySpread _pushArraySpread

func (_pushArraySpread) exec(vm *vm) {
	arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject)
	vm.r.getIterator(vm.stack[vm.sp-1], nil).iterate(func(val Value) {
		if arr.length < math.MaxUint32 {
			arr.length++
		} else {
			vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length"))
			return
		}
		arr.values = append(arr.values, val)
		arr.objCount++
	})
	vm.sp--
	vm.pc++
}

type _pushSpread struct{}

var pushSpread _pushSpread

func (_pushSpread) exec(vm *vm) {
	vm.sp--
	obj := vm.stack[vm.sp]
	vm.r.getIterator(obj, nil).iterate(func(val Value) {
		vm.push(val)
	})
	vm.pc++
}

type _newArrayFromIter struct{}

var newArrayFromIter _newArrayFromIter

func (_newArrayFromIter) exec(vm *vm) {
	var values []Value
	l := len(vm.iterStack) - 1
	iter := vm.iterStack[l].iter
	vm.iterStack[l] = iterStackItem{}
	vm.iterStack = vm.iterStack[:l]
	if iter.iterator != nil {
		iter.iterate(func(val Value) {
			values = append(values, val)
		})
	}
	vm.push(vm.r.newArrayValues(values))
	vm.pc++
}

type newRegexp struct {
	pattern *regexpPattern
	src     String
}

func (n *newRegexp) exec(vm *vm) {
	vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.getRegExpPrototype()).val)
	vm.pc++
}

func (vm *vm) setLocalLex(s int) {
	v := vm.stack[vm.sp-1]
	level := s >> 24
	idx := uint32(s & 0x00FFFFFF)
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}
	p := &stash.values[idx]
	if *p == nil {
		panic(errAccessBeforeInit)
	}
	*p = v
	vm.pc++
}

func (vm *vm) initLocal(s int) {
	v := vm.stack[vm.sp-1]
	level := s >> 24
	idx := uint32(s & 0x00FFFFFF)
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}
	stash.initByIdx(idx, v)
	vm.pc++
}

type storeStash uint32

func (s storeStash) exec(vm *vm) {
	vm.initLocal(int(s))
}

type storeStashP uint32

func (s storeStashP) exec(vm *vm) {
	vm.initLocal(int(s))
	vm.sp--
}

type storeStashLex uint32

func (s storeStashLex) exec(vm *vm) {
	vm.setLocalLex(int(s))
}

type storeStashLexP uint32

func (s storeStashLexP) exec(vm *vm) {
	vm.setLocalLex(int(s))
	vm.sp--
}

type initStash uint32

func (s initStash) exec(vm *vm) {
	vm.initLocal(int(s))
}

type initStashP uint32

func (s initStashP) exec(vm *vm) {
	vm.initLocal(int(s))
	vm.sp--
}

type initGlobalP unistring.String

func (s initGlobalP) exec(vm *vm) {
	vm.sp--
	vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp])
	vm.pc++
}

type initGlobal unistring.String

func (s initGlobal) exec(vm *vm) {
	vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp])
	vm.pc++
}

type resolveVar1 unistring.String

func (s resolveVar1) exec(vm *vm) {
	name := unistring.String(s)
	var ref ref
	for stash := vm.stash; stash != nil; stash = stash.outer {
		ref = stash.getRefByName(name, false)
		if ref != nil {
			goto end
		}
	}

	ref = &objStrRef{
		base:    vm.r.globalObject,
		name:    name,
		binding: true,
	}

end:
	vm.refStack = append(vm.refStack, ref)
	vm.pc++
}

type deleteVar unistring.String

func (d deleteVar) exec(vm *vm) {
	name := unistring.String(d)
	ret := true
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if stash.obj != nil {
			if stashObjHas(stash.obj, name) {
				ret = stash.obj.self.deleteStr(name, false)
				goto end
			}
		} else {
			if idx, exists := stash.names[name]; exists {
				if idx&(maskVar|maskDeletable) == maskVar|maskDeletable {
					stash.deleteBinding(name)
				} else {
					ret = false
				}
				goto end
			}
		}
	}

	if vm.r.globalObject.self.hasPropertyStr(name) {
		ret = vm.r.globalObject.self.deleteStr(name, false)
	}

end:
	if ret {
		vm.push(valueTrue)
	} else {
		vm.push(valueFalse)
	}
	vm.pc++
}

type deleteGlobal unistring.String

func (d deleteGlobal) exec(vm *vm) {
	name := unistring.String(d)
	var ret bool
	if vm.r.globalObject.self.hasPropertyStr(name) {
		ret = vm.r.globalObject.self.deleteStr(name, false)
	} else {
		ret = true
	}
	if ret {
		vm.push(valueTrue)
	} else {
		vm.push(valueFalse)
	}
	vm.pc++
}

type resolveVar1Strict unistring.String

func (s resolveVar1Strict) exec(vm *vm) {
	name := unistring.String(s)
	var ref ref
	for stash := vm.stash; stash != nil; stash = stash.outer {
		ref = stash.getRefByName(name, true)
		if ref != nil {
			goto end
		}
	}

	if vm.r.globalObject.self.hasPropertyStr(name) {
		ref = &objStrRef{
			base:    vm.r.globalObject,
			name:    name,
			binding: true,
			strict:  true,
		}
		goto end
	}

	ref = &unresolvedRef{
		runtime: vm.r,
		name:    name,
	}

end:
	vm.refStack = append(vm.refStack, ref)
	vm.pc++
}

type setGlobal unistring.String

func (s setGlobal) exec(vm *vm) {
	vm.r.setGlobal(unistring.String(s), vm.peek(), false)
	vm.pc++
}

type setGlobalStrict unistring.String

func (s setGlobalStrict) exec(vm *vm) {
	vm.r.setGlobal(unistring.String(s), vm.peek(), true)
	vm.pc++
}

// Load a var from stash
type loadStash uint32

func (g loadStash) exec(vm *vm) {
	level := int(g >> 24)
	idx := uint32(g & 0x00FFFFFF)
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}

	vm.push(nilSafe(stash.getByIdx(idx)))
	vm.pc++
}

// Load a lexical binding from stash
type loadStashLex uint32

func (g loadStashLex) exec(vm *vm) {
	level := int(g >> 24)
	idx := uint32(g & 0x00FFFFFF)
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}

	v := stash.getByIdx(idx)
	if v == nil {
		vm.throw(errAccessBeforeInit)
		return
	}
	vm.push(v)
	vm.pc++
}

// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found
// return the indexed var binding value from stash
type loadMixed struct {
	name   unistring.String
	idx    uint32
	callee bool
}

func (g *loadMixed) exec(vm *vm) {
	level := int(g.idx >> 24)
	idx := g.idx & 0x00FFFFFF
	stash := vm.stash
	name := g.name
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	if stash != nil {
		vm.push(nilSafe(stash.getByIdx(idx)))
	}
end:
	vm.pc++
}

// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found
// return the indexed lexical binding value from stash
type loadMixedLex loadMixed

func (g *loadMixedLex) exec(vm *vm) {
	level := int(g.idx >> 24)
	idx := g.idx & 0x00FFFFFF
	stash := vm.stash
	name := g.name
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	if stash != nil {
		v := stash.getByIdx(idx)
		if v == nil {
			vm.throw(errAccessBeforeInit)
			return
		}
		vm.push(v)
	}
end:
	vm.pc++
}

// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found
// return the indexed var binding value from stack
type loadMixedStack struct {
	name   unistring.String
	idx    int
	level  uint8
	callee bool
}

// same as loadMixedStack, but the args have been moved to stash (therefore stack layout is different)
type loadMixedStack1 loadMixedStack

func (g *loadMixedStack) exec(vm *vm) {
	stash := vm.stash
	name := g.name
	level := int(g.level)
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	loadStack(g.idx).exec(vm)
	return
end:
	vm.pc++
}

func (g *loadMixedStack1) exec(vm *vm) {
	stash := vm.stash
	name := g.name
	level := int(g.level)
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	loadStack1(g.idx).exec(vm)
	return
end:
	vm.pc++
}

type loadMixedStackLex loadMixedStack

// same as loadMixedStackLex but when the arguments have been moved into stash
type loadMixedStack1Lex loadMixedStack

func (g *loadMixedStackLex) exec(vm *vm) {
	stash := vm.stash
	name := g.name
	level := int(g.level)
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	loadStackLex(g.idx).exec(vm)
	return
end:
	vm.pc++
}

func (g *loadMixedStack1Lex) exec(vm *vm) {
	stash := vm.stash
	name := g.name
	level := int(g.level)
	for i := 0; i < level; i++ {
		if v, found := stash.getByName(name); found {
			if g.callee {
				if stash.obj != nil {
					vm.push(stash.obj)
				} else {
					vm.push(_undefined)
				}
			}
			vm.push(v)
			goto end
		}
		stash = stash.outer
	}
	if g.callee {
		vm.push(_undefined)
	}
	loadStack1Lex(g.idx).exec(vm)
	return
end:
	vm.pc++
}

type resolveMixed struct {
	name   unistring.String
	idx    uint32
	typ    varType
	strict bool
}

func newStashRef(typ varType, name unistring.String, v *[]Value, idx int) ref {
	switch typ {
	case varTypeVar:
		return &stashRef{
			n:   name,
			v:   v,
			idx: idx,
		}
	case varTypeLet:
		return &stashRefLex{
			stashRef: stashRef{
				n:   name,
				v:   v,
				idx: idx,
			},
		}
	case varTypeConst, varTypeStrictConst:
		return &stashRefConst{
			stashRefLex: stashRefLex{
				stashRef: stashRef{
					n:   name,
					v:   v,
					idx: idx,
				},
			},
			strictConst: typ == varTypeStrictConst,
		}
	}
	panic("unsupported var type")
}

func (r *resolveMixed) exec(vm *vm) {
	level := int(r.idx >> 24)
	idx := r.idx & 0x00FFFFFF
	stash := vm.stash
	var ref ref
	for i := 0; i < level; i++ {
		ref = stash.getRefByName(r.name, r.strict)
		if ref != nil {
			goto end
		}
		stash = stash.outer
	}

	if stash != nil {
		ref = newStashRef(r.typ, r.name, &stash.values, int(idx))
		goto end
	}

	ref = &unresolvedRef{
		runtime: vm.r,
		name:    r.name,
	}

end:
	vm.refStack = append(vm.refStack, ref)
	vm.pc++
}

type resolveMixedStack struct {
	name   unistring.String
	idx    int
	typ    varType
	level  uint8
	strict bool
}

type resolveMixedStack1 resolveMixedStack

func (r *resolveMixedStack) exec(vm *vm) {
	level := int(r.level)
	stash := vm.stash
	var ref ref
	var idx int
	for i := 0; i < level; i++ {
		ref = stash.getRefByName(r.name, r.strict)
		if ref != nil {
			goto end
		}
		stash = stash.outer
	}

	if r.idx > 0 {
		idx = vm.sb + vm.args + r.idx
	} else {
		idx = vm.sb - r.idx
	}

	ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), idx)

end:
	vm.refStack = append(vm.refStack, ref)
	vm.pc++
}

func (r *resolveMixedStack1) exec(vm *vm) {
	level := int(r.level)
	stash := vm.stash
	var ref ref
	for i := 0; i < level; i++ {
		ref = stash.getRefByName(r.name, r.strict)
		if ref != nil {
			goto end
		}
		stash = stash.outer
	}

	ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), vm.sb+r.idx)

end:
	vm.refStack = append(vm.refStack, ref)
	vm.pc++
}

type _getValue struct{}

var getValue _getValue

func (_getValue) exec(vm *vm) {
	ref := vm.refStack[len(vm.refStack)-1]
	vm.push(nilSafe(ref.get()))
	vm.pc++
}

type _putValue struct{}

var putValue _putValue

func (_putValue) exec(vm *vm) {
	l := len(vm.refStack) - 1
	ref := vm.refStack[l]
	vm.refStack[l] = nil
	vm.refStack = vm.refStack[:l]
	ref.set(vm.stack[vm.sp-1])
	vm.pc++
}

type _popRef struct{}

var popRef _popRef

func (_popRef) exec(vm *vm) {
	l := len(vm.refStack) - 1
	vm.refStack[l] = nil
	vm.refStack = vm.refStack[:l]
	vm.pc++
}

type _putValueP struct{}

var putValueP _putValueP

func (_putValueP) exec(vm *vm) {
	l := len(vm.refStack) - 1
	ref := vm.refStack[l]
	vm.refStack[l] = nil
	vm.refStack = vm.refStack[:l]
	ref.set(vm.stack[vm.sp-1])
	vm.sp--
	vm.pc++
}

type _initValueP struct{}

var initValueP _initValueP

func (_initValueP) exec(vm *vm) {
	l := len(vm.refStack) - 1
	ref := vm.refStack[l]
	vm.refStack[l] = nil
	vm.refStack = vm.refStack[:l]
	ref.init(vm.stack[vm.sp-1])
	vm.sp--
	vm.pc++
}

type loadDynamic unistring.String

func (n loadDynamic) exec(vm *vm) {
	name := unistring.String(n)
	var val Value
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if v, exists := stash.getByName(name); exists {
			val = v
			break
		}
	}
	if val == nil {
		val = vm.r.globalObject.self.getStr(name, nil)
		if val == nil {
			vm.throw(vm.r.newReferenceError(name))
			return
		}
	}
	vm.push(val)
	vm.pc++
}

type loadDynamicRef unistring.String

func (n loadDynamicRef) exec(vm *vm) {
	name := unistring.String(n)
	var val Value
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if v, exists := stash.getByName(name); exists {
			val = v
			break
		}
	}
	if val == nil {
		val = vm.r.globalObject.self.getStr(name, nil)
		if val == nil {
			val = valueUnresolved{r: vm.r, ref: name}
		}
	}
	vm.push(val)
	vm.pc++
}

type loadDynamicCallee unistring.String

func (n loadDynamicCallee) exec(vm *vm) {
	name := unistring.String(n)
	var val Value
	var callee *Object
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if v, exists := stash.getByName(name); exists {
			callee = stash.obj
			val = v
			break
		}
	}
	if val == nil {
		val = vm.r.globalObject.self.getStr(name, nil)
		if val == nil {
			val = valueUnresolved{r: vm.r, ref: name}
		}
	}
	if callee != nil {
		vm.push(callee)
	} else {
		vm.push(_undefined)
	}
	vm.push(val)
	vm.pc++
}

type _pop struct{}

var pop _pop

func (_pop) exec(vm *vm) {
	vm.sp--
	vm.pc++
}

func (vm *vm) callEval(n int, strict bool) {
	if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval {
		if n > 0 {
			srcVal := vm.stack[vm.sp-n]
			if src, ok := srcVal.(String); ok {
				ret := vm.r.eval(src, true, strict)
				vm.stack[vm.sp-n-2] = ret
			} else {
				vm.stack[vm.sp-n-2] = srcVal
			}
		} else {
			vm.stack[vm.sp-n-2] = _undefined
		}

		vm.sp -= n + 1
		vm.pc++
	} else {
		call(n).exec(vm)
	}
}

type callEval uint32

func (numargs callEval) exec(vm *vm) {
	vm.callEval(int(numargs), false)
}

type callEvalStrict uint32

func (numargs callEvalStrict) exec(vm *vm) {
	vm.callEval(int(numargs), true)
}

type _callEvalVariadic struct{}

var callEvalVariadic _callEvalVariadic

func (_callEvalVariadic) exec(vm *vm) {
	vm.callEval(vm.countVariadicArgs()-2, false)
}

type _callEvalVariadicStrict struct{}

var callEvalVariadicStrict _callEvalVariadicStrict

func (_callEvalVariadicStrict) exec(vm *vm) {
	vm.callEval(vm.countVariadicArgs()-2, true)
}

type _boxThis struct{}

var boxThis _boxThis

func (_boxThis) exec(vm *vm) {
	v := vm.stack[vm.sb]
	if v == _undefined || v == _null {
		vm.stack[vm.sb] = vm.r.globalObject
	} else {
		vm.stack[vm.sb] = v.ToObject(vm.r)
	}
	vm.pc++
}

var variadicMarker Value = newSymbol(asciiString("[variadic marker]"))

type _startVariadic struct{}

var startVariadic _startVariadic

func (_startVariadic) exec(vm *vm) {
	vm.push(variadicMarker)
	vm.pc++
}

type _callVariadic struct{}

var callVariadic _callVariadic

func (vm *vm) countVariadicArgs() int {
	count := 0
	for i := vm.sp - 1; i >= 0; i-- {
		if vm.stack[i] == variadicMarker {
			return count
		}
		count++
	}
	panic("Variadic marker was not found. Compiler bug.")
}

func (_callVariadic) exec(vm *vm) {
	call(vm.countVariadicArgs() - 2).exec(vm)
}

type _endVariadic struct{}

var endVariadic _endVariadic

func (_endVariadic) exec(vm *vm) {
	vm.sp--
	vm.stack[vm.sp-1] = vm.stack[vm.sp]
	vm.pc++
}

type call uint32

func (numargs call) exec(vm *vm) {
	// this
	// callee
	// arg0
	// ...
	// arg<numargs-1>
	n := int(numargs)
	v := vm.stack[vm.sp-n-1] // callee
	obj := vm.toCallee(v)
	obj.self.vmCall(vm, n)
}

func (vm *vm) clearStack() {
	sp := vm.sp
	stackTail := vm.stack[sp:]
	for i := range stackTail {
		stackTail[i] = nil
	}
	vm.stack = vm.stack[:sp]
}

type enterBlock struct {
	names     map[unistring.String]uint32
	stashSize uint32
	stackSize uint32
}

func (e *enterBlock) exec(vm *vm) {
	if e.stashSize > 0 {
		vm.newStash()
		vm.stash.values = make([]Value, e.stashSize)
		if len(e.names) > 0 {
			vm.stash.names = e.names
		}
	}
	ss := int(e.stackSize)
	vm.stack.expand(vm.sp + ss - 1)
	vv := vm.stack[vm.sp : vm.sp+ss]
	for i := range vv {
		vv[i] = nil
	}
	vm.sp += ss
	vm.pc++
}

type enterCatchBlock struct {
	names     map[unistring.String]uint32
	stashSize uint32
	stackSize uint32
}

func (e *enterCatchBlock) exec(vm *vm) {
	vm.newStash()
	vm.stash.values = make([]Value, e.stashSize)
	if len(e.names) > 0 {
		vm.stash.names = e.names
	}
	vm.sp--
	vm.stash.values[0] = vm.stack[vm.sp]
	ss := int(e.stackSize)
	vm.stack.expand(vm.sp + ss - 1)
	vv := vm.stack[vm.sp : vm.sp+ss]
	for i := range vv {
		vv[i] = nil
	}
	vm.sp += ss
	vm.pc++
}

type leaveBlock struct {
	stackSize uint32
	popStash  bool
}

func (l *leaveBlock) exec(vm *vm) {
	if l.popStash {
		vm.stash = vm.stash.outer
	}
	if ss := l.stackSize; ss > 0 {
		vm.sp -= int(ss)
	}
	vm.pc++
}

type enterFunc struct {
	names       map[unistring.String]uint32
	stashSize   uint32
	stackSize   uint32
	numArgs     uint32
	funcType    funcType
	argsToStash bool
	extensible  bool
}

func (e *enterFunc) exec(vm *vm) {
	// Input stack:
	//
	// callee
	// this
	// arg0
	// ...
	// argN
	// <- sp

	// Output stack:
	//
	// this <- sb
	// <local stack vars...>
	// <- sp
	sp := vm.sp
	vm.sb = sp - vm.args - 1
	vm.newStash()
	stash := vm.stash
	stash.funcType = e.funcType
	stash.values = make([]Value, e.stashSize)
	if len(e.names) > 0 {
		if e.extensible {
			m := make(map[unistring.String]uint32, len(e.names))
			for name, idx := range e.names {
				m[name] = idx
			}
			stash.names = m
		} else {
			stash.names = e.names
		}
	}

	ss := int(e.stackSize)
	ea := 0
	if e.argsToStash {
		offset := vm.args - int(e.numArgs)
		copy(stash.values, vm.stack[sp-vm.args:sp])
		if offset > 0 {
			vm.stash.extraArgs = make([]Value, offset)
			copy(stash.extraArgs, vm.stack[sp-offset:])
		} else {
			vv := stash.values[vm.args:e.numArgs]
			for i := range vv {
				vv[i] = _undefined
			}
		}
		sp -= vm.args
	} else {
		d := int(e.numArgs) - vm.args
		if d > 0 {
			ss += d
			ea = d
			vm.args = int(e.numArgs)
		}
	}
	vm.stack.expand(sp + ss - 1)
	if ea > 0 {
		vv := vm.stack[sp : vm.sp+ea]
		for i := range vv {
			vv[i] = _undefined
		}
	}
	vv := vm.stack[sp+ea : sp+ss]
	for i := range vv {
		vv[i] = nil
	}
	vm.sp = sp + ss
	vm.pc++
}

// Similar to enterFunc, but for when arguments may be accessed before they are initialised,
// e.g. by an eval() code or from a closure, or from an earlier initialiser code.
// In this case the arguments remain on stack, first argsToCopy of them are copied to the stash.
type enterFunc1 struct {
	names      map[unistring.String]uint32
	stashSize  uint32
	numArgs    uint32
	argsToCopy uint32
	funcType   funcType
	extensible bool
}

func (e *enterFunc1) exec(vm *vm) {
	sp := vm.sp
	vm.sb = sp - vm.args - 1
	vm.newStash()
	stash := vm.stash
	stash.funcType = e.funcType
	stash.values = make([]Value, e.stashSize)
	if len(e.names) > 0 {
		if e.extensible {
			m := make(map[unistring.String]uint32, len(e.names))
			for name, idx := range e.names {
				m[name] = idx
			}
			stash.names = m
		} else {
			stash.names = e.names
		}
	}
	offset := vm.args - int(e.argsToCopy)
	if offset > 0 {
		copy(stash.values, vm.stack[sp-vm.args:sp-offset])
		if offset := vm.args - int(e.numArgs); offset > 0 {
			vm.stash.extraArgs = make([]Value, offset)
			copy(stash.extraArgs, vm.stack[sp-offset:])
		}
	} else {
		copy(stash.values, vm.stack[sp-vm.args:sp])
		if int(e.argsToCopy) > vm.args {
			vv := stash.values[vm.args:e.argsToCopy]
			for i := range vv {
				vv[i] = _undefined
			}
		}
	}

	vm.pc++
}

// Finalises the initialisers section and starts the function body which has its own
// scope. When used in conjunction with enterFunc1 adjustStack is set to true which
// causes the arguments to be removed from the stack.
type enterFuncBody struct {
	enterBlock
	funcType    funcType
	extensible  bool
	adjustStack bool
}

func (e *enterFuncBody) exec(vm *vm) {
	if e.stashSize > 0 || e.extensible {
		vm.newStash()
		stash := vm.stash
		stash.funcType = e.funcType
		stash.values = make([]Value, e.stashSize)
		if len(e.names) > 0 {
			if e.extensible {
				m := make(map[unistring.String]uint32, len(e.names))
				for name, idx := range e.names {
					m[name] = idx
				}
				stash.names = m
			} else {
				stash.names = e.names
			}
		}
	}
	sp := vm.sp
	if e.adjustStack {
		sp -= vm.args
	}
	nsp := sp + int(e.stackSize)
	if e.stackSize > 0 {
		vm.stack.expand(nsp - 1)
		vv := vm.stack[sp:nsp]
		for i := range vv {
			vv[i] = nil
		}
	}
	vm.sp = nsp
	vm.pc++
}

type _ret struct{}

var ret _ret

func (_ret) exec(vm *vm) {
	// callee -3
	// this -2 <- sb
	// retval -1

	vm.stack[vm.sb-1] = vm.stack[vm.sp-1]
	vm.sp = vm.sb
	vm.popCtx()
	vm.pc++
}

type cret uint32

func (c cret) exec(vm *vm) {
	vm.stack[vm.sb] = *vm.getStashPtr(uint32(c))
	ret.exec(vm)
}

type enterFuncStashless struct {
	stackSize uint32
	args      uint32
}

func (e *enterFuncStashless) exec(vm *vm) {
	sp := vm.sp
	vm.sb = sp - vm.args - 1
	d := int(e.args) - vm.args
	if d > 0 {
		ss := sp + int(e.stackSize) + d
		vm.stack.expand(ss)
		vv := vm.stack[sp : sp+d]
		for i := range vv {
			vv[i] = _undefined
		}
		vv = vm.stack[sp+d : ss]
		for i := range vv {
			vv[i] = nil
		}
		vm.args = int(e.args)
		vm.sp = ss
	} else {
		if e.stackSize > 0 {
			ss := sp + int(e.stackSize)
			vm.stack.expand(ss)
			vv := vm.stack[sp:ss]
			for i := range vv {
				vv[i] = nil
			}
			vm.sp = ss
		}
	}
	vm.pc++
}

type newFuncInstruction interface {
	getPrg() *Program
}

type newFunc struct {
	prg    *Program
	name   unistring.String
	source string

	length int
	strict bool
}

func (n *newFunc) exec(vm *vm) {
	obj := vm.r.newFunc(n.name, n.length, n.strict)
	obj.prg = n.prg
	obj.stash = vm.stash
	obj.privEnv = vm.privEnv
	obj.src = n.source
	vm.push(obj.val)
	vm.pc++
}

func (n *newFunc) getPrg() *Program {
	return n.prg
}

type newAsyncFunc struct {
	newFunc
}

func (n *newAsyncFunc) exec(vm *vm) {
	obj := vm.r.newAsyncFunc(n.name, n.length, n.strict)
	obj.prg = n.prg
	obj.stash = vm.stash
	obj.privEnv = vm.privEnv
	obj.src = n.source
	vm.push(obj.val)
	vm.pc++
}

type newGeneratorFunc struct {
	newFunc
}

func (n *newGeneratorFunc) exec(vm *vm) {
	obj := vm.r.newGeneratorFunc(n.name, n.length, n.strict)
	obj.prg = n.prg
	obj.stash = vm.stash
	obj.privEnv = vm.privEnv
	obj.src = n.source
	vm.push(obj.val)
	vm.pc++
}

type newMethod struct {
	newFunc
	homeObjOffset uint32
}

func (n *newMethod) _exec(vm *vm, obj *methodFuncObject) {
	obj.prg = n.prg
	obj.stash = vm.stash
	obj.privEnv = vm.privEnv
	obj.src = n.source
	if n.homeObjOffset > 0 {
		obj.homeObject = vm.r.toObject(vm.stack[vm.sp-int(n.homeObjOffset)])
	}
	vm.push(obj.val)
	vm.pc++
}

func (n *newMethod) exec(vm *vm) {
	n._exec(vm, vm.r.newMethod(n.name, n.length, n.strict))
}

type newAsyncMethod struct {
	newMethod
}

func (n *newAsyncMethod) exec(vm *vm) {
	obj := vm.r.newAsyncMethod(n.name, n.length, n.strict)
	n._exec(vm, &obj.methodFuncObject)
}

type newGeneratorMethod struct {
	newMethod
}

func (n *newGeneratorMethod) exec(vm *vm) {
	obj := vm.r.newGeneratorMethod(n.name, n.length, n.strict)
	n._exec(vm, &obj.methodFuncObject)
}

type newArrowFunc struct {
	newFunc
}

type newAsyncArrowFunc struct {
	newArrowFunc
}

func getFuncObject(v Value) *Object {
	if o, ok := v.(*Object); ok {
		if fn, ok := o.self.(*arrowFuncObject); ok {
			return fn.funcObj
		}
		return o
	}
	if v == _undefined {
		return nil
	}
	panic(typeError("Value is not an Object"))
}

func getHomeObject(v Value) *Object {
	if o, ok := v.(*Object); ok {
		switch fn := o.self.(type) {
		case *methodFuncObject:
			return fn.homeObject
		case *generatorMethodFuncObject:
			return fn.homeObject
		case *asyncMethodFuncObject:
			return fn.homeObject
		case *classFuncObject:
			return o.runtime.toObject(fn.getStr("prototype", nil))
		case *arrowFuncObject:
			return getHomeObject(fn.funcObj)
		case *asyncArrowFuncObject:
			return getHomeObject(fn.funcObj)
		}
	}
	panic(newTypeError("Compiler bug: getHomeObject() on the wrong value: %T", v))
}

func (n *newArrowFunc) _exec(vm *vm, obj *arrowFuncObject) {
	obj.prg = n.prg
	obj.stash = vm.stash
	obj.privEnv = vm.privEnv
	obj.src = n.source
	if vm.sb > 0 {
		obj.funcObj = getFuncObject(vm.stack[vm.sb-1])
	}
	vm.push(obj.val)
	vm.pc++
}

func (n *newArrowFunc) exec(vm *vm) {
	n._exec(vm, vm.r.newArrowFunc(n.name, n.length, n.strict))
}

func (n *newAsyncArrowFunc) exec(vm *vm) {
	obj := vm.r.newAsyncArrowFunc(n.name, n.length, n.strict)
	n._exec(vm, &obj.arrowFuncObject)
}

func (vm *vm) alreadyDeclared(name unistring.String) Value {
	return vm.r.newErrorf(vm.r.getSyntaxError(), "Identifier '%s' has already been declared", name)
}

func (vm *vm) checkBindVarsGlobal(names []unistring.String) {
	o := vm.r.globalObject.self
	sn := vm.r.global.stash.names
	if bo, ok := o.(*baseObject); ok {
		// shortcut
		if bo.extensible {
			for _, name := range names {
				if _, exists := sn[name]; exists {
					panic(vm.alreadyDeclared(name))
				}
			}
		} else {
			for _, name := range names {
				if !bo.hasOwnPropertyStr(name) {
					panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name))
				}
				if _, exists := sn[name]; exists {
					panic(vm.alreadyDeclared(name))
				}
			}
		}
	} else {
		for _, name := range names {
			if !o.hasOwnPropertyStr(name) && !o.isExtensible() {
				panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name))
			}
			if _, exists := sn[name]; exists {
				panic(vm.alreadyDeclared(name))
			}
		}
	}
}

func (vm *vm) createGlobalVarBindings(names []unistring.String, d bool) {
	o := vm.r.globalObject.self
	if bo, ok := o.(*templatedObject); ok {
		for _, name := range names {
			if !bo.hasOwnPropertyStr(name) && bo.extensible {
				bo._putProp(name, _undefined, true, true, d)
			}
		}
	} else {
		var cf Flag
		if d {
			cf = FLAG_TRUE
		} else {
			cf = FLAG_FALSE
		}
		for _, name := range names {
			if !o.hasOwnPropertyStr(name) && o.isExtensible() {
				o.defineOwnPropertyStr(name, PropertyDescriptor{
					Value:        _undefined,
					Writable:     FLAG_TRUE,
					Enumerable:   FLAG_TRUE,
					Configurable: cf,
				}, true)
				o.setOwnStr(name, _undefined, false)
			}
		}
	}
}

func (vm *vm) createGlobalFuncBindings(names []unistring.String, d bool) {
	o := vm.r.globalObject.self
	b := vm.sp - len(names)
	var shortcutObj *templatedObject
	if o, ok := o.(*templatedObject); ok {
		shortcutObj = o
	}
	for i, name := range names {
		var desc PropertyDescriptor
		prop := o.getOwnPropStr(name)
		desc.Value = vm.stack[b+i]
		if shortcutObj != nil && prop == nil && shortcutObj.extensible {
			shortcutObj._putProp(name, desc.Value, true, true, d)
		} else {
			if prop, ok := prop.(*valueProperty); ok && !prop.configurable {
				// no-op
			} else {
				desc.Writable = FLAG_TRUE
				desc.Enumerable = FLAG_TRUE
				if d {
					desc.Configurable = FLAG_TRUE
				} else {
					desc.Configurable = FLAG_FALSE
				}
			}
			if shortcutObj != nil {
				shortcutObj.defineOwnPropertyStr(name, desc, true)
			} else {
				o.defineOwnPropertyStr(name, desc, true)
				o.setOwnStr(name, desc.Value, false) // not a bug, see https://262.ecma-international.org/#sec-createglobalfunctionbinding
			}
		}
	}
	vm.sp = b
}

func (vm *vm) checkBindFuncsGlobal(names []unistring.String) {
	o := vm.r.globalObject.self
	sn := vm.r.global.stash.names
	for _, name := range names {
		if _, exists := sn[name]; exists {
			panic(vm.alreadyDeclared(name))
		}
		prop := o.getOwnPropStr(name)
		allowed := true
		switch prop := prop.(type) {
		case nil:
			allowed = o.isExtensible()
		case *valueProperty:
			allowed = prop.configurable || prop.getterFunc == nil && prop.setterFunc == nil && prop.writable && prop.enumerable
		}
		if !allowed {
			panic(vm.r.NewTypeError("Cannot redefine global function '%s'", name))
		}
	}
}

func (vm *vm) checkBindLexGlobal(names []unistring.String) {
	o := vm.r.globalObject.self
	s := &vm.r.global.stash
	for _, name := range names {
		if _, exists := s.names[name]; exists {
			goto fail
		}
		if prop, ok := o.getOwnPropStr(name).(*valueProperty); ok && !prop.configurable {
			goto fail
		}
		continue
	fail:
		panic(vm.alreadyDeclared(name))
	}
}

type bindVars struct {
	names     []unistring.String
	deletable bool
}

func (d *bindVars) exec(vm *vm) {
	var target *stash
	for _, name := range d.names {
		for s := vm.stash; s != nil; s = s.outer {
			if idx, exists := s.names[name]; exists && idx&maskVar == 0 {
				vm.throw(vm.alreadyDeclared(name))
				return
			}
			if s.isVariable() {
				target = s
				break
			}
		}
	}
	if target == nil {
		target = vm.stash
	}
	deletable := d.deletable
	for _, name := range d.names {
		target.createBinding(name, deletable)
	}
	vm.pc++
}

type bindGlobal struct {
	vars, funcs, lets, consts []unistring.String

	deletable bool
}

func (b *bindGlobal) exec(vm *vm) {
	vm.checkBindFuncsGlobal(b.funcs)
	vm.checkBindLexGlobal(b.lets)
	vm.checkBindLexGlobal(b.consts)
	vm.checkBindVarsGlobal(b.vars)

	s := &vm.r.global.stash
	for _, name := range b.lets {
		s.createLexBinding(name, false)
	}
	for _, name := range b.consts {
		s.createLexBinding(name, true)
	}
	vm.createGlobalFuncBindings(b.funcs, b.deletable)
	vm.createGlobalVarBindings(b.vars, b.deletable)
	vm.pc++
}

type jneP int32

func (j jneP) exec(vm *vm) {
	vm.sp--
	if !vm.stack[vm.sp].ToBoolean() {
		vm.pc += int(j)
	} else {
		vm.pc++
	}
}

type jeqP int32

func (j jeqP) exec(vm *vm) {
	vm.sp--
	if vm.stack[vm.sp].ToBoolean() {
		vm.pc += int(j)
	} else {
		vm.pc++
	}
}

type jeq int32

func (j jeq) exec(vm *vm) {
	if vm.stack[vm.sp-1].ToBoolean() {
		vm.pc += int(j)
	} else {
		vm.sp--
		vm.pc++
	}
}

type jne int32

func (j jne) exec(vm *vm) {
	if !vm.stack[vm.sp-1].ToBoolean() {
		vm.pc += int(j)
	} else {
		vm.sp--
		vm.pc++
	}
}

type jdef int32

func (j jdef) exec(vm *vm) {
	if vm.stack[vm.sp-1] != _undefined {
		vm.pc += int(j)
	} else {
		vm.sp--
		vm.pc++
	}
}

type jdefP int32

func (j jdefP) exec(vm *vm) {
	if vm.stack[vm.sp-1] != _undefined {
		vm.pc += int(j)
	} else {
		vm.pc++
	}
	vm.sp--
}

type jopt int32

func (j jopt) exec(vm *vm) {
	switch vm.stack[vm.sp-1] {
	case _null:
		vm.stack[vm.sp-1] = _undefined
		fallthrough
	case _undefined:
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type joptc int32

func (j joptc) exec(vm *vm) {
	switch vm.stack[vm.sp-1].(type) {
	case valueNull, valueUndefined, memberUnresolved:
		vm.sp--
		vm.stack[vm.sp-1] = _undefined
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type joptdel int32

func (j joptdel) exec(vm *vm) {
	switch vm.stack[vm.sp-1].(type) {
	case valueNull, valueUndefined:
		vm.stack[vm.sp-1] = valueTrue
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type joptdelc int32

func (j joptdelc) exec(vm *vm) {
	switch vm.stack[vm.sp-1].(type) {
	case valueNull, valueUndefined, memberUnresolved:
		vm.sp--
		vm.stack[vm.sp-1] = valueTrue
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type joptdelP int32

func (j joptdelP) exec(vm *vm) {
	switch vm.stack[vm.sp-1].(type) {
	case valueNull, valueUndefined:
		vm.sp--
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type joptdelcP int32

func (j joptdelcP) exec(vm *vm) {
	switch vm.stack[vm.sp-1].(type) {
	case valueNull, valueUndefined, memberUnresolved:
		vm.sp -= 2
		vm.pc += int(j)
	default:
		vm.pc++
	}
}

type jcoalesc int32

func (j jcoalesc) exec(vm *vm) {
	switch vm.stack[vm.sp-1] {
	case _undefined, _null:
		vm.sp--
		vm.pc++
	default:
		vm.pc += int(j)
	}
}

type jcoalescP int32

func (j jcoalescP) exec(vm *vm) {
	vm.sp--
	switch vm.stack[vm.sp] {
	case _undefined, _null:
		vm.pc++
	default:
		vm.pc += int(j)
	}
}

type _not struct{}

var not _not

func (_not) exec(vm *vm) {
	if vm.stack[vm.sp-1].ToBoolean() {
		vm.stack[vm.sp-1] = valueFalse
	} else {
		vm.stack[vm.sp-1] = valueTrue
	}
	vm.pc++
}

func toPrimitiveNumber(v Value) Value {
	if o, ok := v.(*Object); ok {
		return o.toPrimitiveNumber()
	}
	return v
}

func toPrimitive(v Value) Value {
	if o, ok := v.(*Object); ok {
		return o.toPrimitive()
	}
	return v
}

func cmp(px, py Value) Value {
	var ret bool
	xs, isPxString := px.(String)
	ys, isPyString := py.(String)

	if isPxString && isPyString {
		ret = xs.CompareTo(ys) < 0
		goto end
	} else {
		if px, ok := px.(*valueBigInt); ok && isPyString {
			ny, err := stringToBigInt(ys.toTrimmedUTF8())
			if err != nil {
				return _undefined
			}
			ret = (*big.Int)(px).Cmp(ny) < 0
			goto end
		}
		if py, ok := py.(*valueBigInt); ok && isPxString {
			nx, err := stringToBigInt(xs.toTrimmedUTF8())
			if err != nil {
				return _undefined
			}
			ret = nx.Cmp((*big.Int)(py)) < 0
			goto end
		}
	}

	px = toNumeric(px)
	py = toNumeric(py)

	switch nx := px.(type) {
	case valueInt:
		switch ny := py.(type) {
		case valueInt:
			ret = nx < ny
			goto end
		case *valueBigInt:
			ret = big.NewInt(int64(nx)).Cmp((*big.Int)(ny)) < 0
			goto end
		}
	case valueFloat:
		switch ny := py.(type) {
		case *valueBigInt:
			switch {
			case math.IsNaN(float64(nx)):
				return _undefined
			case nx == _negativeInf:
				ret = true
				goto end
			}
			if nx := big.NewFloat(float64(nx)); nx.IsInt() {
				nx, _ := nx.Int(nil)
				ret = nx.Cmp((*big.Int)(ny)) < 0
			} else {
				ret = nx.Cmp(new(big.Float).SetInt((*big.Int)(ny))) < 0
			}
			goto end
		}
	case *valueBigInt:
		switch ny := py.(type) {
		case valueInt:
			ret = (*big.Int)(nx).Cmp(big.NewInt(int64(ny))) < 0
			goto end
		case valueFloat:
			switch {
			case math.IsNaN(float64(ny)):
				return _undefined
			case ny == _positiveInf:
				ret = true
				goto end
			}
			if ny := big.NewFloat(float64(ny)); ny.IsInt() {
				ny, _ := ny.Int(nil)
				ret = (*big.Int)(nx).Cmp(ny) < 0
			} else {
				ret = new(big.Float).SetInt((*big.Int)(nx)).Cmp(ny) < 0
			}
			goto end
		case *valueBigInt:
			ret = (*big.Int)(nx).Cmp((*big.Int)(ny)) < 0
			goto end
		}
	}

	if nx, ny := px.ToFloat(), py.ToFloat(); math.IsNaN(nx) || math.IsNaN(ny) {
		return _undefined
	} else {
		ret = nx < ny
	}

end:
	if ret {
		return valueTrue
	}
	return valueFalse

}

type _op_lt struct{}

var op_lt _op_lt

func (_op_lt) exec(vm *vm) {
	left := toPrimitiveNumber(vm.stack[vm.sp-2])
	right := toPrimitiveNumber(vm.stack[vm.sp-1])

	r := cmp(left, right)
	if r == _undefined {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = r
	}
	vm.sp--
	vm.pc++
}

type _op_lte struct{}

var op_lte _op_lte

func (_op_lte) exec(vm *vm) {
	left := toPrimitiveNumber(vm.stack[vm.sp-2])
	right := toPrimitiveNumber(vm.stack[vm.sp-1])

	r := cmp(right, left)
	if r == _undefined || r == valueTrue {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = valueTrue
	}

	vm.sp--
	vm.pc++
}

type _op_gt struct{}

var op_gt _op_gt

func (_op_gt) exec(vm *vm) {
	left := toPrimitiveNumber(vm.stack[vm.sp-2])
	right := toPrimitiveNumber(vm.stack[vm.sp-1])

	r := cmp(right, left)
	if r == _undefined {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = r
	}
	vm.sp--
	vm.pc++
}

type _op_gte struct{}

var op_gte _op_gte

func (_op_gte) exec(vm *vm) {
	left := toPrimitiveNumber(vm.stack[vm.sp-2])
	right := toPrimitiveNumber(vm.stack[vm.sp-1])

	r := cmp(left, right)
	if r == _undefined || r == valueTrue {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = valueTrue
	}

	vm.sp--
	vm.pc++
}

type _op_eq struct{}

var op_eq _op_eq

func (_op_eq) exec(vm *vm) {
	if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) {
		vm.stack[vm.sp-2] = valueTrue
	} else {
		vm.stack[vm.sp-2] = valueFalse
	}
	vm.sp--
	vm.pc++
}

type _op_neq struct{}

var op_neq _op_neq

func (_op_neq) exec(vm *vm) {
	if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = valueTrue
	}
	vm.sp--
	vm.pc++
}

type _op_strict_eq struct{}

var op_strict_eq _op_strict_eq

func (_op_strict_eq) exec(vm *vm) {
	if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) {
		vm.stack[vm.sp-2] = valueTrue
	} else {
		vm.stack[vm.sp-2] = valueFalse
	}
	vm.sp--
	vm.pc++
}

type _op_strict_neq struct{}

var op_strict_neq _op_strict_neq

func (_op_strict_neq) exec(vm *vm) {
	if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) {
		vm.stack[vm.sp-2] = valueFalse
	} else {
		vm.stack[vm.sp-2] = valueTrue
	}
	vm.sp--
	vm.pc++
}

type _op_instanceof struct{}

var op_instanceof _op_instanceof

func (_op_instanceof) exec(vm *vm) {
	left := vm.stack[vm.sp-2]
	right := vm.r.toObject(vm.stack[vm.sp-1])

	if instanceOfOperator(left, right) {
		vm.stack[vm.sp-2] = valueTrue
	} else {
		vm.stack[vm.sp-2] = valueFalse
	}

	vm.sp--
	vm.pc++
}

type _op_in struct{}

var op_in _op_in

func (_op_in) exec(vm *vm) {
	left := vm.stack[vm.sp-2]
	right := vm.r.toObject(vm.stack[vm.sp-1])

	if right.hasProperty(left) {
		vm.stack[vm.sp-2] = valueTrue
	} else {
		vm.stack[vm.sp-2] = valueFalse
	}

	vm.sp--
	vm.pc++
}

type try struct {
	catchOffset   int32
	finallyOffset int32
}

func (t try) exec(vm *vm) {
	var catchPos, finallyPos int32
	if t.catchOffset > 0 {
		catchPos = int32(vm.pc) + t.catchOffset
	} else {
		catchPos = -1
	}
	if t.finallyOffset > 0 {
		finallyPos = int32(vm.pc) + t.finallyOffset
	} else {
		finallyPos = -1
	}
	vm.pushTryFrame(catchPos, finallyPos)
	vm.pc++
}

type leaveTry struct{}

func (leaveTry) exec(vm *vm) {
	tf := &vm.tryStack[len(vm.tryStack)-1]
	if tf.finallyPos >= 0 {
		tf.finallyRet = int32(vm.pc + 1)
		vm.pc = int(tf.finallyPos)
		tf.finallyPos = -1
		tf.catchPos = -1
		vm.sp, vm.stash = int(tf.sp), tf.stash
	} else {
		vm.popTryFrame()
		vm.pc++
	}
}

type enterFinally struct{}

func (enterFinally) exec(vm *vm) {
	tf := &vm.tryStack[len(vm.tryStack)-1]
	tf.finallyPos = -1
	vm.pc++
}

type leaveFinally struct{}

func (leaveFinally) exec(vm *vm) {
	tf := &vm.tryStack[len(vm.tryStack)-1]
	ex, ret := tf.exception, tf.finallyRet
	tf.exception = nil
	vm.popTryFrame()
	if ex != nil {
		vm.throw(ex)
		return
	} else {
		if ret != -1 {
			vm.pc = int(ret)
		} else {
			vm.pc++
		}
	}
}

type _throw struct{}

var throw _throw

func (_throw) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	ex := &Exception{
		val: v,
	}

	if o, ok := v.(*Object); ok {
		if e, ok := o.self.(*errorObject); ok {
			if len(e.stack) > 0 {
				ex.stack = e.stack
			}
		}
	}

	if ex.stack == nil {
		ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0)
	}

	if ex = vm.handleThrow(ex); ex != nil {
		panic(ex)
	}
}

type _newVariadic struct{}

var newVariadic _newVariadic

func (_newVariadic) exec(vm *vm) {
	_new(vm.countVariadicArgs() - 1).exec(vm)
}

type _new uint32

func (n _new) exec(vm *vm) {
	sp := vm.sp - int(n)
	obj := vm.stack[sp-1]
	ctor := vm.r.toConstructor(obj)
	vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp], nil)
	vm.sp = sp
	vm.pc++
}

type superCall uint32

func (s superCall) exec(vm *vm) {
	l := len(vm.refStack) - 1
	thisRef := vm.refStack[l]
	vm.refStack[l] = nil
	vm.refStack = vm.refStack[:l]

	obj := vm.r.toObject(vm.stack[vm.sb-1])
	var cls *classFuncObject
	switch fn := obj.self.(type) {
	case *classFuncObject:
		cls = fn
	case *arrowFuncObject:
		cls, _ = fn.funcObj.self.(*classFuncObject)
	}
	if cls == nil {
		vm.throw(vm.r.NewTypeError("wrong callee type for super()"))
		return
	}
	sp := vm.sp - int(s)
	newTarget := vm.r.toObject(vm.newTarget)
	v := cls.createInstance(vm.stack[sp:vm.sp], newTarget)
	thisRef.set(v)
	vm.sp = sp
	cls._initFields(v)
	vm.push(v)
	vm.pc++
}

type _superCallVariadic struct{}

var superCallVariadic _superCallVariadic

func (_superCallVariadic) exec(vm *vm) {
	superCall(vm.countVariadicArgs()).exec(vm)
}

type _loadNewTarget struct{}

var loadNewTarget _loadNewTarget

func (_loadNewTarget) exec(vm *vm) {
	if t := vm.newTarget; t != nil {
		vm.push(t)
	} else {
		vm.push(_undefined)
	}
	vm.pc++
}

type _typeof struct{}

var typeof _typeof

func (_typeof) exec(vm *vm) {
	var r Value
	switch v := vm.stack[vm.sp-1].(type) {
	case valueUndefined, valueUnresolved:
		r = stringUndefined
	case valueNull:
		r = stringObjectC
	case *Object:
		r = v.self.typeOf()
	case valueBool:
		r = stringBoolean
	case String:
		r = stringString
	case valueInt, valueFloat:
		r = stringNumber
	case *valueBigInt:
		r = stringBigInt
	case *Symbol:
		r = stringSymbol
	default:
		panic(newTypeError("Compiler bug: unknown type: %T", v))
	}
	vm.stack[vm.sp-1] = r
	vm.pc++
}

type createArgsMapped uint32

func (formalArgs createArgsMapped) exec(vm *vm) {
	v := &Object{runtime: vm.r}
	args := &argumentsObject{}
	args.extensible = true
	args.prototype = vm.r.global.ObjectPrototype
	args.class = "Arguments"
	v.self = args
	args.val = v
	args.length = vm.args
	args.init()
	i := 0
	c := int(formalArgs)
	if vm.args < c {
		c = vm.args
	}
	for ; i < c; i++ {
		args._put(unistring.String(strconv.Itoa(i)), &mappedProperty{
			valueProperty: valueProperty{
				writable:     true,
				configurable: true,
				enumerable:   true,
			},
			v: &vm.stash.values[i],
		})
	}

	for _, v := range vm.stash.extraArgs {
		args._put(unistring.String(strconv.Itoa(i)), v)
		i++
	}

	args._putProp("callee", vm.stack[vm.sb-1], true, false, true)
	args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true))
	vm.push(v)
	vm.pc++
}

type createArgsUnmapped uint32

func (formalArgs createArgsUnmapped) exec(vm *vm) {
	args := vm.r.newBaseObject(vm.r.global.ObjectPrototype, "Arguments")
	i := 0
	c := int(formalArgs)
	if vm.args < c {
		c = vm.args
	}
	for _, v := range vm.stash.values[:c] {
		args._put(unistring.String(strconv.Itoa(i)), v)
		i++
	}

	for _, v := range vm.stash.extraArgs {
		args._put(unistring.String(strconv.Itoa(i)), v)
		i++
	}

	args._putProp("length", intToValue(int64(vm.args)), true, false, true)
	args._put("callee", vm.r.newThrowerProperty(false))
	args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true))
	vm.push(args.val)
	vm.pc++
}

type _enterWith struct{}

var enterWith _enterWith

func (_enterWith) exec(vm *vm) {
	vm.newStash()
	vm.stash.obj = vm.stack[vm.sp-1].ToObject(vm.r)
	vm.sp--
	vm.pc++
}

type _leaveWith struct{}

var leaveWith _leaveWith

func (_leaveWith) exec(vm *vm) {
	vm.stash = vm.stash.outer
	vm.pc++
}

func emptyIter() (propIterItem, iterNextFunc) {
	return propIterItem{}, nil
}

type _enumerate struct{}

var enumerate _enumerate

func (_enumerate) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	if v == _undefined || v == _null {
		vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter})
	} else {
		vm.iterStack = append(vm.iterStack, iterStackItem{f: enumerateRecursive(v.ToObject(vm.r))})
	}
	vm.sp--
	vm.pc++
}

type enumNext int32

func (jmp enumNext) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	item, n := vm.iterStack[l].f()
	if n != nil {
		vm.iterStack[l].val = item.name
		vm.iterStack[l].f = n
		vm.pc++
	} else {
		vm.pc += int(jmp)
	}
}

type _enumGet struct{}

var enumGet _enumGet

func (_enumGet) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	vm.push(vm.iterStack[l].val)
	vm.pc++
}

type _enumPop struct{}

var enumPop _enumPop

func (_enumPop) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	vm.iterStack[l] = iterStackItem{}
	vm.iterStack = vm.iterStack[:l]
	vm.pc++
}

type _enumPopClose struct{}

var enumPopClose _enumPopClose

func (_enumPopClose) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	item := vm.iterStack[l]
	vm.iterStack[l] = iterStackItem{}
	vm.iterStack = vm.iterStack[:l]
	if iter := item.iter; iter != nil {
		iter.returnIter()
	}
	vm.pc++
}

type _iterateP struct{}

var iterateP _iterateP

func (_iterateP) exec(vm *vm) {
	iter := vm.r.getIterator(vm.stack[vm.sp-1], nil)
	vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter})
	vm.sp--
	vm.pc++
}

type _iterate struct{}

var iterate _iterate

func (_iterate) exec(vm *vm) {
	iter := vm.r.getIterator(vm.stack[vm.sp-1], nil)
	vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter})
	vm.pc++
}

type iterNext int32

func (jmp iterNext) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	iter := vm.iterStack[l].iter
	value, ex := iter.step()
	if ex == nil {
		if value == nil {
			vm.pc += int(jmp)
		} else {
			vm.iterStack[l].val = value
			vm.pc++
		}
	} else {
		l := len(vm.iterStack) - 1
		vm.iterStack[l] = iterStackItem{}
		vm.iterStack = vm.iterStack[:l]
		vm.throw(ex.val)
		return
	}
}

type iterGetNextOrUndef struct{}

func (iterGetNextOrUndef) exec(vm *vm) {
	l := len(vm.iterStack) - 1
	iter := vm.iterStack[l].iter
	var value Value
	if iter.iterator != nil {
		var ex *Exception
		value, ex = iter.step()
		if ex != nil {
			l := len(vm.iterStack) - 1
			vm.iterStack[l] = iterStackItem{}
			vm.iterStack = vm.iterStack[:l]
			vm.throw(ex.val)
			return
		}
	}
	vm.push(nilSafe(value))
	vm.pc++
}

type copyStash struct{}

func (copyStash) exec(vm *vm) {
	oldStash := vm.stash
	newStash := &stash{
		outer: oldStash.outer,
	}
	vm.stashAllocs++
	newStash.values = append([]Value(nil), oldStash.values...)
	newStash.names = oldStash.names
	vm.stash = newStash
	vm.pc++
}

type _throwAssignToConst struct{}

var throwAssignToConst _throwAssignToConst

func (_throwAssignToConst) exec(vm *vm) {
	vm.throw(errAssignToConst)
}

func (r *Runtime) copyDataProperties(target, source Value) {
	targetObj := r.toObject(target)
	if source == _null || source == _undefined {
		return
	}
	sourceObj := source.ToObject(r)
	for item, next := iterateEnumerableProperties(sourceObj)(); next != nil; item, next = next() {
		createDataPropertyOrThrow(targetObj, item.name, item.value)
	}
}

type _copySpread struct{}

var copySpread _copySpread

func (_copySpread) exec(vm *vm) {
	vm.r.copyDataProperties(vm.stack[vm.sp-2], vm.stack[vm.sp-1])
	vm.sp--
	vm.pc++
}

type _copyRest struct{}

var copyRest _copyRest

func (_copyRest) exec(vm *vm) {
	vm.push(vm.r.NewObject())
	vm.r.copyDataProperties(vm.stack[vm.sp-1], vm.stack[vm.sp-2])
	vm.pc++
}

type _createDestructSrc struct{}

var createDestructSrc _createDestructSrc

func (_createDestructSrc) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	vm.r.checkObjectCoercible(v)
	vm.push(vm.r.newDestructKeyedSource(v))
	vm.pc++
}

type _checkObjectCoercible struct{}

var checkObjectCoercible _checkObjectCoercible

func (_checkObjectCoercible) exec(vm *vm) {
	vm.r.checkObjectCoercible(vm.stack[vm.sp-1])
	vm.pc++
}

type createArgsRestStack int

func (n createArgsRestStack) exec(vm *vm) {
	var values []Value
	delta := vm.args - int(n)
	if delta > 0 {
		values = make([]Value, delta)
		copy(values, vm.stack[vm.sb+int(n)+1:])
	}
	vm.push(vm.r.newArrayValues(values))
	vm.pc++
}

type _createArgsRestStash struct{}

var createArgsRestStash _createArgsRestStash

func (_createArgsRestStash) exec(vm *vm) {
	vm.push(vm.r.newArrayValues(vm.stash.extraArgs))
	vm.stash.extraArgs = nil
	vm.pc++
}

type concatStrings int

func (n concatStrings) exec(vm *vm) {
	strs := vm.stack[vm.sp-int(n) : vm.sp]
	length := 0
	allAscii := true
	for i, s := range strs {
		switch s := s.(type) {
		case asciiString:
			length += s.Length()
		case unicodeString:
			length += s.Length()
			allAscii = false
		case *importedString:
			s.ensureScanned()
			if s.u != nil {
				strs[i] = s.u
				length += s.u.Length()
				allAscii = false
			} else {
				strs[i] = asciiString(s.s)
				length += len(s.s)
			}
		default:
			panic(unknownStringTypeErr(s))
		}
	}

	vm.sp -= int(n) - 1
	if allAscii {
		var buf strings.Builder
		buf.Grow(length)
		for _, s := range strs {
			buf.WriteString(string(s.(asciiString)))
		}
		vm.stack[vm.sp-1] = asciiString(buf.String())
	} else {
		var buf unicodeStringBuilder
		buf.Grow(length)
		for _, s := range strs {
			buf.writeString(s.(String))
		}
		vm.stack[vm.sp-1] = buf.String()
	}
	vm.pc++
}

type getTaggedTmplObject struct {
	raw, cooked []Value
}

// As tagged template objects are not cached (because it's hard to ensure the cache is cleaned without using
// finalizers) this wrapper is needed to override the equality method so that two objects for the same template
// literal appeared to be equal from the code's point of view.
type taggedTemplateArray struct {
	*arrayObject
	idPtr *[]Value
}

func (a *taggedTemplateArray) equal(other objectImpl) bool {
	if o, ok := other.(*taggedTemplateArray); ok {
		return a.idPtr == o.idPtr
	}
	return false
}

func (c *getTaggedTmplObject) exec(vm *vm) {
	cooked := vm.r.newArrayObject()
	setArrayValues(cooked, c.cooked)
	raw := vm.r.newArrayObject()
	setArrayValues(raw, c.raw)

	cooked.propValueCount = len(c.cooked)
	cooked.lengthProp.writable = false

	raw.propValueCount = len(c.raw)
	raw.lengthProp.writable = false

	raw.preventExtensions(true)
	raw.val.self = &taggedTemplateArray{
		arrayObject: raw,
		idPtr:       &c.raw,
	}

	cooked._putProp("raw", raw.val, false, false, false)
	cooked.preventExtensions(true)
	cooked.val.self = &taggedTemplateArray{
		arrayObject: cooked,
		idPtr:       &c.cooked,
	}

	vm.push(cooked.val)
	vm.pc++
}

type _loadSuper struct{}

var loadSuper _loadSuper

func (_loadSuper) exec(vm *vm) {
	homeObject := getHomeObject(vm.stack[vm.sb-1])
	if proto := homeObject.Prototype(); proto != nil {
		vm.push(proto)
	} else {
		vm.push(_undefined)
	}
	vm.pc++
}

type newClass struct {
	ctor       *Program
	name       unistring.String
	source     string
	initFields *Program

	privateFields, privateMethods       []unistring.String // only set when dynamic resolution is needed
	numPrivateFields, numPrivateMethods uint32

	length        int
	hasPrivateEnv bool
}

type newDerivedClass struct {
	newClass
}

func (vm *vm) createPrivateType(f *classFuncObject, numFields, numMethods uint32) {
	typ := &privateEnvType{}
	typ.numFields = numFields
	typ.numMethods = numMethods
	f.privateEnvType = typ
	f.privateMethods = make([]Value, numMethods)
}

func (vm *vm) fillPrivateNamesMap(typ *privateEnvType, privateFields, privateMethods []unistring.String) {
	if len(privateFields) > 0 || len(privateMethods) > 0 {
		penv := vm.privEnv.names
		if penv == nil {
			penv = make(privateNames)
			vm.privEnv.names = penv
		}
		for idx, field := range privateFields {
			penv[field] = &privateId{
				typ: typ,
				idx: uint32(idx),
			}
		}
		for idx, method := range privateMethods {
			penv[method] = &privateId{
				typ:      typ,
				idx:      uint32(idx),
				isMethod: true,
			}
		}
	}
}

func (c *newClass) create(protoParent, ctorParent *Object, vm *vm, derived bool) (prototype, cls *Object) {
	proto := vm.r.newBaseObject(protoParent, classObject)
	f := vm.r.newClassFunc(c.name, c.length, ctorParent, derived)
	f._putProp("prototype", proto.val, false, false, false)
	proto._putProp("constructor", f.val, true, false, true)
	f.prg = c.ctor
	f.stash = vm.stash
	f.src = c.source
	f.initFields = c.initFields
	if c.hasPrivateEnv {
		vm.privEnv = &privateEnv{
			outer: vm.privEnv,
		}
		vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods)
		vm.fillPrivateNamesMap(f.privateEnvType, c.privateFields, c.privateMethods)
		vm.privEnv.instanceType = f.privateEnvType
	}
	f.privEnv = vm.privEnv
	return proto.val, f.val
}

func (c *newClass) exec(vm *vm) {
	proto, cls := c.create(vm.r.global.ObjectPrototype, vm.r.getFunctionPrototype(), vm, false)
	sp := vm.sp
	vm.stack.expand(sp + 1)
	vm.stack[sp] = proto
	vm.stack[sp+1] = cls
	vm.sp = sp + 2
	vm.pc++
}

func (c *newDerivedClass) exec(vm *vm) {
	var protoParent *Object
	var superClass *Object
	if o := vm.stack[vm.sp-1]; o != _null {
		if sc, ok := o.(*Object); !ok || sc.self.assertConstructor() == nil {
			vm.throw(vm.r.NewTypeError("Class extends value is not a constructor or null"))
			return
		} else {
			v := sc.self.getStr("prototype", nil)
			if v != _null {
				if o, ok := v.(*Object); ok {
					protoParent = o
				} else {
					vm.throw(vm.r.NewTypeError("Class extends value does not have valid prototype property"))
					return
				}
			}
			superClass = sc
		}
	} else {
		superClass = vm.r.getFunctionPrototype()
	}

	proto, cls := c.create(protoParent, superClass, vm, true)
	vm.stack[vm.sp-1] = proto
	vm.push(cls)
	vm.pc++
}

// Creates a special instance of *classFuncObject which is only used during evaluation of a class declaration
// to initialise static fields and instance private methods of another class.
type newStaticFieldInit struct {
	initFields                          *Program
	numPrivateFields, numPrivateMethods uint32
}

func (c *newStaticFieldInit) exec(vm *vm) {
	f := vm.r.newClassFunc("", 0, vm.r.getFunctionPrototype(), false)
	if c.numPrivateFields > 0 || c.numPrivateMethods > 0 {
		vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods)
	}
	f.initFields = c.initFields
	f.stash = vm.stash
	vm.push(f.val)
	vm.pc++
}

func (vm *vm) loadThis(v Value) {
	if v != nil {
		vm.push(v)
	} else {
		vm.throw(vm.r.newError(vm.r.getReferenceError(), "Must call super constructor in derived class before accessing 'this'"))
		return
	}
	vm.pc++
}

type loadThisStash uint32

func (l loadThisStash) exec(vm *vm) {
	vm.loadThis(*vm.getStashPtr(uint32(l)))
}

type loadThisStack struct{}

func (loadThisStack) exec(vm *vm) {
	vm.loadThis(vm.stack[vm.sb])
}

func (vm *vm) getStashPtr(s uint32) *Value {
	level := int(s) >> 24
	idx := s & 0x00FFFFFF
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}

	return &stash.values[idx]
}

type getThisDynamic struct{}

func (getThisDynamic) exec(vm *vm) {
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if stash.obj == nil {
			if v, exists := stash.getByName(thisBindingName); exists {
				vm.push(v)
				vm.pc++
				return
			}
		}
	}
	vm.push(vm.r.globalObject)
	vm.pc++
}

type throwConst struct {
	v interface{}
}

func (t throwConst) exec(vm *vm) {
	vm.throw(t.v)
}

type resolveThisStack struct{}

func (r resolveThisStack) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &thisRef{v: (*[]Value)(&vm.stack), idx: vm.sb})
	vm.pc++
}

type resolveThisStash uint32

func (r resolveThisStash) exec(vm *vm) {
	level := int(r) >> 24
	idx := r & 0x00FFFFFF
	stash := vm.stash
	for i := 0; i < level; i++ {
		stash = stash.outer
	}
	vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx)})
	vm.pc++
}

type resolveThisDynamic struct{}

func (resolveThisDynamic) exec(vm *vm) {
	for stash := vm.stash; stash != nil; stash = stash.outer {
		if stash.obj == nil {
			if idx, exists := stash.names[thisBindingName]; exists {
				vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx &^ maskTyp)})
				vm.pc++
				return
			}
		}
	}
	panic(vm.r.newError(vm.r.getReferenceError(), "Compiler bug: 'this' reference is not found in resolveThisDynamic"))
}

type defineComputedKey int

func (offset defineComputedKey) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-int(offset)])
	if h, ok := obj.self.(*classFuncObject); ok {
		key := toPropertyKey(vm.stack[vm.sp-1])
		h.computedKeys = append(h.computedKeys, key)
		vm.sp--
		vm.pc++
		return
	}
	panic(vm.r.NewTypeError("Compiler bug: unexpected target for defineComputedKey: %v", obj))
}

type loadComputedKey int

func (idx loadComputedKey) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sb-1])
	if h, ok := obj.self.(*classFuncObject); ok {
		vm.push(h.computedKeys[idx])
		vm.pc++
		return
	}
	panic(vm.r.NewTypeError("Compiler bug: unexpected target for loadComputedKey: %v", obj))
}

type initStaticElements struct {
	privateFields, privateMethods []unistring.String
}

func (i *initStaticElements) exec(vm *vm) {
	cls := vm.stack[vm.sp-1]
	staticInit := vm.r.toObject(vm.stack[vm.sp-3])
	vm.sp -= 2
	if h, ok := staticInit.self.(*classFuncObject); ok {
		h._putProp("prototype", cls, true, true, true) // so that 'super' resolution work
		h.privEnv = vm.privEnv
		if h.privateEnvType != nil {
			vm.privEnv.staticType = h.privateEnvType
			vm.fillPrivateNamesMap(h.privateEnvType, i.privateFields, i.privateMethods)
		}
		h._initFields(vm.r.toObject(cls))
		vm.stack[vm.sp-1] = cls

		vm.pc++
		return
	}
	panic(vm.r.NewTypeError("Compiler bug: unexpected target for initStaticElements: %v", staticInit))
}

type definePrivateMethod struct {
	idx          int
	targetOffset int
}

func (d *definePrivateMethod) getPrivateMethods(vm *vm) []Value {
	obj := vm.r.toObject(vm.stack[vm.sp-d.targetOffset])
	if cls, ok := obj.self.(*classFuncObject); ok {
		return cls.privateMethods
	} else {
		panic(vm.r.NewTypeError("Compiler bug: wrong target type for definePrivateMethod: %T", obj.self))
	}
}

func (d *definePrivateMethod) exec(vm *vm) {
	methods := d.getPrivateMethods(vm)
	methods[d.idx] = vm.stack[vm.sp-1]
	vm.sp--
	vm.pc++
}

type definePrivateGetter struct {
	definePrivateMethod
}

func (d *definePrivateGetter) exec(vm *vm) {
	methods := d.getPrivateMethods(vm)
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)
	p, _ := methods[d.idx].(*valueProperty)
	if p == nil {
		p = &valueProperty{
			accessor: true,
		}
		methods[d.idx] = p
	}
	if p.getterFunc != nil {
		vm.throw(vm.r.NewTypeError("Private getter has already been declared"))
		return
	}
	p.getterFunc = method
	vm.sp--
	vm.pc++
}

type definePrivateSetter struct {
	definePrivateMethod
}

func (d *definePrivateSetter) exec(vm *vm) {
	methods := d.getPrivateMethods(vm)
	val := vm.stack[vm.sp-1]
	method := vm.r.toObject(val)
	p, _ := methods[d.idx].(*valueProperty)
	if p == nil {
		p = &valueProperty{
			accessor: true,
		}
		methods[d.idx] = p
	}
	if p.setterFunc != nil {
		vm.throw(vm.r.NewTypeError("Private setter has already been declared"))
		return
	}
	p.setterFunc = method
	vm.sp--
	vm.pc++
}

type definePrivateProp struct {
	idx int
}

func (d *definePrivateProp) exec(vm *vm) {
	f := vm.r.toObject(vm.stack[vm.sb-1]).self.(*classFuncObject)
	obj := vm.r.toObject(vm.stack[vm.sp-2])
	penv := obj.self.getPrivateEnv(f.privateEnvType, false)
	penv.fields[d.idx] = vm.stack[vm.sp-1]
	vm.sp--
	vm.pc++
}

type getPrivatePropRes resolvedPrivateName

func (vm *vm) getPrivateType(level uint8, isStatic bool) *privateEnvType {
	e := vm.privEnv
	for i := uint8(0); i < level; i++ {
		e = e.outer
	}
	if isStatic {
		return e.staticType
	}
	return e.instanceType
}

func (g *getPrivatePropRes) _get(base Value, vm *vm) Value {
	return vm.getPrivateProp(base, g.name, vm.getPrivateType(g.level, g.isStatic), g.idx, g.isMethod)
}

func (g *getPrivatePropRes) exec(vm *vm) {
	vm.stack[vm.sp-1] = g._get(vm.stack[vm.sp-1], vm)
	vm.pc++
}

type getPrivatePropId privateId

func (g *getPrivatePropId) exec(vm *vm) {
	vm.stack[vm.sp-1] = vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod)
	vm.pc++
}

type getPrivatePropIdCallee privateId

func (g *getPrivatePropIdCallee) exec(vm *vm) {
	prop := vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*privateId)(g).string()}}
	}
	vm.push(prop)

	vm.pc++
}

func (vm *vm) getPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool) Value {
	obj := vm.r.toObject(base)
	penv := obj.self.getPrivateEnv(typ, false)
	var v Value
	if penv != nil {
		if isMethod {
			v = penv.methods[idx]
		} else {
			v = penv.fields[idx]
			if v == nil {
				panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name))
			}
		}
	} else {
		panic(vm.r.NewTypeError("Cannot read private member #%s from an object whose class did not declare it", name))
	}
	if prop, ok := v.(*valueProperty); ok {
		if prop.getterFunc == nil {
			panic(vm.r.NewTypeError("'#%s' was defined without a getter", name))
		}
		v = prop.get(obj)
	}
	return v
}

type getPrivatePropResCallee getPrivatePropRes

func (g *getPrivatePropResCallee) exec(vm *vm) {
	prop := (*getPrivatePropRes)(g)._get(vm.stack[vm.sp-1], vm)
	if prop == nil {
		prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*resolvedPrivateName)(g).string()}}
	}
	vm.push(prop)

	vm.pc++
}

func (vm *vm) setPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool, val Value) {
	obj := vm.r.toObject(base)
	penv := obj.self.getPrivateEnv(typ, false)
	if penv != nil {
		if isMethod {
			v := penv.methods[idx]
			if prop, ok := v.(*valueProperty); ok {
				if prop.setterFunc != nil {
					prop.set(base, val)
				} else {
					panic(vm.r.NewTypeError("Cannot assign to read only property '#%s'", name))
				}
			} else {
				panic(vm.r.NewTypeError("Private method '#%s' is not writable", name))
			}
		} else {
			ptr := &penv.fields[idx]
			if *ptr == nil {
				panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name))
			}
			*ptr = val
		}
	} else {
		panic(vm.r.NewTypeError("Cannot write private member #%s from an object whose class did not declare it", name))
	}
}

func (vm *vm) exceptionFromValue(x interface{}) *Exception {
	var ex *Exception
	switch x1 := x.(type) {
	case *Object:
		ex = &Exception{
			val: x1,
		}
		if er, ok := x1.self.(*errorObject); ok {
			ex.stack = er.stack
		}
	case Value:
		ex = &Exception{
			val: x1,
		}
	case *Exception:
		ex = x1
	case typeError:
		ex = &Exception{
			val: vm.r.NewTypeError(string(x1)),
		}
	case referenceError:
		ex = &Exception{
			val: vm.r.newError(vm.r.getReferenceError(), string(x1)),
		}
	case rangeError:
		ex = &Exception{
			val: vm.r.newError(vm.r.getRangeError(), string(x1)),
		}
	case syntaxError:
		ex = &Exception{
			val: vm.r.newError(vm.r.getSyntaxError(), string(x1)),
		}
	default:
		/*
			if vm.prg != nil {
				vm.prg.dumpCode(log.Printf)
			}
			log.Print("Stack: ", string(debug.Stack()))
			panic(fmt.Errorf("Panic at %d: %v", vm.pc, x))
		*/
		return nil
	}
	if ex.stack == nil {
		ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0)
	}
	return ex
}

type setPrivatePropRes resolvedPrivateName

func (p *setPrivatePropRes) _set(base Value, val Value, vm *vm) {
	vm.setPrivateProp(base, p.name, vm.getPrivateType(p.level, p.isStatic), p.idx, p.isMethod, val)
}

func (p *setPrivatePropRes) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	p._set(vm.stack[vm.sp-2], v, vm)
	vm.stack[vm.sp-2] = v
	vm.sp--
	vm.pc++
}

type setPrivatePropResP setPrivatePropRes

func (p *setPrivatePropResP) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	(*setPrivatePropRes)(p)._set(vm.stack[vm.sp-2], v, vm)
	vm.sp -= 2
	vm.pc++
}

type setPrivatePropId privateId

func (p *setPrivatePropId) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v)
	vm.stack[vm.sp-2] = v
	vm.sp--
	vm.pc++
}

type setPrivatePropIdP privateId

func (p *setPrivatePropIdP) exec(vm *vm) {
	v := vm.stack[vm.sp-1]
	vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v)
	vm.sp -= 2
	vm.pc++
}

type popPrivateEnv struct{}

func (popPrivateEnv) exec(vm *vm) {
	vm.privEnv = vm.privEnv.outer
	vm.pc++
}

type privateInRes resolvedPrivateName

func (i *privateInRes) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-1])
	pe := obj.self.getPrivateEnv(vm.getPrivateType(i.level, i.isStatic), false)
	if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) {
		vm.stack[vm.sp-1] = valueTrue
	} else {
		vm.stack[vm.sp-1] = valueFalse
	}
	vm.pc++
}

type privateInId privateId

func (i *privateInId) exec(vm *vm) {
	obj := vm.r.toObject(vm.stack[vm.sp-1])
	pe := obj.self.getPrivateEnv(i.typ, false)
	if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) {
		vm.stack[vm.sp-1] = valueTrue
	} else {
		vm.stack[vm.sp-1] = valueFalse
	}
	vm.pc++
}

type getPrivateRefRes resolvedPrivateName

func (r *getPrivateRefRes) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &privateRefRes{
		base: vm.stack[vm.sp-1].ToObject(vm.r),
		name: (*resolvedPrivateName)(r),
	})
	vm.sp--
	vm.pc++
}

type getPrivateRefId privateId

func (r *getPrivateRefId) exec(vm *vm) {
	vm.refStack = append(vm.refStack, &privateRefId{
		base: vm.stack[vm.sp-1].ToObject(vm.r),
		id:   (*privateId)(r),
	})
	vm.sp--
	vm.pc++
}

func (y *yieldMarker) exec(vm *vm) {
	vm.pc = -vm.pc // this will terminate the run loop
	vm.push(y)     // marker so the caller knows it's a yield, not a return
}

func (y *yieldMarker) String() string {
	if y == yieldEmpty {
		return "empty"
	}
	switch y.resultType {
	case resultYield:
		return "yield"
	case resultYieldRes:
		return "yieldRes"
	case resultYieldDelegate:
		return "yield*"
	case resultYieldDelegateRes:
		return "yield*Res"
	case resultAwait:
		return "await"
	default:
		return "unknown"
	}
}
