Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions vm/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type Scope struct {
Len int
Count int
Acc any
// Fast paths
Ints []int
Floats []float64
Strings []string
Anys []any
}

type groupBy = map[any][]any
Expand Down
125 changes: 97 additions & 28 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type VM struct {
debug bool
step chan struct{}
curr chan int
scopePool []Scope // Pre-allocated pool of Scope values for reuse
scopePoolIdx int // Current index into scopePool for allocation
currScope *Scope // Cached pointer to the current scope (optimization)
}

func (vm *VM) Run(program *Program, env any) (_ any, err error) {
Expand Down Expand Up @@ -76,6 +79,8 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
clearSlice(vm.Scopes)
vm.Scopes = vm.Scopes[0:0]
}
vm.scopePoolIdx = 0 // Reset pool index for reuse
vm.currScope = nil
if len(vm.Variables) < program.variables {
vm.Variables = make([]any, program.variables)
}
Expand Down Expand Up @@ -221,8 +226,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
if arg < 0 {
panic("negative jump offset is invalid")
}
scope := vm.scope()
if scope.Index >= scope.Len {
if vm.currScope.Index >= vm.currScope.Len {
vm.ip += arg
}

Expand Down Expand Up @@ -511,40 +515,45 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
vm.push(deref.Interface(a))

case OpIncrementIndex:
vm.scope().Index++
vm.currScope.Index++

case OpDecrementIndex:
scope := vm.scope()
scope.Index--
vm.currScope.Index--

case OpIncrementCount:
scope := vm.scope()
scope.Count++
vm.currScope.Count++

case OpGetIndex:
vm.push(vm.scope().Index)
vm.push(vm.currScope.Index)

case OpGetCount:
scope := vm.scope()
vm.push(scope.Count)
vm.push(vm.currScope.Count)

case OpGetLen:
scope := vm.scope()
vm.push(scope.Len)
vm.push(vm.currScope.Len)

case OpGetAcc:
vm.push(vm.scope().Acc)
vm.push(vm.currScope.Acc)

case OpSetAcc:
vm.scope().Acc = vm.pop()
vm.currScope.Acc = vm.pop()

case OpSetIndex:
scope := vm.scope()
scope.Index = vm.pop().(int)
vm.currScope.Index = vm.pop().(int)

case OpPointer:
scope := vm.scope()
vm.push(scope.Array.Index(scope.Index).Interface())
scope := vm.currScope
if scope.Ints != nil {
vm.push(scope.Ints[scope.Index])
} else if scope.Floats != nil {
vm.push(scope.Floats[scope.Index])
} else if scope.Strings != nil {
vm.push(scope.Strings[scope.Index])
} else if scope.Anys != nil {
vm.push(scope.Anys[scope.Index])
} else {
vm.push(scope.Array.Index(scope.Index).Interface())
}

case OpThrow:
panic(vm.pop().(error))
Expand All @@ -554,7 +563,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
case 1:
vm.push(make(groupBy))
case 2:
scope := vm.scope()
scope := vm.currScope
var desc bool
switch vm.pop().(string) {
case "asc":
Expand All @@ -574,21 +583,43 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
}

case OpGroupBy:
scope := vm.scope()
scope := vm.currScope
key := vm.pop()
item := scope.Array.Index(scope.Index).Interface()
var item any
if scope.Ints != nil {
item = scope.Ints[scope.Index]
} else if scope.Floats != nil {
item = scope.Floats[scope.Index]
} else if scope.Strings != nil {
item = scope.Strings[scope.Index]
} else if scope.Anys != nil {
item = scope.Anys[scope.Index]
} else {
item = scope.Array.Index(scope.Index).Interface()
}
scope.Acc.(groupBy)[key] = append(scope.Acc.(groupBy)[key], item)

case OpSortBy:
scope := vm.scope()
scope := vm.currScope
value := vm.pop()
item := scope.Array.Index(scope.Index).Interface()
var item any
if scope.Ints != nil {
item = scope.Ints[scope.Index]
} else if scope.Floats != nil {
item = scope.Floats[scope.Index]
} else if scope.Strings != nil {
item = scope.Strings[scope.Index]
} else if scope.Anys != nil {
item = scope.Anys[scope.Index]
} else {
item = scope.Array.Index(scope.Index).Interface()
}
sortable := scope.Acc.(*runtime.SortBy)
sortable.Array = append(sortable.Array, item)
sortable.Values = append(sortable.Values, value)

case OpSort:
scope := vm.scope()
scope := vm.currScope
sortable := scope.Acc.(*runtime.SortBy)
sort.Sort(sortable)
vm.memGrow(uint(scope.Len))
Expand All @@ -605,10 +636,23 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
case OpBegin:
a := vm.pop()
array := reflect.ValueOf(a)
vm.Scopes = append(vm.Scopes, &Scope{
Array: array,
Len: array.Len(),
})
s := vm.allocScope(array)
switch v := a.(type) {
case []int:
s.Ints = v
s.Len = len(v)
case []float64:
s.Floats = v
s.Len = len(v)
case []string:
s.Strings = v
s.Len = len(v)
case []any:
s.Anys = v
s.Len = len(v)
}
vm.Scopes = append(vm.Scopes, s)
vm.currScope = s

case OpAnd:
a := vm.pop()
Expand All @@ -622,6 +666,11 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {

case OpEnd:
vm.Scopes = vm.Scopes[:len(vm.Scopes)-1]
if len(vm.Scopes) > 0 {
vm.currScope = vm.Scopes[len(vm.Scopes)-1]
} else {
vm.currScope = nil
}

default:
panic(fmt.Sprintf("unknown bytecode %#x", op))
Expand Down Expand Up @@ -675,6 +724,26 @@ func (vm *VM) scope() *Scope {
return vm.Scopes[len(vm.Scopes)-1]
}

// allocScope returns a pointer to a Scope from the pool, growing the pool if needed.
// The returned Scope has Array and Len set; Index, Count, and Acc are zeroed.
func (vm *VM) allocScope(array reflect.Value) *Scope {
if vm.scopePoolIdx >= len(vm.scopePool) {
vm.scopePool = append(vm.scopePool, Scope{})
}
s := &vm.scopePool[vm.scopePoolIdx]
vm.scopePoolIdx++
s.Array = array
s.Len = array.Len()
s.Index = 0
s.Count = 0
s.Acc = nil
s.Ints = nil
s.Floats = nil
s.Strings = nil
s.Anys = nil
return s
}

// getArgsForFunc lazily initializes the buffer the first time it is called for
// a given program (thus, it also needs "program" to run). It will
// take "needed" elements from the buffer and populate them with vm.pop() in
Expand Down
Loading