package core import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) // compileLoop compiles an endless loop. func (f *Function) compileLoop(node *ast.Loop) error { f.Count.Loop++ headLabel := f.CreateLabel("loop.head", f.Count.Loop) exitLabel := f.CreateLabel("loop.exit", f.Count.Loop) beforeLoop := f.Block() loopHead := ssa.NewBlock(headLabel) loopExit := ssa.NewBlock(exitLabel) loopBlockIndex := len(f.Blocks) loop := &Loop{ Head: loopHead, Exit: loopExit, } if node.Head != nil { // Before the loop starts, we evaluate the lower limit // and identify it as the loop counter. name, from, to := f.parseLoopHeader(node.Head) if from == nil { return errors.New(InvalidLoopHeader, f.File, node.Head.Source()) } loop.IteratorName = name fromValue, err := f.evaluateRight(from) if err != nil { return err } if !types.Is(fromValue.Type(), types.AnyInt) { return errors.New(&TypeMismatch{Encountered: fromValue.Type().Name(), Expected: types.AnyInt.Name()}, f.File, from.Source()) } if f.Block().IsIdentified(fromValue) { fromValue = f.copy(fromValue, from.Source(), isIdentifierRead(from)) } beforeLoop.Identify(name, fromValue) f.jump(loopHead) // Loop starts, this is the jump target for new iterations. // The upper limit is recalculated on every iteration. // We check that the condition to jump to the loop body is true, // otherwise we jump to the loop exit. f.AddBlock(loopHead) toValue, err := f.evaluateRight(to) if err != nil { return err } if !types.Is(toValue.Type(), fromValue.Type()) { return errors.New(&TypeMismatch{Encountered: toValue.Type().Name(), Expected: fromValue.Type().Name()}, f.File, to.Source()) } condition := f.Append(&ssa.BinaryOp{ Op: token.Less, Left: fromValue, Right: toValue, }) bodyLabel := f.CreateLabel("loop.body", f.Count.Loop) bodyBlock := ssa.NewBlock(bodyLabel) f.Append(&ssa.Branch{ Condition: condition, Then: bodyBlock, Else: loopExit, }) loopHead.AddSuccessor(bodyBlock) // Loop condition is true from now on so we'll // execute the code inside the loop body. f.AddBlock(bodyBlock) f.loopStack.Push(loop) err = f.compileAST(node.Body) if err != nil { return err } f.loopStack.Pop() } else { f.jump(loopHead) f.AddBlock(loopHead) // For infinite loops, there are no conditions to check, // we can simply process the loop body. f.loopStack.Push(loop) err := f.compileAST(node.Body) if err != nil { return err } f.loopStack.Pop() } // Jump back to the loop head. f.loopNext(loop) // The initial compilation of the loop body does not know // that the code is repeated in a loop. Therefore, we need // to find identifiers that were both defined outside the loop // and modified within the loop. For these identifiers, // we created Phi functions at the top of the loop head. // All that's left to do is to replace all the occurrences // of the old values with their new Phi in the loop blocks. loopBlocks := f.Blocks[loopBlockIndex:] for _, block := range loopBlocks { if block.Loop == nil { block.Loop = loopHead } } for _, block := range loopBlocks { for phi := range loopHead.Phis { oldValue := phi.Arguments[0] if oldValue == ssa.Undefined { continue } for _, instr := range block.Instructions { if instr == phi { continue } switch instr := instr.(type) { case *ssa.Jump: jumpBlock := instr.To if jumpBlock.Loop != nil { continue } for name, value := range jumpBlock.Identifiers.Before.Raw() { if value == oldValue { jumpBlock.ReplaceIdentifier(name, oldValue, phi) } else if jumpBlock.Index(value) != -1 { value.Replace(oldValue, phi) } } default: instr.Replace(oldValue, phi) } } for i := 1; i < len(phi.Arguments); i++ { if phi.Arguments[i] == oldValue { phi.Arguments[i] = phi } } } } if node.Head != nil { loopHead.AddSuccessor(loopExit) } f.AddBlock(loopExit) return nil }