// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ssa // tighten moves Values closer to the Blocks in which they are used. // This can reduce the amount of register spilling required, // if it doesn't also create more live values. // For now, it handles only the trivial case in which a // Value with one or fewer args is only used in a single Block, // and not in a phi value. // TODO: Do something smarter. // A Value can be moved to any block that // dominates all blocks in which it is used. // Figure out when that will be an improvement. func tighten(f *Func) { // For each value, the number of blocks in which it is used. uses := make([]int32, f.NumValues()) // For each value, whether that value is ever an arg to a phi value. phi := make([]bool, f.NumValues()) // For each value, one block in which that value is used. home := make([]*Block, f.NumValues()) changed := true for changed { changed = false // Reset uses for i := range uses { uses[i] = 0 } // No need to reset home; any relevant values will be written anew anyway. // No need to reset phi; once used in a phi, always used in a phi. for _, b := range f.Blocks { for _, v := range b.Values { for _, w := range v.Args { if v.Op == OpPhi { phi[w.ID] = true } uses[w.ID]++ home[w.ID] = b } } if b.Control != nil { uses[b.Control.ID]++ home[b.Control.ID] = b } } for _, b := range f.Blocks { for i := 0; i < len(b.Values); i++ { v := b.Values[i] switch v.Op { case OpPhi, OpGetClosurePtr, OpConvert, OpArg: // GetClosurePtr & Arg must stay in entry block. // OpConvert must not float over call sites. // TODO do we instead need a dependence edge of some sort for OpConvert? // Would memory do the trick, or do we need something else that relates // to safe point operations? continue default: } if v.Op == OpSelect0 || v.Op == OpSelect1 { // tuple selector must stay with tuple generator continue } if len(v.Args) > 0 && v.Args[len(v.Args)-1].Type.IsMemory() { // We can't move values which have a memory arg - it might // make two memory values live across a block boundary. continue } if uses[v.ID] == 1 && !phi[v.ID] && home[v.ID] != b && len(v.Args) < 2 { // v is used in exactly one block, and it is not b. // Furthermore, it takes at most one input, // so moving it will not increase the // number of live values anywhere. // Move v to that block. c := home[v.ID] c.Values = append(c.Values, v) v.Block = c last := len(b.Values) - 1 b.Values[i] = b.Values[last] b.Values[last] = nil b.Values = b.Values[:last] changed = true } } } } } // phiTighten moves constants closer to phi users. // This pass avoids having lots of constants live for lots of the program. // See issue 16407. func phiTighten(f *Func) { for _, b := range f.Blocks { for _, v := range b.Values { if v.Op != OpPhi { continue } for i, a := range v.Args { if !a.rematerializeable() { continue // not a constant we can move around } if a.Block == b.Preds[i].b { continue // already in the right place } // Make a copy of a, put in predecessor block. v.SetArg(i, a.copyInto(b.Preds[i].b)) } } } }