225 lines
4.5 KiB
Go
225 lines
4.5 KiB
Go
package vm
|
|
|
|
import "aego/core/handle"
|
|
|
|
type coTag struct{}
|
|
|
|
type CoHandle = handle.Handle[coTag]
|
|
|
|
type CoState uint8
|
|
|
|
const (
|
|
CoRunning CoState = iota
|
|
CoSuspended
|
|
CoDone
|
|
CoCanceled
|
|
CoFaulted
|
|
)
|
|
|
|
type WaitKind uint8
|
|
|
|
const (
|
|
WaitNext WaitKind = iota
|
|
WaitTime
|
|
WaitFrames
|
|
WaitFixed
|
|
WaitAwait
|
|
WaitUnscaled
|
|
WaitHandle
|
|
)
|
|
|
|
type CoHooks interface {
|
|
Yielded(h CoHandle, w WaitKind, arg Value)
|
|
Ended(h CoHandle, st CoState)
|
|
}
|
|
|
|
type coroutine struct {
|
|
gen uint32
|
|
live bool
|
|
canceled bool
|
|
fn *Func
|
|
pc int
|
|
regs []Value
|
|
owner InstanceID
|
|
state CoState
|
|
}
|
|
|
|
func CoValue(h CoHandle) Value {
|
|
if h.IsZero() {
|
|
return Value{}
|
|
}
|
|
return Value{A: uint64(h.Gen)<<32 | uint64(h.Index)}
|
|
}
|
|
|
|
func CoOf(v Value) CoHandle {
|
|
return CoHandle{Index: uint32(v.A), Gen: uint32(v.A >> 32)}
|
|
}
|
|
|
|
func (m *Machine) co(h CoHandle) *coroutine {
|
|
if h.Gen == 0 || int(h.Index) >= len(m.cos) {
|
|
return nil
|
|
}
|
|
c := &m.cos[h.Index]
|
|
if !c.live || c.gen != h.Gen {
|
|
return nil
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (m *Machine) newCo(f *Func, owner InstanceID) CoHandle {
|
|
var idx uint32
|
|
if n := len(m.cofree); n > 0 {
|
|
idx = m.cofree[n-1]
|
|
m.cofree = m.cofree[:n-1]
|
|
} else {
|
|
m.cos = append(m.cos, coroutine{})
|
|
idx = uint32(len(m.cos) - 1)
|
|
}
|
|
c := &m.cos[idx]
|
|
c.gen++
|
|
if c.gen == 0 {
|
|
c.gen = 1
|
|
}
|
|
c.live, c.canceled, c.fn, c.pc, c.owner, c.state = true, false, f, 0, owner, CoRunning
|
|
if cap(c.regs) < f.Regs {
|
|
c.regs = make([]Value, f.Regs)
|
|
} else {
|
|
c.regs = c.regs[:f.Regs]
|
|
}
|
|
m.colive++
|
|
return CoHandle{Index: idx, Gen: c.gen}
|
|
}
|
|
|
|
func (m *Machine) endCo(idx uint32, st CoState) {
|
|
c := &m.cos[idx]
|
|
h := CoHandle{Index: idx, Gen: c.gen}
|
|
c.live, c.fn, c.state, c.canceled = false, nil, st, false
|
|
clear(c.regs)
|
|
m.cofree = append(m.cofree, idx)
|
|
m.colive--
|
|
if m.Co != nil {
|
|
m.Co.Ended(h, st)
|
|
}
|
|
}
|
|
|
|
func (m *Machine) yieldCo(idx uint32, ret, pc int, regs []Value, w WaitKind, arg Value) {
|
|
c := &m.cos[idx]
|
|
h := CoHandle{Index: idx, Gen: c.gen}
|
|
if c.canceled {
|
|
m.endCo(idx, CoCanceled)
|
|
} else {
|
|
c.pc = pc
|
|
copy(c.regs, regs)
|
|
c.state = CoSuspended
|
|
if m.Co != nil {
|
|
m.Co.Yielded(h, w, arg)
|
|
}
|
|
}
|
|
if ret >= 0 {
|
|
m.stack[ret] = CoValue(h)
|
|
}
|
|
}
|
|
|
|
func (m *Machine) finishCo(idx uint32, ret int) {
|
|
c := &m.cos[idx]
|
|
h := CoHandle{Index: idx, Gen: c.gen}
|
|
st := CoDone
|
|
if c.canceled {
|
|
st = CoCanceled
|
|
}
|
|
m.endCo(idx, st)
|
|
if ret >= 0 {
|
|
m.stack[ret] = CoValue(h)
|
|
}
|
|
}
|
|
|
|
func (m *Machine) Start(f *Func, self []Value, args []Value, owner InstanceID) (CoHandle, *Fault) {
|
|
if len(m.frames) != 0 {
|
|
return CoHandle{}, &Fault{Code: CodeLink, Message: "the machine is already running a call"}
|
|
}
|
|
if f == nil || !f.Coroutine || len(args) != f.Params {
|
|
return CoHandle{}, &Fault{Code: CodeLink, Message: "function is not a coroutine or the argument count does not match"}
|
|
}
|
|
if f.Regs > len(m.stack) {
|
|
m.grow(f.Regs)
|
|
}
|
|
copy(m.stack, args)
|
|
clear(m.stack[len(args):f.Regs])
|
|
h := m.newCo(f, owner)
|
|
m.left = m.opt.Budget
|
|
m.frames = append(m.frames, frame{f: f, ret: -1, self: self, owner: owner, co: h.Index + 1})
|
|
m.stats.Calls++
|
|
if fl := m.finish(); fl != nil {
|
|
return CoHandle{}, fl
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
func (m *Machine) Resume(h CoHandle, self []Value) (bool, *Fault) {
|
|
if len(m.frames) != 0 {
|
|
return false, &Fault{Code: CodeLink, Message: "the machine is already running a call"}
|
|
}
|
|
c := m.co(h)
|
|
if c == nil || c.state != CoSuspended {
|
|
return true, nil
|
|
}
|
|
f := c.fn
|
|
if f.Regs > len(m.stack) {
|
|
m.grow(f.Regs)
|
|
}
|
|
copy(m.stack, c.regs[:f.Regs])
|
|
c.state = CoRunning
|
|
m.left = m.opt.Budget
|
|
m.frames = append(m.frames, frame{f: f, pc: c.pc, ret: -1, self: self, owner: c.owner, co: h.Index + 1})
|
|
m.stats.Calls++
|
|
if fl := m.finish(); fl != nil {
|
|
return true, fl
|
|
}
|
|
c = m.co(h)
|
|
return c == nil || c.state != CoSuspended, nil
|
|
}
|
|
|
|
func (m *Machine) Cancel(h CoHandle) {
|
|
c := m.co(h)
|
|
switch {
|
|
case c == nil:
|
|
case c.state == CoRunning:
|
|
c.canceled = true
|
|
default:
|
|
m.endCo(h.Index, CoCanceled)
|
|
}
|
|
}
|
|
|
|
func (m *Machine) CoState(h CoHandle) (CoState, bool) {
|
|
c := m.co(h)
|
|
if c == nil {
|
|
return CoDone, false
|
|
}
|
|
if c.canceled {
|
|
return CoCanceled, true
|
|
}
|
|
return c.state, true
|
|
}
|
|
|
|
func (m *Machine) CoOwner(h CoHandle) (InstanceID, bool) {
|
|
c := m.co(h)
|
|
if c == nil {
|
|
return NoInstance, false
|
|
}
|
|
return c.owner, true
|
|
}
|
|
|
|
func (m *Machine) CoRunning(v Value) bool {
|
|
c := m.co(CoOf(v))
|
|
return c != nil && !c.canceled
|
|
}
|
|
|
|
func (m *Machine) unwind(floor int) {
|
|
for i := len(m.frames) - 1; i >= floor; i-- {
|
|
if c := m.frames[i].co; c != 0 {
|
|
m.endCo(c-1, CoFaulted)
|
|
}
|
|
}
|
|
m.frames = m.frames[:floor]
|
|
}
|