| // 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 ( | |
| "cmp" | |
| "slices" | |
| "cmd/compile/internal/ssa" | |
| "cmd/compile/internal/ssa/rewrite/dec" | |
| "cmd/compile/internal/ssa/rewrite/dec64" | |
| "cmd/compile/internal/ssa/ssaop" | |
| "cmd/compile/internal/types" | |
| ) | |
| // decompose converts phi ops on compound builtin types into phi | |
| // ops on simple types, then invokes rewrite rules to decompose | |
| // other ops on those types. | |
| func decomposeBuiltin(f *ssa.Func) { | |
| // Decompose phis | |
| for _, b := range f.Blocks { | |
| for _, v := range b.Values { | |
| if v.Op != ssaop.OpPhi { | |
| continue | |
| } | |
| decomposeBuiltinPhi(v) | |
| } | |
| } | |
| // Decompose other values | |
| // Note: Leave dead values because we need to keep the original | |
| // values around so the name component resolution below can still work. | |
| applyRewrite(f, dec.RewriteBlock, dec.RewriteValue, ssa.LeaveDeadValues) | |
| if f.Config.RegSize == 4 { | |
| applyRewrite(f, dec64.RewriteBlock, dec64.RewriteValue, ssa.LeaveDeadValues) | |
| } | |
| // Split up named values into their components. | |
| // accumulate old names for aggregates (that are decomposed) in toDelete for efficient bulk deletion, | |
| // accumulate new LocalSlots in newNames for addition after the iteration. This decomposition is for | |
| // builtin types with leaf components, and thus there is no need to reprocess the newly create LocalSlots. | |
| var toDelete []namedVal | |
| var newNames []ssa.LocalSlot | |
| for i, name := range f.Names { | |
| t := name.Type | |
| switch { | |
| case t.IsInteger() && t.Size() > f.Config.RegSize: | |
| hiName, loName := f.SplitInt64(f.LocalSlotAddr(name)) | |
| newNames = maybeAppend2(f, newNames, hiName, loName) | |
| for j, v := range f.NamedValues[name] { | |
| if v.Op != ssaop.OpInt64Make { | |
| continue | |
| } | |
| f.NamedValues[*hiName] = append(f.NamedValues[*hiName], v.Args[0]) | |
| f.NamedValues[*loName] = append(f.NamedValues[*loName], v.Args[1]) | |
| toDelete = append(toDelete, namedVal{i, j}) | |
| } | |
| case t.IsComplex(): | |
| rName, iName := f.SplitComplex(f.LocalSlotAddr(name)) | |
| newNames = maybeAppend2(f, newNames, rName, iName) | |
| for j, v := range f.NamedValues[name] { | |
| if v.Op != ssaop.OpComplexMake { | |
| continue | |
| } | |
| f.NamedValues[*rName] = append(f.NamedValues[*rName], v.Args[0]) | |
| f.NamedValues[*iName] = append(f.NamedValues[*iName], v.Args[1]) | |
| toDelete = append(toDelete, namedVal{i, j}) | |
| } | |
| case t.IsString(): | |
| ptrName, lenName := f.SplitString(f.LocalSlotAddr(name)) | |
| newNames = maybeAppend2(f, newNames, ptrName, lenName) | |
| for j, v := range f.NamedValues[name] { | |
| if v.Op != ssaop.OpStringMake { | |
| continue | |
| } | |
| f.NamedValues[*ptrName] = append(f.NamedValues[*ptrName], v.Args[0]) | |
| f.NamedValues[*lenName] = append(f.NamedValues[*lenName], v.Args[1]) | |
| toDelete = append(toDelete, namedVal{i, j}) | |
| } | |
| case t.IsSlice(): | |
| ptrName, lenName, capName := f.SplitSlice(f.LocalSlotAddr(name)) | |
| newNames = maybeAppend2(f, newNames, ptrName, lenName) | |
| newNames = maybeAppend(f, newNames, capName) | |
| for j, v := range f.NamedValues[name] { | |
| if v.Op != ssaop.OpSliceMake { | |
| continue | |
| } | |
| f.NamedValues[*ptrName] = append(f.NamedValues[*ptrName], v.Args[0]) | |
| f.NamedValues[*lenName] = append(f.NamedValues[*lenName], v.Args[1]) | |
| f.NamedValues[*capName] = append(f.NamedValues[*capName], v.Args[2]) | |
| toDelete = append(toDelete, namedVal{i, j}) | |
| } | |
| case t.IsInterface(): | |
| typeName, dataName := f.SplitInterface(f.LocalSlotAddr(name)) | |
| newNames = maybeAppend2(f, newNames, typeName, dataName) | |
| for j, v := range f.NamedValues[name] { | |
| if v.Op != ssaop.OpIMake { | |
| continue | |
| } | |
| f.NamedValues[*typeName] = append(f.NamedValues[*typeName], v.Args[0]) | |
| f.NamedValues[*dataName] = append(f.NamedValues[*dataName], v.Args[1]) | |
| toDelete = append(toDelete, namedVal{i, j}) | |
| } | |
| case t.IsFloat(): | |
| // floats are never decomposed, even ones bigger than RegSize | |
| case t.Size() > f.Config.RegSize && !t.IsSIMD(): | |
| f.Fatalf("undecomposed named type %s %v", name, t) | |
| } | |
| } | |
| deleteNamedVals(f, toDelete) | |
| f.Names = append(f.Names, newNames...) | |
| } | |
| func maybeAppend(f *ssa.Func, ss []ssa.LocalSlot, s *ssa.LocalSlot) []ssa.LocalSlot { | |
| if _, ok := f.NamedValues[*s]; !ok { | |
| f.NamedValues[*s] = nil | |
| return append(ss, *s) | |
| } | |
| return ss | |
| } | |
| func maybeAppend2(f *ssa.Func, ss []ssa.LocalSlot, s1, s2 *ssa.LocalSlot) []ssa.LocalSlot { | |
| return maybeAppend(f, maybeAppend(f, ss, s1), s2) | |
| } | |
| func decomposeBuiltinPhi(v *ssa.Value) { | |
| switch { | |
| case v.Type.IsInteger() && v.Type.Size() > v.Block.Func.Config.RegSize: | |
| decomposeInt64Phi(v) | |
| case v.Type.IsComplex(): | |
| decomposeComplexPhi(v) | |
| case v.Type.IsString(): | |
| decomposeStringPhi(v) | |
| case v.Type.IsSlice(): | |
| decomposeSlicePhi(v) | |
| case v.Type.IsInterface(): | |
| decomposeInterfacePhi(v) | |
| case v.Type.IsFloat(): | |
| // floats are never decomposed, even ones bigger than RegSize | |
| case v.Type.Size() > v.Block.Func.Config.RegSize && !v.Type.IsSIMD(): | |
| v.Fatalf("%v undecomposed type %v", v, v.Type) | |
| } | |
| } | |
| func decomposeStringPhi(v *ssa.Value) { | |
| types := &v.Block.Func.Config.Types | |
| ptrType := types.BytePtr | |
| lenType := types.Int | |
| ptr := v.Block.NewValue0(v.Pos, ssaop.OpPhi, ptrType) | |
| len := v.Block.NewValue0(v.Pos, ssaop.OpPhi, lenType) | |
| for _, a := range v.Args { | |
| ptr.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpStringPtr, ptrType, a)) | |
| len.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpStringLen, lenType, a)) | |
| } | |
| v.Reset(ssaop.OpStringMake) | |
| v.AddArg(ptr) | |
| v.AddArg(len) | |
| } | |
| func decomposeSlicePhi(v *ssa.Value) { | |
| types := &v.Block.Func.Config.Types | |
| ptrType := v.Type.Elem().PtrTo() | |
| lenType := types.Int | |
| ptr := v.Block.NewValue0(v.Pos, ssaop.OpPhi, ptrType) | |
| len := v.Block.NewValue0(v.Pos, ssaop.OpPhi, lenType) | |
| cap := v.Block.NewValue0(v.Pos, ssaop.OpPhi, lenType) | |
| for _, a := range v.Args { | |
| ptr.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpSlicePtr, ptrType, a)) | |
| len.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpSliceLen, lenType, a)) | |
| cap.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpSliceCap, lenType, a)) | |
| } | |
| v.Reset(ssaop.OpSliceMake) | |
| v.AddArg(ptr) | |
| v.AddArg(len) | |
| v.AddArg(cap) | |
| } | |
| func decomposeInt64Phi(v *ssa.Value) { | |
| cfgtypes := &v.Block.Func.Config.Types | |
| var partType *types.Type | |
| if v.Type.IsSigned() { | |
| partType = cfgtypes.Int32 | |
| } else { | |
| partType = cfgtypes.UInt32 | |
| } | |
| hi := v.Block.NewValue0(v.Pos, ssaop.OpPhi, partType) | |
| lo := v.Block.NewValue0(v.Pos, ssaop.OpPhi, cfgtypes.UInt32) | |
| for _, a := range v.Args { | |
| hi.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpInt64Hi, partType, a)) | |
| lo.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpInt64Lo, cfgtypes.UInt32, a)) | |
| } | |
| v.Reset(ssaop.OpInt64Make) | |
| v.AddArg(hi) | |
| v.AddArg(lo) | |
| } | |
| func decomposeComplexPhi(v *ssa.Value) { | |
| cfgtypes := &v.Block.Func.Config.Types | |
| var partType *types.Type | |
| switch z := v.Type.Size(); z { | |
| case 8: | |
| partType = cfgtypes.Float32 | |
| case 16: | |
| partType = cfgtypes.Float64 | |
| default: | |
| v.Fatalf("decomposeComplexPhi: bad complex size %d", z) | |
| } | |
| real := v.Block.NewValue0(v.Pos, ssaop.OpPhi, partType) | |
| imag := v.Block.NewValue0(v.Pos, ssaop.OpPhi, partType) | |
| for _, a := range v.Args { | |
| real.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpComplexReal, partType, a)) | |
| imag.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpComplexImag, partType, a)) | |
| } | |
| v.Reset(ssaop.OpComplexMake) | |
| v.AddArg(real) | |
| v.AddArg(imag) | |
| } | |
| func decomposeInterfacePhi(v *ssa.Value) { | |
| uintptrType := v.Block.Func.Config.Types.Uintptr | |
| ptrType := v.Block.Func.Config.Types.BytePtr | |
| itab := v.Block.NewValue0(v.Pos, ssaop.OpPhi, uintptrType) | |
| data := v.Block.NewValue0(v.Pos, ssaop.OpPhi, ptrType) | |
| for _, a := range v.Args { | |
| itab.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpITab, uintptrType, a)) | |
| data.AddArg(a.Block.NewValue1(v.Pos, ssaop.OpIData, ptrType, a)) | |
| } | |
| v.Reset(ssaop.OpIMake) | |
| v.AddArg(itab) | |
| v.AddArg(data) | |
| } | |
| func decomposeUser(f *ssa.Func) { | |
| for _, b := range f.Blocks { | |
| for _, v := range b.Values { | |
| if v.Op != ssaop.OpPhi { | |
| continue | |
| } | |
| decomposeUserPhi(v) | |
| } | |
| } | |
| // Split up named values into their components. | |
| i := 0 | |
| var newNames []ssa.LocalSlot | |
| for _, name := range f.Names { | |
| t := name.Type | |
| switch { | |
| case isStructNotSIMD(t): | |
| newNames = decomposeUserStructInto(f, f.LocalSlotAddr(name), newNames) | |
| case t.IsArray(): | |
| newNames = decomposeUserArrayInto(f, f.LocalSlotAddr(name), newNames) | |
| default: | |
| f.Names[i] = name | |
| i++ | |
| } | |
| } | |
| f.Names = f.Names[:i] | |
| f.Names = append(f.Names, newNames...) | |
| } | |
| // decomposeUserArrayInto creates names for the element(s) of arrays referenced | |
| // by name where possible, and appends those new names to slots, which is then | |
| // returned. | |
| func decomposeUserArrayInto(f *ssa.Func, name *ssa.LocalSlot, slots []ssa.LocalSlot) []ssa.LocalSlot { | |
| t := name.Type | |
| if t.Size() == 0 { | |
| // TODO(khr): Not sure what to do here. Probably nothing. | |
| // Names for empty arrays aren't important. | |
| return slots | |
| } | |
| if t.NumElem() != 1 { | |
| // shouldn't get here due to CanSSA | |
| f.Fatalf("array not of size 1") | |
| } | |
| elemName := f.SplitArray(name) | |
| var keep []*ssa.Value | |
| for _, v := range f.NamedValues[*name] { | |
| if v.Op != ssaop.OpArrayMake1 { | |
| keep = append(keep, v) | |
| continue | |
| } | |
| f.NamedValues[*elemName] = append(f.NamedValues[*elemName], v.Args[0]) | |
| } | |
| if len(keep) == 0 { | |
| // delete the name for the array as a whole | |
| delete(f.NamedValues, *name) | |
| } else { | |
| f.NamedValues[*name] = keep | |
| } | |
| if t.Elem().IsArray() { | |
| return decomposeUserArrayInto(f, elemName, slots) | |
| } else if isStructNotSIMD(t.Elem()) { | |
| return decomposeUserStructInto(f, elemName, slots) | |
| } | |
| return append(slots, *elemName) | |
| } | |
| // decomposeUserStructInto creates names for the fields(s) of structs referenced | |
| // by name where possible, and appends those new names to slots, which is then | |
| // returned. | |
| func decomposeUserStructInto(f *ssa.Func, name *ssa.LocalSlot, slots []ssa.LocalSlot) []ssa.LocalSlot { | |
| fnames := []*ssa.LocalSlot{} // slots for struct in name | |
| t := name.Type | |
| n := t.NumFields() | |
| for i := 0; i < n; i++ { | |
| fs := f.SplitStruct(name, i) | |
| fnames = append(fnames, fs) | |
| // arrays and structs will be decomposed further, so | |
| // there's no need to record a name | |
| if !fs.Type.IsArray() && !isStructNotSIMD(fs.Type) { | |
| slots = maybeAppend(f, slots, fs) | |
| } | |
| } | |
| var keep []*ssa.Value | |
| // create named values for each struct field | |
| for _, v := range f.NamedValues[*name] { | |
| if v.Op != ssaop.OpStructMake || len(v.Args) != n { | |
| keep = append(keep, v) | |
| continue | |
| } | |
| for i := 0; i < len(fnames); i++ { | |
| f.NamedValues[*fnames[i]] = append(f.NamedValues[*fnames[i]], v.Args[i]) | |
| } | |
| } | |
| if len(keep) == 0 { | |
| // delete the name for the struct as a whole | |
| delete(f.NamedValues, *name) | |
| } else { | |
| f.NamedValues[*name] = keep | |
| } | |
| // now that this f.NamedValues contains values for the struct | |
| // fields, recurse into nested structs | |
| for i := 0; i < n; i++ { | |
| if isStructNotSIMD(name.Type.FieldType(i)) { | |
| slots = decomposeUserStructInto(f, fnames[i], slots) | |
| delete(f.NamedValues, *fnames[i]) | |
| } else if name.Type.FieldType(i).IsArray() { | |
| slots = decomposeUserArrayInto(f, fnames[i], slots) | |
| delete(f.NamedValues, *fnames[i]) | |
| } | |
| } | |
| return slots | |
| } | |
| func decomposeUserPhi(v *ssa.Value) { | |
| switch { | |
| case isStructNotSIMD(v.Type): | |
| decomposeStructPhi(v) | |
| case v.Type.IsArray(): | |
| decomposeArrayPhi(v) | |
| } | |
| } | |
| // decomposeStructPhi replaces phi-of-struct with structmake(phi-for-each-field), | |
| // and then recursively decomposes the phis for each field. | |
| func decomposeStructPhi(v *ssa.Value) { | |
| t := v.Type | |
| if t.Size() == 0 { | |
| v.Reset(ssaop.OpEmpty) | |
| return | |
| } | |
| n := t.NumFields() | |
| fields := make([]*ssa.Value, 0, ssa.MaxStruct) | |
| for i := 0; i < n; i++ { | |
| fields = append(fields, v.Block.NewValue0(v.Pos, ssaop.OpPhi, t.FieldType(i))) | |
| } | |
| for _, a := range v.Args { | |
| for i := 0; i < n; i++ { | |
| fields[i].AddArg(a.Block.NewValue1I(v.Pos, ssaop.OpStructSelect, t.FieldType(i), int64(i), a)) | |
| } | |
| } | |
| v.Reset(ssaop.OpStructMake) | |
| v.AddArgs(fields...) | |
| // Recursively decompose phis for each field. | |
| for _, f := range fields { | |
| decomposeUserPhi(f) | |
| } | |
| } | |
| // decomposeArrayPhi replaces phi-of-array with arraymake(phi-of-array-element), | |
| // and then recursively decomposes the element phi. | |
| func decomposeArrayPhi(v *ssa.Value) { | |
| t := v.Type | |
| if t.Size() == 0 { | |
| v.Reset(ssaop.OpEmpty) | |
| return | |
| } | |
| if t.NumElem() != 1 { | |
| v.Fatalf("SSAable array must have no more than 1 element") | |
| } | |
| elem := v.Block.NewValue0(v.Pos, ssaop.OpPhi, t.Elem()) | |
| for _, a := range v.Args { | |
| elem.AddArg(a.Block.NewValue1I(v.Pos, ssaop.OpArraySelect, t.Elem(), 0, a)) | |
| } | |
| v.Reset(ssaop.OpArrayMake1) | |
| v.AddArg(elem) | |
| // Recursively decompose elem phi. | |
| decomposeUserPhi(elem) | |
| } | |
| type namedVal struct { | |
| locIndex, valIndex int // f.NamedValues[f.Names[locIndex]][valIndex] = key | |
| } | |
| // deleteNamedVals removes particular values with debugger names from f's naming data structures, | |
| // removes all values with OpInvalid, and re-sorts the list of Names. | |
| func deleteNamedVals(f *ssa.Func, toDelete []namedVal) { | |
| // Arrange to delete from larger indices to smaller, to ensure swap-with-end deletion does not invalidate pending indices. | |
| slices.SortFunc(toDelete, func(a, b namedVal) int { | |
| if a.locIndex != b.locIndex { | |
| return cmp.Compare(b.locIndex, a.locIndex) | |
| } | |
| return cmp.Compare(b.valIndex, a.valIndex) | |
| }) | |
| // Get rid of obsolete names | |
| for _, d := range toDelete { | |
| loc := f.Names[d.locIndex] | |
| vals := f.NamedValues[loc] | |
| l := len(vals) - 1 | |
| if l > 0 { | |
| vals[d.valIndex] = vals[l] | |
| } | |
| vals[l] = nil | |
| f.NamedValues[loc] = vals[:l] | |
| } | |
| // Delete locations with no values attached. | |
| end := len(f.Names) | |
| for i := len(f.Names) - 1; i >= 0; i-- { | |
| loc := f.Names[i] | |
| vals := f.NamedValues[loc] | |
| last := len(vals) | |
| for j := len(vals) - 1; j >= 0; j-- { | |
| if vals[j].Op == ssaop.OpInvalid { | |
| last-- | |
| vals[j] = vals[last] | |
| vals[last] = nil | |
| } | |
| } | |
| if last < len(vals) { | |
| f.NamedValues[loc] = vals[:last] | |
| } | |
| if len(vals) == 0 { | |
| delete(f.NamedValues, loc) | |
| end-- | |
| f.Names[i] = f.Names[end] | |
| f.Names[end] = ssa.LocalSlot{} | |
| } | |
| } | |
| f.Names = f.Names[:end] | |
| } | |
| func isStructNotSIMD(t *types.Type) bool { | |
| return t.IsStruct() && !t.IsSIMD() | |
| } |