| // 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 ssacompile |
| |
| import ( |
| "fmt" |
| "math" |
| |
| "cmd/compile/internal/ssa" |
| "cmd/compile/internal/ssa/ssaop" |
| "cmd/internal/src" |
| ) |
| |
| // deadcode indicates whether rewrite should try to remove any values that become dead. |
| func applyRewrite(f *ssa.Func, rb ssa.BlockRewriter, rv ssa.ValueRewriter, deadcode ssa.DeadValueChoice) { |
| // repeat rewrites until we find no more rewrites |
| pendingLines := f.CachedLineStarts // Holds statement boundaries that need to be moved to a new value/block |
| pendingLines.Clear() |
| debug := f.Pass.Debug |
| if debug > 1 { |
| fmt.Printf("%s: rewriting for %s\n", f.Pass.Name, f.Name) |
| } |
| // if the number of rewrite iterations reaches itersLimit we will |
| // at that point turn on cycle detection. Instead of a fixed limit, |
| // size the limit according to func size to allow for cases such |
| // as the one in issue #66773. |
| itersLimit := f.NumBlocks() |
| if itersLimit < 20 { |
| itersLimit = 20 |
| } |
| var iters int |
| var states map[string]bool |
| for { |
| if debug > 1 { |
| fmt.Printf("%s: iter %d\n", f.Pass.Name, iters) |
| } |
| change := false |
| deadChange := false |
| for _, b := range f.Blocks { |
| var b0 *ssa.Block |
| if debug > 1 { |
| fmt.Printf("%s: start block\n", f.Pass.Name) |
| b0 = new(ssa.Block) |
| *b0 = *b |
| b0.Succs = append([]ssa.Edge{}, b.Succs...) // make a new copy, not aliasing |
| } |
| for i, c := range b.ControlValues() { |
| for c.Op == ssaop.OpCopy { |
| c = c.Args[0] |
| b.ReplaceControl(i, c) |
| } |
| } |
| if rb(b) { |
| change = true |
| if debug > 1 { |
| fmt.Printf("rewriting %s -> %s\n", b0.LongString(), b.LongString()) |
| } |
| } |
| for j, v := range b.Values { |
| if debug > 1 { |
| fmt.Printf("%s: consider %v\n", f.Pass.Name, v.LongString()) |
| } |
| var v0 *ssa.Value |
| if debug > 1 { |
| v0 = new(ssa.Value) |
| *v0 = *v |
| v0.Args = append([]*ssa.Value{}, v.Args...) // make a new copy, not aliasing |
| } |
| if v.Uses == 0 && v.Removeable() { |
| if v.Op != ssaop.OpInvalid && deadcode == ssa.RemoveDeadValues { |
| // Reset any values that are now unused, so that we decrement |
| // the use count of all of its arguments. |
| // Not quite a deadcode pass, because it does not handle cycles. |
| // But it should help Uses==1 rules to fire. |
| v.Reset(ssaop.OpInvalid) |
| deadChange = true |
| } |
| // No point rewriting values which aren't used. |
| continue |
| } |
| |
| vchange := ssa.PhiElimValue(v) |
| if vchange && debug > 1 { |
| fmt.Printf("rewriting %s -> %s\n", v0.LongString(), v.LongString()) |
| } |
| |
| // Eliminate copy inputs. |
| // If any copy input becomes unused, mark it |
| // as invalid and discard its argument. Repeat |
| // recursively on the discarded argument. |
| // This phase helps remove phantom "dead copy" uses |
| // of a value so that a x.Uses==1 rule condition |
| // fires reliably. |
| for i, a := range v.Args { |
| if a.Op != ssaop.OpCopy { |
| continue |
| } |
| aa := copySource(a) |
| v.SetArg(i, aa) |
| // If a, a copy, has a line boundary indicator, attempt to find a new value |
| // to hold it. The first candidate is the value that will replace a (aa), |
| // if it shares the same block and line and is eligible. |
| // The second option is v, which has a as an input. Because aa is earlier in |
| // the data flow, it is the better choice. |
| if a.Pos.IsStmt() == src.PosIsStmt { |
| if aa.Block == a.Block && aa.Pos.Line() == a.Pos.Line() && aa.Pos.IsStmt() != src.PosNotStmt { |
| aa.Pos = aa.Pos.WithIsStmt() |
| } else if v.Block == a.Block && v.Pos.Line() == a.Pos.Line() && v.Pos.IsStmt() != src.PosNotStmt { |
| v.Pos = v.Pos.WithIsStmt() |
| } else { |
| // Record the lost line and look for a new home after all rewrites are complete. |
| // TODO: it's possible (in FOR loops, in particular) for statement boundaries for the same |
| // line to appear in more than one block, but only one block is stored, so if both end |
| // up here, then one will be lost. |
| pendingLines.Set(a.Pos, int32(a.Block.ID)) |
| } |
| a.Pos = a.Pos.WithNotStmt() |
| } |
| vchange = true |
| for a.Uses == 0 { |
| b := a.Args[0] |
| a.Reset(ssaop.OpInvalid) |
| a = b |
| } |
| } |
| if vchange && debug > 1 { |
| fmt.Printf("rewriting %s -> %s\n", v0.LongString(), v.LongString()) |
| } |
| |
| // apply rewrite function |
| if rv(v) { |
| vchange = true |
| // If value changed to a poor choice for a statement boundary, move the boundary |
| if v.Pos.IsStmt() == src.PosIsStmt { |
| if k := nextGoodStatementIndex(v, j, b); k != j { |
| v.Pos = v.Pos.WithNotStmt() |
| b.Values[k].Pos = b.Values[k].Pos.WithIsStmt() |
| } |
| } |
| } |
| |
| change = change || vchange |
| if vchange && debug > 1 { |
| fmt.Printf("rewriting %s -> %s\n", v0.LongString(), v.LongString()) |
| } |
| } |
| } |
| if !change && !deadChange { |
| break |
| } |
| iters++ |
| if (iters > itersLimit || debug >= 2) && change { |
| // We've done a suspiciously large number of rewrites (or we're in debug mode). |
| // As of Sep 2021, 90% of rewrites complete in 4 iterations or fewer |
| // and the maximum value encountered during make.bash is 12. |
| // Start checking for cycles. (This is too expensive to do routinely.) |
| // Note: we avoid this path for deadChange-only iterations, to fix #51639. |
| if states == nil { |
| states = make(map[string]bool) |
| } |
| h := f.RewriteHash() |
| if _, ok := states[h]; ok { |
| // We've found a cycle. |
| // To diagnose it, set debug to 2 and start again, |
| // so that we'll print all rules applied until we complete another cycle. |
| // If debug is already >= 2, we've already done that, so it's time to crash. |
| if debug < 2 { |
| debug = 2 |
| states = make(map[string]bool) |
| } else { |
| f.Fatalf("rewrite cycle detected") |
| } |
| } |
| states[h] = true |
| } |
| } |
| // remove clobbered values |
| for _, b := range f.Blocks { |
| j := 0 |
| for i, v := range b.Values { |
| vl := v.Pos |
| if v.Op == ssaop.OpInvalid { |
| if v.Pos.IsStmt() == src.PosIsStmt { |
| pendingLines.Set(vl, int32(b.ID)) |
| } |
| f.FreeValue(v) |
| continue |
| } |
| if v.Pos.IsStmt() != src.PosNotStmt && !ssa.NotStmtBoundary(v.Op) { |
| if pl, ok := pendingLines.Get(vl); ok && pl == int32(b.ID) { |
| pendingLines.Remove(vl) |
| v.Pos = v.Pos.WithIsStmt() |
| } |
| } |
| if i != j { |
| b.Values[j] = v |
| } |
| j++ |
| } |
| if pl, ok := pendingLines.Get(b.Pos); ok && pl == int32(b.ID) { |
| b.Pos = b.Pos.WithIsStmt() |
| pendingLines.Remove(b.Pos) |
| } |
| b.TruncateValues(j) |
| } |
| } |
| |
| // truncate64Fto32F converts a float64 value to a float32 preserving the bit pattern |
| // of the mantissa. It will panic if the truncation results in lost information. |
| func truncate64Fto32F(f float64) float32 { |
| if !isExactFloat32(f) { |
| panic("truncate64Fto32F: truncation is not exact") |
| } |
| if !math.IsNaN(f) { |
| return float32(f) |
| } |
| // NaN bit patterns aren't necessarily preserved across conversion |
| // instructions so we need to do the conversion manually. |
| b := math.Float64bits(f) |
| m := b & ((1 << 52) - 1) // mantissa (a.k.a. significand) |
| // | sign | exponent | mantissa | |
| r := uint32(((b >> 32) & (1 << 31)) | 0x7f800000 | (m >> (52 - 23))) |
| return math.Float32frombits(r) |
| } |
| |
| // auxTo32F decodes a float32 from the AuxInt value provided. |
| func auxTo32F(i int64) float32 { |
| return truncate64Fto32F(math.Float64frombits(uint64(i))) |
| } |
| |
| // mergePoint finds a block among a's blocks which dominates b and is itself |
| // dominated by all of a's blocks. Returns nil if it can't find one. |
| // Might return nil even if one does exist. |
| func mergePoint(b *ssa.Block, a ...*ssa.Value) *ssa.Block { |
| // Walk backward from b looking for one of the a's blocks. |
| |
| // Max distance |
| d := 100 |
| |
| for d > 0 { |
| for _, x := range a { |
| if b == x.Block { |
| goto found |
| } |
| } |
| if len(b.Preds) > 1 { |
| // Don't know which way to go back. Abort. |
| return nil |
| } |
| b = b.Preds[0].B |
| d-- |
| } |
| return nil // too far away |
| found: |
| // At this point, r is the first value in a that we find by walking backwards. |
| // if we return anything, r will be it. |
| r := b |
| |
| // Keep going, counting the other a's that we find. They must all dominate r. |
| na := 0 |
| for d > 0 { |
| for _, x := range a { |
| if b == x.Block { |
| na++ |
| } |
| } |
| if na == len(a) { |
| // Found all of a in a backwards walk. We can return r. |
| return r |
| } |
| if len(b.Preds) > 1 { |
| return nil |
| } |
| b = b.Preds[0].B |
| d-- |
| |
| } |
| return nil // too far away |
| } |
| |
| // encodes condition code and NZCV flags into result. |
| func arm64ConditionalParamsAuxInt(cond ssaop.Op, nzcv uint8) ssa.Arm64ConditionalParams { |
| if cond < ssaop.OpARM64Equal || cond > ssaop.OpARM64GreaterEqualU { |
| panic("Wrong conditional operation") |
| } |
| if nzcv&0x0f != nzcv { |
| panic("Wrong value of NZCV flag") |
| } |
| return ssa.Arm64ConditionalParams{Cond: cond, NzcvVal: nzcv, ConstVal: 0, Ind: false} |
| } |
| |
| // encodes condition code, NZCV flags and constant value into auxint. |
| func arm64ConditionalParamsAuxIntWithValue(cond ssaop.Op, nzcv uint8, value uint8) ssa.Arm64ConditionalParams { |
| if value&0x1f != value { |
| panic("Wrong value of constant") |
| } |
| params := arm64ConditionalParamsAuxInt(cond, nzcv) |
| params.ConstVal = value |
| params.Ind = true |
| return params |
| } |