Skip to content
Open
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
22 changes: 22 additions & 0 deletions vm/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ type Scope struct {
Len int
Count int
Acc any
// Fast paths
Ints []int
Floats []float64
Strings []string
Anys []any
}

// Item returns the current element from the scope using fast paths when available.
func (s *Scope) Item() any {
if s.Ints != nil {
return s.Ints[s.Index]
}
if s.Floats != nil {
return s.Floats[s.Index]
}
if s.Strings != nil {
return s.Strings[s.Index]
}
if s.Anys != nil {
return s.Anys[s.Index]
}
return s.Array.Index(s.Index).Interface()
}

type groupBy = map[any][]any
Expand Down
100 changes: 69 additions & 31 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; grows as needed but never shrinks
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,34 @@ 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())
vm.push(vm.currScope.Item())

case OpThrow:
panic(vm.pop().(error))
Expand All @@ -554,7 +552,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
order, ok := vm.pop().(string)
if !ok {
Expand All @@ -578,21 +576,19 @@ 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()
scope.Acc.(groupBy)[key] = append(scope.Acc.(groupBy)[key], item)
scope.Acc.(groupBy)[key] = append(scope.Acc.(groupBy)[key], scope.Item())

case OpSortBy:
scope := vm.scope()
scope := vm.currScope
value := vm.pop()
item := scope.Array.Index(scope.Index).Interface()
sortable := scope.Acc.(*runtime.SortBy)
sortable.Array = append(sortable.Array, item)
sortable.Array = append(sortable.Array, scope.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 @@ -608,11 +604,26 @@ 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()
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)
default:
s.Array = reflect.ValueOf(a)
s.Len = s.Array.Len()
}
vm.Scopes = append(vm.Scopes, s)
vm.currScope = s

case OpAnd:
a := vm.pop()
Expand All @@ -626,6 +637,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 @@ -679,6 +695,28 @@ 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.
// Callers must set Len and exactly one of: Ints, Floats, Strings, Anys, or Array.
func (vm *VM) allocScope() *Scope {
if vm.scopePoolIdx >= len(vm.scopePool) {
vm.scopePool = append(vm.scopePool, Scope{})
}
s := &vm.scopePool[vm.scopePoolIdx]
vm.scopePoolIdx++
// Reset iteration state
s.Index = 0
s.Count = 0
s.Acc = nil
// Clear typed slice pointers to avoid stale fast-path matches
s.Ints = nil
s.Floats = nil
s.Strings = nil
s.Anys = nil
// Clear Array to release reference for GC (only matters for fallback path)
s.Array = reflect.Value{}
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