diff --git a/src/cli/newBuild.go b/src/cli/newBuild.go index 4d1cbaa..f0866cc 100644 --- a/src/cli/newBuild.go +++ b/src/cli/newBuild.go @@ -41,12 +41,18 @@ func newBuild(args []string) (*config.Build, error) { build.Filter = args[i] + case "-no-fold": + build.FoldConstants = false + case "-no-lint": build.Lint(false) case "-no-optimize": build.Optimize(false) + case "-no-reorder": + build.ReorderValues = false + case "-os": i++ diff --git a/src/config/Build.go b/src/config/Build.go index 93dc629..a8639eb 100644 --- a/src/config/Build.go +++ b/src/config/Build.go @@ -8,5 +8,6 @@ type Build struct { Filter string Dry bool FoldConstants bool + ReorderValues bool LintBinaryOps bool } \ No newline at end of file diff --git a/src/config/Optimize.go b/src/config/Optimize.go index 2aeb77a..d6eb3d1 100644 --- a/src/config/Optimize.go +++ b/src/config/Optimize.go @@ -3,4 +3,5 @@ package config // Optimize enables or disables the optimizer. func (build *Build) Optimize(enabled bool) { build.FoldConstants = enabled + build.ReorderValues = enabled } \ No newline at end of file diff --git a/src/core/optimize.go b/src/core/optimize.go index b575bec..08081ed 100644 --- a/src/core/optimize.go +++ b/src/core/optimize.go @@ -47,6 +47,14 @@ func (f *Function) optimize() error { // to calculate the list of users. f.ComputeUsers() + // Move values closer to their first use to reduce the number + // of values that are alive at the same time. + if f.Env.Build.ReorderValues { + for _, block := range f.Blocks { + block.Reorder() + } + } + // Now that we have the list of users for each instruction, // we can filter out dead values. err := f.removeDeadCode(folded) diff --git a/src/ssa/Block.go b/src/ssa/Block.go index f69b8ea..a204497 100644 --- a/src/ssa/Block.go +++ b/src/ssa/Block.go @@ -5,6 +5,7 @@ import ( "maps" "slices" + "git.urbach.dev/cli/q/src/set" "git.urbach.dev/cli/q/src/types" ) @@ -300,6 +301,36 @@ func (b *Block) RemoveNilValues() { }) } +// Reorder moves values closer to their first use. +func (b *Block) Reorder() { + for start, instr := range slices.Backward(b.Instructions) { + switch instr.(type) { + case *Bytes, *Data, *Int: + default: + continue + } + + users := instr.Users() + + if len(users) == 0 { + continue + } + + firstUser := users[0] + end := b.Index(firstUser) + + if end <= start+1 { + continue + } + + for end > start && slices.Contains(b.Instructions[end-1].Users(), firstUser) { + end-- + } + + set.BringToBack(b.Instructions[start:end], 0) + } +} + // ReplaceAllUses replaces all uses of `old` with `new`. func (b *Block) ReplaceAllUses(old Value, new Value) { for _, instr := range b.Instructions { diff --git a/tests/reorder-values.q b/tests/reorder-values.q new file mode 100644 index 0000000..4b7b569 --- /dev/null +++ b/tests/reorder-values.q @@ -0,0 +1,9 @@ +main() { + a := 1 + b := 2 + d := 3 + e := 4 + c := a + b + f := d + e + assert c + f == 10 +} \ No newline at end of file diff --git a/tests/tests_test.go b/tests/tests_test.go index f1b50de..d6967b4 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -117,6 +117,7 @@ var tests = []run{ {"strings-suffix", nil, "", "", 0}, {"strings-concat", nil, "", "", 0}, {"reorder-add", nil, "", "", 0}, + {"reorder-values", nil, "", "", 0}, {"free-registers", nil, "", "", 0}, {"overload", nil, "", "", 0}, {"resource", nil, "", "", 0},