package core import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/linter" "git.urbach.dev/cli/q/src/optimizer" "git.urbach.dev/cli/q/src/ssa" ) // optimize applies various algorithms after the compilation. func (f *Function) optimize() error { // Sometimes unreachable blocks are created at the end of // an infinite loop. These should be removed to avoid // unnecessary return statements in the later phases. optimizer.RemoveDeadBlocks(&f.IR) // After the removal of dead blocks, if the last block is // not part of a loop and did not end with a return // statement, an implicit return is inserted. if f.needsImplicitReturn() { if len(f.Output) > 0 { return errors.New(&ReturnCountMismatch{Count: 0, ExpectedCount: len(f.Output)}, f.File, f.Output[0].Source) } f.deleteResources(nil) f.Block().Append(&ssa.Return{}) } // Lint binary operations that can often be reduced to // simpler expressions. if f.Env.Build.LintBinaryOps { err := linter.LintBinaryOps(f.IR, f.File) if err != nil { return err } } // Binary operations with constant operands are evaluated // at compile time. For example, 1 + 2 becomes 3, and the // result is propagated to subsequent operations. var folded map[ssa.Value]struct{} if f.Env.Build.Fold { folded = optimizer.Fold(f.IR) } // Replace copies with their actual values. if f.Env.Build.RemoveCopies { optimizer.RemoveCopies(&f.IR) } // After cleaning up some of the instructions we can proceed // to calculate the list of users. f.ComputeUsers() // Now that we have the list of users for each instruction, // we can filter out dead values. err := f.removeDeadCode(folded) if err != nil { return err } // Move values closer to their first use to reduce the number // of values that are alive at the same time. if f.Env.Build.Reorder { optimizer.Reorder(f.IR) optimizer.ReorderBlocks(&f.IR) } // Resource types that are still defined at the end of a // scope must be freed. return f.verifyDeallocation() }