1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-13 19:52:52 +02:00

Refactor loop compilation; introduce LoopSortCompiler and LoopCollectCompiler to unify sort and collect logic, restructure loop methods, and improve clarity

This commit is contained in:
Tim Voronov
2025-06-19 16:54:13 -04:00
parent f3e660ed5e
commit 87ace590bf
12 changed files with 253 additions and 236 deletions

View File

@@ -10,12 +10,13 @@ type CompilerContext struct {
Loops *core.LoopTable Loops *core.LoopTable
CatchTable *core.CatchStack CatchTable *core.CatchStack
ExprCompiler *ExprCompiler ExprCompiler *ExprCompiler
LiteralCompiler *LiteralCompiler LiteralCompiler *LiteralCompiler
StmtCompiler *StmtCompiler StmtCompiler *StmtCompiler
LoopCompiler *LoopCompiler LoopCompiler *LoopCompiler
CollectCompiler *CollectCompiler LoopSortCompiler *LoopSortCompiler
WaitCompiler *WaitCompiler LoopCollectCompiler *LoopCollectCompiler
WaitCompiler *WaitCompiler
} }
// NewCompilerContext initializes a new CompilerContext with default values. // NewCompilerContext initializes a new CompilerContext with default values.
@@ -34,7 +35,8 @@ func NewCompilerContext() *CompilerContext {
ctx.LiteralCompiler = NewLiteralCompiler(ctx) ctx.LiteralCompiler = NewLiteralCompiler(ctx)
ctx.StmtCompiler = NewStmtCompiler(ctx) ctx.StmtCompiler = NewStmtCompiler(ctx)
ctx.LoopCompiler = NewLoopCompiler(ctx) ctx.LoopCompiler = NewLoopCompiler(ctx)
ctx.CollectCompiler = NewCollectCompiler(ctx) ctx.LoopSortCompiler = NewLoopSortCompiler(ctx)
ctx.LoopCollectCompiler = NewCollectCompiler(ctx)
ctx.WaitCompiler = NewWaitCompiler(ctx) ctx.WaitCompiler = NewWaitCompiler(ctx)
return ctx return ctx

View File

@@ -23,6 +23,10 @@ func (e *Emitter) Size() int {
return len(e.instructions) return len(e.instructions)
} }
func (e *Emitter) Position() int {
return len(e.instructions) - 1
}
// PatchSwapAB modifies an instruction at the given position to swap operands and update its operation and destination. // PatchSwapAB modifies an instruction at the given position to swap operands and update its operation and destination.
func (e *Emitter) PatchSwapAB(pos int, op vm.Opcode, dst, src1 vm.Operand) { func (e *Emitter) PatchSwapAB(pos int, op vm.Opcode, dst, src1 vm.Operand) {
e.instructions[pos] = vm.Instruction{ e.instructions[pos] = vm.Instruction{

View File

@@ -0,0 +1,8 @@
package core
import "github.com/MontFerret/ferret/pkg/vm"
type KV struct {
Key vm.Operand
Value vm.Operand
}

View File

@@ -64,11 +64,22 @@ func (l *Loop) DeclareValueVar(name string, st *SymbolTable) {
} }
func (l *Loop) EmitInitialization(alloc *RegisterAllocator, emitter *Emitter) { func (l *Loop) EmitInitialization(alloc *RegisterAllocator, emitter *Emitter) {
if l.Allocate {
emitter.EmitAb(vm.OpDataSet, l.Result, l.Distinct)
l.ResultPos = emitter.Position()
}
if l.Iterator == vm.NoopOperand { if l.Iterator == vm.NoopOperand {
l.Iterator = alloc.Allocate(Temp) l.Iterator = alloc.Allocate(Temp)
} }
emitter.EmitIter(l.Iterator, l.Src) emitter.EmitIter(l.Iterator, l.Src)
// JumpPlaceholder is a placeholder for the exit jump position
l.Jump = emitter.EmitJumpc(vm.OpIterNext, JumpPlaceholder, l.Iterator)
l.BindValueVar(emitter)
l.BindKeyVar(emitter)
} }
func (l *Loop) EmitNext(emitter *Emitter) { func (l *Loop) EmitNext(emitter *Emitter) {

View File

@@ -2,9 +2,8 @@ package core
import ( import (
"fmt" "fmt"
"strings"
"github.com/MontFerret/ferret/pkg/vm" "github.com/MontFerret/ferret/pkg/vm"
"strings"
) )
type LoopTable struct { type LoopTable struct {
@@ -19,6 +18,30 @@ func NewLoopTable(registers *RegisterAllocator) *LoopTable {
} }
} }
func (lt *LoopTable) Create(loopType LoopType, kind LoopKind, distinct bool) *Loop {
parent := lt.Current()
allocate := parent == nil || parent.Type != PassThroughLoop
result := vm.NoopOperand
if allocate && loopType != TemporalLoop {
result = lt.registers.Allocate(Result)
} else if parent != nil {
result = parent.Result
}
loop := &Loop{
Type: loopType,
Kind: kind,
Distinct: distinct,
Result: result,
Allocate: allocate,
}
lt.Push(loop)
return loop
}
func (lt *LoopTable) Push(loop *Loop) { func (lt *LoopTable) Push(loop *Loop) {
lt.stack = append(lt.stack, loop) lt.stack = append(lt.stack, loop)
} }
@@ -43,26 +66,6 @@ func (lt *LoopTable) Depth() int {
return len(lt.stack) return len(lt.stack)
} }
func (lt *LoopTable) NewLoop(loopType LoopType, kind LoopKind, distinct bool) *Loop {
parent := lt.Current()
allocate := parent == nil || parent.Type != PassThroughLoop
result := vm.NoopOperand
if allocate && loopType != TemporalLoop {
result = lt.registers.Allocate(Result)
} else if parent != nil {
result = parent.Result
}
return &Loop{
Type: loopType,
Kind: kind,
Distinct: distinct,
Result: result,
Allocate: allocate,
}
}
func (lt *LoopTable) DebugView() string { func (lt *LoopTable) DebugView() string {
var out strings.Builder var out strings.Builder
for i, loop := range lt.stack { for i, loop := range lt.stack {

View File

@@ -456,7 +456,7 @@ func (ec *ExprCompiler) CompileArgumentList(ctx fql.IArgumentListContext) core.R
// TODO: Figure out how to remove OpMove and use Registers returned from each expression // TODO: Figure out how to remove OpMove and use Registers returned from each expression
// The reason we move is that the argument list must be a contiguous sequence of registers // The reason we move is that the argument list must be a contiguous sequence of registers
// Otherwise, we cannot initialize neither a list nor an object literal with arguments // Otherwise, we cannot compileInitialization neither a list nor an object literal with arguments
ec.ctx.Emitter.EmitMove(seq[i], srcReg) ec.ctx.Emitter.EmitMove(seq[i], srcReg)
// Free source register if temporary // Free source register if temporary

View File

@@ -18,6 +18,23 @@ func NewLoopCompiler(ctx *CompilerContext) *LoopCompiler {
} }
func (lc *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand { func (lc *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand {
returnRuleCtx := lc.compileInitialization(ctx)
// body
if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 {
for _, b := range body {
if c := b.ForExpressionStatement(); c != nil {
lc.compileForExpressionStatement(c)
} else if c := b.ForExpressionClause(); c != nil {
lc.compileForExpressionClause(c)
}
}
}
return lc.compileFinalization(returnRuleCtx)
}
func (lc *LoopCompiler) compileInitialization(ctx fql.IForExpressionContext) antlr.RuleContext {
var distinct bool var distinct bool
var returnRuleCtx antlr.RuleContext var returnRuleCtx antlr.RuleContext
var loopType core.LoopType var loopType core.LoopType
@@ -32,12 +49,11 @@ func (lc *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand {
loopType = core.PassThroughLoop loopType = core.PassThroughLoop
} }
loop := lc.ctx.Loops.NewLoop(loopType, core.ForLoop, distinct) loop := lc.ctx.Loops.Create(loopType, core.ForLoop, distinct)
lc.ctx.Symbols.EnterScope() lc.ctx.Symbols.EnterScope()
lc.ctx.Loops.Push(loop)
if loop.Kind == core.ForLoop { if loop.Kind == core.ForLoop {
loop.Src = lc.CompileForExpressionSource(ctx.ForExpressionSource()) loop.Src = lc.compileForExpressionSource(ctx.ForExpressionSource())
if val := ctx.GetValueVariable(); val != nil { if val := ctx.GetValueVariable(); val != nil {
loop.DeclareValueVar(val.GetText(), lc.ctx.Symbols) loop.DeclareValueVar(val.GetText(), lc.ctx.Symbols)
@@ -49,42 +65,36 @@ func (lc *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand {
} else { } else {
} }
lc.EmitLoopBegin(loop) loop.EmitInitialization(lc.ctx.Registers, lc.ctx.Emitter)
// body return returnRuleCtx
if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 { }
for _, b := range body {
if c := b.ForExpressionStatement(); c != nil {
lc.CompileForExpressionStatement(c)
} else if c := b.ForExpressionClause(); c != nil {
lc.CompileForExpressionClause(c)
}
}
}
loop = lc.ctx.Loops.Current() func (lc *LoopCompiler) compileFinalization(ctx antlr.RuleContext) vm.Operand {
loop := lc.ctx.Loops.Current()
// RETURN // RETURN
if loop.Type != core.PassThroughLoop { if loop.Type != core.PassThroughLoop {
c := returnRuleCtx.(*fql.ReturnExpressionContext) c := ctx.(*fql.ReturnExpressionContext)
expReg := lc.ctx.ExprCompiler.Compile(c.Expression()) expReg := lc.ctx.ExprCompiler.Compile(c.Expression())
lc.ctx.Emitter.EmitAB(vm.OpPush, loop.Result, expReg) lc.ctx.Emitter.EmitAB(vm.OpPush, loop.Result, expReg)
} else if returnRuleCtx != nil { } else if ctx != nil {
if c, ok := returnRuleCtx.(*fql.ForExpressionContext); ok { if c, ok := ctx.(*fql.ForExpressionContext); ok {
lc.Compile(c) lc.Compile(c)
} }
} }
res := lc.EmitLoopEnd(loop) loop.EmitFinalization(lc.ctx.Emitter)
lc.ctx.Symbols.ExitScope() lc.ctx.Symbols.ExitScope()
lc.ctx.Loops.Pop() lc.ctx.Loops.Pop()
return res // TODO: Free operands
return loop.Result
} }
func (lc *LoopCompiler) CompileForExpressionSource(ctx fql.IForExpressionSourceContext) vm.Operand { func (lc *LoopCompiler) compileForExpressionSource(ctx fql.IForExpressionSourceContext) vm.Operand {
if c := ctx.FunctionCallExpression(); c != nil { if c := ctx.FunctionCallExpression(); c != nil {
return lc.ctx.ExprCompiler.CompileFunctionCallExpression(c) return lc.ctx.ExprCompiler.CompileFunctionCallExpression(c)
} }
@@ -116,40 +126,40 @@ func (lc *LoopCompiler) CompileForExpressionSource(ctx fql.IForExpressionSourceC
panic(runtime.Error(core.ErrUnexpectedToken, ctx.GetText())) panic(runtime.Error(core.ErrUnexpectedToken, ctx.GetText()))
} }
func (lc *LoopCompiler) CompileForExpressionStatement(ctx fql.IForExpressionStatementContext) { func (lc *LoopCompiler) compileForExpressionStatement(ctx fql.IForExpressionStatementContext) {
if c := ctx.VariableDeclaration(); c != nil { if c := ctx.VariableDeclaration(); c != nil {
_ = lc.ctx.StmtCompiler.CompileVariableDeclaration(c) _ = lc.ctx.StmtCompiler.CompileVariableDeclaration(c)
} else if c := ctx.FunctionCallExpression(); c != nil { } else if c := ctx.FunctionCallExpression(); c != nil {
_ = lc.ctx.ExprCompiler.CompileFunctionCallExpression(c) _ = lc.ctx.ExprCompiler.CompileFunctionCallExpression(c)
// TODO: Free register if needed
} }
// TODO: Free register if needed
} }
func (lc *LoopCompiler) CompileForExpressionClause(ctx fql.IForExpressionClauseContext) { func (lc *LoopCompiler) compileForExpressionClause(ctx fql.IForExpressionClauseContext) {
if c := ctx.LimitClause(); c != nil { if c := ctx.LimitClause(); c != nil {
lc.CompileLimitClause(c) lc.compileLimitClause(c)
} else if c := ctx.FilterClause(); c != nil { } else if c := ctx.FilterClause(); c != nil {
lc.CompileFilterClause(c) lc.compileFilterClause(c)
} else if c := ctx.SortClause(); c != nil { } else if c := ctx.SortClause(); c != nil {
lc.CompileSortClause(c) lc.compileSortClause(c)
} else if c := ctx.CollectClause(); c != nil { } else if c := ctx.CollectClause(); c != nil {
lc.CompileCollectClause(c) lc.compileCollectClause(c)
} }
} }
func (lc *LoopCompiler) CompileLimitClause(ctx fql.ILimitClauseContext) { func (lc *LoopCompiler) compileLimitClause(ctx fql.ILimitClauseContext) {
clauses := ctx.AllLimitClauseValue() clauses := ctx.AllLimitClauseValue()
if len(clauses) == 1 { if len(clauses) == 1 {
lc.CompileLimit(lc.CompileLimitClauseValue(clauses[0])) lc.compileLimit(lc.compileLimitClauseValue(clauses[0]))
} else { } else {
lc.CompileOffset(lc.CompileLimitClauseValue(clauses[0])) lc.compileOffset(lc.compileLimitClauseValue(clauses[0]))
lc.CompileLimit(lc.CompileLimitClauseValue(clauses[1])) lc.compileLimit(lc.compileLimitClauseValue(clauses[1]))
} }
} }
func (lc *LoopCompiler) CompileLimitClauseValue(ctx fql.ILimitClauseValueContext) vm.Operand { func (lc *LoopCompiler) compileLimitClauseValue(ctx fql.ILimitClauseValueContext) vm.Operand {
if c := ctx.Param(); c != nil { if c := ctx.Param(); c != nil {
return lc.ctx.ExprCompiler.CompileParam(c) return lc.ctx.ExprCompiler.CompileParam(c)
} }
@@ -174,153 +184,25 @@ func (lc *LoopCompiler) CompileLimitClauseValue(ctx fql.ILimitClauseValueContext
} }
func (lc *LoopCompiler) CompileLimit(src vm.Operand) { func (lc *LoopCompiler) compileLimit(src vm.Operand) {
state := lc.ctx.Registers.Allocate(core.State) state := lc.ctx.Registers.Allocate(core.State)
lc.ctx.Emitter.EmitABx(vm.OpIterLimit, state, src, lc.ctx.Loops.Current().Jump) lc.ctx.Emitter.EmitABx(vm.OpIterLimit, state, src, lc.ctx.Loops.Current().Jump)
} }
func (lc *LoopCompiler) CompileOffset(src vm.Operand) { func (lc *LoopCompiler) compileOffset(src vm.Operand) {
state := lc.ctx.Registers.Allocate(core.State) state := lc.ctx.Registers.Allocate(core.State)
lc.ctx.Emitter.EmitABx(vm.OpIterSkip, state, src, lc.ctx.Loops.Current().Jump) lc.ctx.Emitter.EmitABx(vm.OpIterSkip, state, src, lc.ctx.Loops.Current().Jump)
} }
func (lc *LoopCompiler) CompileFilterClause(ctx fql.IFilterClauseContext) { func (lc *LoopCompiler) compileFilterClause(ctx fql.IFilterClauseContext) {
src := lc.ctx.ExprCompiler.Compile(ctx.Expression()) src := lc.ctx.ExprCompiler.Compile(ctx.Expression())
lc.ctx.Emitter.EmitJumpIfFalse(src, lc.ctx.Loops.Current().Jump) lc.ctx.Emitter.EmitJumpIfFalse(src, lc.ctx.Loops.Current().Jump)
} }
func (lc *LoopCompiler) CompileSortClause(ctx fql.ISortClauseContext) { func (lc *LoopCompiler) compileSortClause(ctx fql.ISortClauseContext) {
loop := lc.ctx.Loops.Current() lc.ctx.LoopSortCompiler.Compile(ctx)
// We collect the sorting conditions (keys
// And wrap each loop element by a KeyValuePair
// Where a key is either a single value or a list of values
// These KeyValuePairs are then added to the dataset
kvKeyReg := lc.ctx.Registers.Allocate(core.Temp)
clauses := ctx.AllSortClauseExpression()
var directions []runtime.SortDirection
isSortMany := len(clauses) > 1
if isSortMany {
clausesRegs := make([]vm.Operand, len(clauses))
directions = make([]runtime.SortDirection, len(clauses))
// We create a sequence of Registers for the clauses
// To pack them into an array
keyRegs := lc.ctx.Registers.AllocateSequence(len(clauses))
for i, clause := range clauses {
clauseReg := lc.ctx.ExprCompiler.Compile(clause.Expression())
lc.ctx.Emitter.EmitMove(keyRegs[i], clauseReg)
clausesRegs[i] = keyRegs[i]
directions[i] = sortDirection(clause.SortDirection())
// TODO: Free Registers
}
arrReg := lc.ctx.Registers.Allocate(core.Temp)
lc.ctx.Emitter.EmitAs(vm.OpList, arrReg, keyRegs)
lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, arrReg) // TODO: Free Registers
} else {
clausesReg := lc.ctx.ExprCompiler.Compile(clauses[0].Expression())
lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, clausesReg)
}
var kvValReg vm.Operand
// In case the value is not used in the loop body, and only key is used
if loop.ValueName != "" {
kvValReg = loop.Value
} else {
// If so, we need to load it from the iterator
kvValReg = lc.ctx.Registers.Allocate(core.Temp)
loop.EmitValue(kvKeyReg, lc.ctx.Emitter)
}
if isSortMany {
encoded := runtime.EncodeSortDirections(directions)
count := len(clauses)
lc.ctx.Emitter.PatchSwapAxy(loop.ResultPos, vm.OpDataSetMultiSorter, loop.Result, encoded, count)
} else {
dir := sortDirection(clauses[0].SortDirection())
lc.ctx.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetSorter, loop.Result, int(dir))
}
lc.ctx.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg)
loop.EmitFinalization(lc.ctx.Emitter)
// Replace source with the Sorter
lc.ctx.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result)
// Create a new loop
lc.EmitLoopBegin(loop)
} }
func (lc *LoopCompiler) CompileCollectClause(ctx fql.ICollectClauseContext) { func (lc *LoopCompiler) compileCollectClause(ctx fql.ICollectClauseContext) {
lc.ctx.CollectCompiler.Compile(ctx) lc.ctx.LoopCollectCompiler.Compile(ctx)
}
// EmitLoopBegin emits an instruction to get the value from the iterator
func (lc *LoopCompiler) EmitLoopBegin(loop *core.Loop) {
if loop.Allocate {
lc.ctx.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct)
loop.ResultPos = lc.ctx.Emitter.Size() - 1
}
loop.Iterator = lc.ctx.Registers.Allocate(core.State)
if loop.Kind == core.ForLoop {
lc.ctx.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Src)
// core.JumpPlaceholder is a placeholder for the exit jump position
loop.Jump = lc.ctx.Emitter.EmitJumpc(vm.OpIterNext, core.JumpPlaceholder, loop.Iterator)
if loop.Value != vm.NoopOperand {
lc.ctx.Emitter.EmitAB(vm.OpIterValue, loop.Value, loop.Iterator)
}
if loop.Key != vm.NoopOperand {
lc.ctx.Emitter.EmitAB(vm.OpIterKey, loop.Key, loop.Iterator)
}
} else {
//counterReg := lc.ctx.Registers.Allocate(Storage)
// TODO: Set JumpOffset here
}
}
func (lc *LoopCompiler) EmitLoopEnd(loop *core.Loop) vm.Operand {
lc.ctx.Emitter.EmitJump(loop.Jump - loop.JumpOffset)
// TODO: Do not allocate for pass-through Loops
dst := lc.ctx.Registers.Allocate(core.Temp)
if loop.Allocate {
// TODO: Reuse the dsReg register
lc.ctx.Emitter.EmitA(vm.OpClose, loop.Iterator)
lc.ctx.Emitter.EmitAB(vm.OpMove, dst, loop.Result)
if loop.Kind == core.ForLoop {
lc.ctx.Emitter.PatchJump(loop.Jump)
} else {
lc.ctx.Emitter.PatchJumpAB(loop.Jump)
}
} else {
if loop.Kind == core.ForLoop {
lc.ctx.Emitter.PatchJumpNext(loop.Jump)
} else {
lc.ctx.Emitter.PatchJumpNextAB(loop.Jump)
}
}
return dst
}
func (lc *LoopCompiler) loopKind(ctx *fql.ForExpressionContext) core.LoopKind {
if ctx.While() == nil {
return core.ForLoop
}
if ctx.Do() == nil {
return core.WhileLoop
}
return core.DoWhileLoop
} }

View File

@@ -9,15 +9,15 @@ import (
"github.com/MontFerret/ferret/pkg/vm" "github.com/MontFerret/ferret/pkg/vm"
) )
type CollectCompiler struct { type LoopCollectCompiler struct {
ctx *CompilerContext ctx *CompilerContext
} }
func NewCollectCompiler(ctx *CompilerContext) *CollectCompiler { func NewCollectCompiler(ctx *CompilerContext) *LoopCollectCompiler {
return &CollectCompiler{ctx: ctx} return &LoopCollectCompiler{ctx: ctx}
} }
func (cc *CollectCompiler) Compile(ctx fql.ICollectClauseContext) { func (cc *LoopCollectCompiler) Compile(ctx fql.ICollectClauseContext) {
aggregator := ctx.CollectAggregator() aggregator := ctx.CollectAggregator()
kvKeyReg, kvValReg, groupSelectors := cc.compileCollect(ctx, aggregator != nil) kvKeyReg, kvValReg, groupSelectors := cc.compileCollect(ctx, aggregator != nil)
@@ -32,7 +32,7 @@ func (cc *CollectCompiler) Compile(ctx fql.ICollectClauseContext) {
} }
} }
func (cc *CollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggregation bool) (vm.Operand, vm.Operand, []fql.ICollectSelectorContext) { func (cc *LoopCollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggregation bool) (vm.Operand, vm.Operand, []fql.ICollectSelectorContext) {
var kvKeyReg, kvValReg vm.Operand var kvKeyReg, kvValReg vm.Operand
var groupSelectors []fql.ICollectSelectorContext var groupSelectors []fql.ICollectSelectorContext
grouping := ctx.CollectGrouping() grouping := ctx.CollectGrouping()
@@ -96,13 +96,15 @@ func (cc *CollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggrega
if projectionVariableName != "" { if projectionVariableName != "" {
// Now we need to expand group variables from the dataset // Now we need to expand group variables from the dataset
loop.DeclareValueVar(projectionVariableName, cc.ctx.Symbols) loop.DeclareValueVar(projectionVariableName, cc.ctx.Symbols)
loop.EmitInitialization(cc.ctx.Registers, cc.ctx.Emitter)
cc.ctx.LoopCompiler.EmitLoopBegin(loop) //cc.ctx.LoopCompiler.EmitLoopBegin(loop)
loop.EmitKey(kvValReg, cc.ctx.Emitter) loop.EmitKey(kvValReg, cc.ctx.Emitter)
loop.BindValueVar(cc.ctx.Emitter) loop.BindValueVar(cc.ctx.Emitter)
} else { } else {
cc.ctx.LoopCompiler.EmitLoopBegin(loop) //cc.ctx.LoopCompiler.EmitLoopBegin(loop)
loop.EmitInitialization(cc.ctx.Registers, cc.ctx.Emitter)
loop.EmitKey(kvKeyReg, cc.ctx.Emitter) loop.EmitKey(kvKeyReg, cc.ctx.Emitter)
//loop.EmitValue(kvValReg, cc.ctx.Emitter) //loop.EmitValue(kvValReg, cc.ctx.Emitter)
@@ -111,7 +113,7 @@ func (cc *CollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggrega
return kvKeyReg, kvValReg, groupSelectors return kvKeyReg, kvValReg, groupSelectors
} }
func (cc *CollectCompiler) compileAggregation(c fql.ICollectAggregatorContext, isGrouped bool) { func (cc *LoopCollectCompiler) compileAggregation(c fql.ICollectAggregatorContext, isGrouped bool) {
if isGrouped { if isGrouped {
cc.compileGroupedAggregation(c) cc.compileGroupedAggregation(c)
} else { } else {
@@ -119,12 +121,12 @@ func (cc *CollectCompiler) compileAggregation(c fql.ICollectAggregatorContext, i
} }
} }
func (cc *CollectCompiler) compileGroupedAggregation(c fql.ICollectAggregatorContext) { func (cc *LoopCollectCompiler) compileGroupedAggregation(c fql.ICollectAggregatorContext) {
parentLoop := cc.ctx.Loops.Current() parentLoop := cc.ctx.Loops.Current()
// We need to allocate a temporary accumulators to store aggregation results // We need to allocate a temporary accumulators to store aggregation results
selectors := c.AllCollectAggregateSelector() selectors := c.AllCollectAggregateSelector()
accums := cc.initAggrAccumulators(selectors) accums := cc.initAggrAccumulators(selectors)
loop := cc.ctx.Loops.NewLoop(core.TemporalLoop, core.ForLoop, false) loop := cc.ctx.Loops.Create(core.TemporalLoop, core.ForLoop, false)
loop.Src = cc.ctx.Registers.Allocate(core.Temp) loop.Src = cc.ctx.Registers.Allocate(core.Temp)
// Now we iterate over the grouped items // Now we iterate over the grouped items
@@ -163,7 +165,7 @@ func (cc *CollectCompiler) compileGroupedAggregation(c fql.ICollectAggregatorCon
// cc.ctx.Registers.Free(aggrIterVal) // cc.ctx.Registers.Free(aggrIterVal)
} }
func (cc *CollectCompiler) compileGlobalAggregation(c fql.ICollectAggregatorContext) { func (cc *LoopCollectCompiler) compileGlobalAggregation(c fql.ICollectAggregatorContext) {
parentLoop := cc.ctx.Loops.Current() parentLoop := cc.ctx.Loops.Current()
loop := parentLoop loop := parentLoop
// we create a custom collector for aggregators // we create a custom collector for aggregators
@@ -171,9 +173,6 @@ func (cc *CollectCompiler) compileGlobalAggregation(c fql.ICollectAggregatorCont
// Nested scope for aggregators // Nested scope for aggregators
cc.ctx.Symbols.EnterScope() cc.ctx.Symbols.EnterScope()
loop.DeclareValueVar(loop.ValueName, cc.ctx.Symbols)
loop.BindValueVar(cc.ctx.Emitter)
// Now we add value selectors to the accumulators // Now we add value selectors to the accumulators
selectors := c.AllCollectAggregateSelector() selectors := c.AllCollectAggregateSelector()
cc.collectAggregationFuncArgs(selectors, func(i int, resultReg vm.Operand) { cc.collectAggregationFuncArgs(selectors, func(i int, resultReg vm.Operand) {
@@ -183,32 +182,26 @@ func (cc *CollectCompiler) compileGlobalAggregation(c fql.ICollectAggregatorCont
cc.ctx.Registers.Free(aggrKeyReg) cc.ctx.Registers.Free(aggrKeyReg)
}) })
// Now we can iterate over the grouped items
loop.EmitFinalization(cc.ctx.Emitter) loop.EmitFinalization(cc.ctx.Emitter)
// Now close the aggregators scope
cc.ctx.Symbols.ExitScope() cc.ctx.Symbols.ExitScope()
parentLoop.ValueName = "" parentLoop.ValueName = ""
parentLoop.KeyName = "" parentLoop.KeyName = ""
// Since we are in the middle of the loop, we need to patch the loop result // Now we can iterate over the grouped items
// Now we just create a range with 1 item to push the aggregated values to the dataset
// Replace source with sorted array
zero := loadConstant(cc.ctx, runtime.Int(0)) zero := loadConstant(cc.ctx, runtime.Int(0))
one := loadConstant(cc.ctx, runtime.Int(1)) one := loadConstant(cc.ctx, runtime.Int(1))
// We move the aggregator to a temporary register to access it later from the new loop
aggregator := cc.ctx.Registers.Allocate(core.Temp) aggregator := cc.ctx.Registers.Allocate(core.Temp)
cc.ctx.Emitter.EmitAB(vm.OpMove, aggregator, loop.Result) cc.ctx.Emitter.EmitAB(vm.OpMove, aggregator, loop.Result)
cc.ctx.Symbols.ExitScope()
// Create new loop with 1 iteration only
cc.ctx.Symbols.EnterScope() cc.ctx.Symbols.EnterScope()
// Create new for loop
cc.ctx.Emitter.EmitABC(vm.OpRange, loop.Src, zero, one) cc.ctx.Emitter.EmitABC(vm.OpRange, loop.Src, zero, one)
cc.ctx.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) cc.ctx.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct)
loop.EmitInitialization(cc.ctx.Registers, cc.ctx.Emitter)
// In case of non-collected aggregators, we just iterate over the grouped items // We just need to take the grouped values and call aggregation functions using them as args
// Retrieve the grouped values by key, execute aggregation funcs and assign variable names to the results
var key vm.Operand var key vm.Operand
var value vm.Operand var value vm.Operand
cc.compileAggregationFuncCall(selectors, func(i int, selectorVarName string) core.RegisterSequence { cc.compileAggregationFuncCall(selectors, func(i int, selectorVarName string) core.RegisterSequence {
@@ -229,7 +222,7 @@ func (cc *CollectCompiler) compileGlobalAggregation(c fql.ICollectAggregatorCont
// cc.ctx.Registers.Free(aggrIterVal) // cc.ctx.Registers.Free(aggrIterVal)
} }
func (cc *CollectCompiler) collectAggregationFuncArgs(selectors []fql.ICollectAggregateSelectorContext, collector func(int, vm.Operand)) { func (cc *LoopCollectCompiler) collectAggregationFuncArgs(selectors []fql.ICollectAggregateSelectorContext, collector func(int, vm.Operand)) {
for i := 0; i < len(selectors); i++ { for i := 0; i < len(selectors); i++ {
selector := selectors[i] selector := selectors[i]
fcx := selector.FunctionCallExpression() fcx := selector.FunctionCallExpression()
@@ -251,7 +244,7 @@ func (cc *CollectCompiler) collectAggregationFuncArgs(selectors []fql.ICollectAg
} }
} }
func (cc *CollectCompiler) compileAggregationFuncCall(selectors []fql.ICollectAggregateSelectorContext, provider func(int, string) core.RegisterSequence, cleanup func(int)) { func (cc *LoopCollectCompiler) compileAggregationFuncCall(selectors []fql.ICollectAggregateSelectorContext, provider func(int, string) core.RegisterSequence, cleanup func(int)) {
for i, selector := range selectors { for i, selector := range selectors {
fcx := selector.FunctionCallExpression() fcx := selector.FunctionCallExpression()
// We won't make any checks here, as we already did it before // We won't make any checks here, as we already did it before
@@ -269,7 +262,7 @@ func (cc *CollectCompiler) compileAggregationFuncCall(selectors []fql.ICollectAg
} }
} }
func (cc *CollectCompiler) compileGrouping(ctx fql.ICollectGroupingContext) (vm.Operand, []fql.ICollectSelectorContext) { func (cc *LoopCollectCompiler) compileGrouping(ctx fql.ICollectGroupingContext) (vm.Operand, []fql.ICollectSelectorContext) {
selectors := ctx.AllCollectSelector() selectors := ctx.AllCollectSelector()
if len(selectors) == 0 { if len(selectors) == 0 {
@@ -300,7 +293,7 @@ func (cc *CollectCompiler) compileGrouping(ctx fql.ICollectGroupingContext) (vm.
return kvKeyReg, selectors return kvKeyReg, selectors
} }
func (cc *CollectCompiler) compileGroupSelectorVariables(selectors []fql.ICollectSelectorContext, kvKeyReg, kvValReg vm.Operand, isAggregation bool) { func (cc *LoopCollectCompiler) compileGroupSelectorVariables(selectors []fql.ICollectSelectorContext, kvKeyReg, kvValReg vm.Operand, isAggregation bool) {
if len(selectors) > 1 { if len(selectors) > 1 {
variables := make([]vm.Operand, len(selectors)) variables := make([]vm.Operand, len(selectors))
@@ -336,7 +329,7 @@ func (cc *CollectCompiler) compileGroupSelectorVariables(selectors []fql.ICollec
} }
} }
func (cc *CollectCompiler) compileDefaultGroupProjection(loop *core.Loop, kvValReg vm.Operand, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string { func (cc *LoopCollectCompiler) compileDefaultGroupProjection(loop *core.Loop, kvValReg vm.Operand, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string {
if keeper == nil { if keeper == nil {
seq := cc.ctx.Registers.AllocateSequence(2) // Key and Value for Map seq := cc.ctx.Registers.AllocateSequence(2) // Key and Value for Map
@@ -371,7 +364,7 @@ func (cc *CollectCompiler) compileDefaultGroupProjection(loop *core.Loop, kvValR
return identifier.GetText() return identifier.GetText()
} }
func (cc *CollectCompiler) compileCustomGroupProjection(_ *core.Loop, kvValReg vm.Operand, selector fql.ICollectSelectorContext) string { func (cc *LoopCollectCompiler) compileCustomGroupProjection(_ *core.Loop, kvValReg vm.Operand, selector fql.ICollectSelectorContext) string {
selectorReg := cc.ctx.ExprCompiler.Compile(selector.Expression()) selectorReg := cc.ctx.ExprCompiler.Compile(selector.Expression())
cc.ctx.Emitter.EmitMove(kvValReg, selectorReg) cc.ctx.Emitter.EmitMove(kvValReg, selectorReg)
cc.ctx.Registers.Free(selectorReg) cc.ctx.Registers.Free(selectorReg)
@@ -379,7 +372,7 @@ func (cc *CollectCompiler) compileCustomGroupProjection(_ *core.Loop, kvValReg v
return selector.Identifier().GetText() return selector.Identifier().GetText()
} }
func (cc *CollectCompiler) selectGroupKey(isAggregation bool, kvKeyReg, kvValReg vm.Operand) vm.Operand { func (cc *LoopCollectCompiler) selectGroupKey(isAggregation bool, kvKeyReg, kvValReg vm.Operand) vm.Operand {
if isAggregation { if isAggregation {
return kvKeyReg return kvKeyReg
} }
@@ -387,7 +380,7 @@ func (cc *CollectCompiler) selectGroupKey(isAggregation bool, kvKeyReg, kvValReg
return kvValReg return kvValReg
} }
func (cc *CollectCompiler) initAggrAccumulators(selectors []fql.ICollectAggregateSelectorContext) []vm.Operand { func (cc *LoopCollectCompiler) initAggrAccumulators(selectors []fql.ICollectAggregateSelectorContext) []vm.Operand {
accums := make([]vm.Operand, len(selectors)) accums := make([]vm.Operand, len(selectors))
// First of all, we allocate registers for accumulators // First of all, we allocate registers for accumulators
@@ -405,7 +398,7 @@ func (cc *CollectCompiler) initAggrAccumulators(selectors []fql.ICollectAggregat
return accums return accums
} }
func (cc *CollectCompiler) emitPushToAggrAccumulators(accums []vm.Operand, selectors []fql.ICollectAggregateSelectorContext, loop *core.Loop) { func (cc *LoopCollectCompiler) emitPushToAggrAccumulators(accums []vm.Operand, selectors []fql.ICollectAggregateSelectorContext, loop *core.Loop) {
for i, selector := range selectors { for i, selector := range selectors {
fcx := selector.FunctionCallExpression() fcx := selector.FunctionCallExpression()
args := cc.ctx.ExprCompiler.CompileArgumentList(fcx.FunctionCall().ArgumentList()) args := cc.ctx.ExprCompiler.CompileArgumentList(fcx.FunctionCall().ArgumentList())

View File

@@ -0,0 +1,82 @@
package internal
import (
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
)
type LoopSortCompiler struct {
ctx *CompilerContext
}
func NewLoopSortCompiler(ctx *CompilerContext) *LoopSortCompiler {
return &LoopSortCompiler{ctx: ctx}
}
func (lc *LoopSortCompiler) Compile(ctx fql.ISortClauseContext) {
loop := lc.ctx.Loops.Current()
// We collect the sorting conditions (keys
// And wrap each loop element by a KeyValuePair
// Where a key is either a single value or a list of values
// These KeyValuePairs are then added to the dataset
kvKeyReg := lc.ctx.Registers.Allocate(core.Temp)
clauses := ctx.AllSortClauseExpression()
var directions []runtime.SortDirection
isSortMany := len(clauses) > 1
if isSortMany {
clausesRegs := make([]vm.Operand, len(clauses))
directions = make([]runtime.SortDirection, len(clauses))
// We create a sequence of Registers for the clauses
// To pack them into an array
keyRegs := lc.ctx.Registers.AllocateSequence(len(clauses))
for i, clause := range clauses {
clauseReg := lc.ctx.ExprCompiler.Compile(clause.Expression())
lc.ctx.Emitter.EmitMove(keyRegs[i], clauseReg)
clausesRegs[i] = keyRegs[i]
directions[i] = sortDirection(clause.SortDirection())
// TODO: Free Registers
}
arrReg := lc.ctx.Registers.Allocate(core.Temp)
lc.ctx.Emitter.EmitAs(vm.OpList, arrReg, keyRegs)
lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, arrReg) // TODO: Free Registers
} else {
clausesReg := lc.ctx.ExprCompiler.Compile(clauses[0].Expression())
lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, clausesReg)
}
var kvValReg vm.Operand
// In case the value is not used in the loop body, and only key is used
if loop.ValueName != "" {
kvValReg = loop.Value
} else {
// If so, we need to load it from the iterator
kvValReg = lc.ctx.Registers.Allocate(core.Temp)
loop.EmitValue(kvKeyReg, lc.ctx.Emitter)
}
if isSortMany {
encoded := runtime.EncodeSortDirections(directions)
count := len(clauses)
lc.ctx.Emitter.PatchSwapAxy(loop.ResultPos, vm.OpDataSetMultiSorter, loop.Result, encoded, count)
} else {
dir := sortDirection(clauses[0].SortDirection())
lc.ctx.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetSorter, loop.Result, int(dir))
}
lc.ctx.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg)
loop.EmitFinalization(lc.ctx.Emitter)
// Replace source with the Sorter
lc.ctx.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result)
// Create a new loop
loop.EmitInitialization(lc.ctx.Registers, lc.ctx.Emitter)
}

View File

@@ -8,7 +8,7 @@ import (
func TestCollectAggregate(t *testing.T) { func TestCollectAggregate(t *testing.T) {
RunUseCases(t, []UseCase{ RunUseCases(t, []UseCase{
ByteCodeCase(` SkipByteCodeCase(`
LET users = [] LET users = []
FOR u IN users FOR u IN users
COLLECT genderGroup = u.gender COLLECT genderGroup = u.gender
@@ -19,6 +19,17 @@ FOR u IN users
minAge, minAge,
maxAge maxAge
} }
`, BC{
I(vm.OpReturn, 0, 7),
}),
ByteCodeCase(`
LET users = []
FOR u IN users
COLLECT AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
RETURN {
minAge,
maxAge
}
`, BC{ `, BC{
I(vm.OpReturn, 0, 7), I(vm.OpReturn, 0, 7),
}), }),

View File

@@ -0,0 +1,18 @@
package bytecode_test
import (
"testing"
"github.com/MontFerret/ferret/pkg/vm"
)
func TestFor(t *testing.T) {
RunUseCases(t, []UseCase{
ByteCodeCase(`
FOR i IN 1..5
RETURN i
`, BC{
I(vm.OpReturn, 0, 7),
}),
})
}

View File

@@ -23,7 +23,10 @@ func TestFor(t *testing.T) {
FOR foo IN foo FOR foo IN foo
RETURN foo RETURN foo
`, "Should not compile FOR foo IN foo"), `, "Should not compile FOR foo IN foo"),
CaseArray("FOR i IN 1..5 RETURN i", []any{1, 2, 3, 4, 5}), CaseArray(`
FOR i IN 1..5
RETURN i
`, []any{1, 2, 3, 4, 5}),
CaseArray( CaseArray(
` `
FOR i IN 1..5 FOR i IN 1..5