github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/escape/utils.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package escape 6 7 import ( 8 "github.com/go-asm/go/cmd/compile/ir" 9 "github.com/go-asm/go/cmd/compile/typecheck" 10 "github.com/go-asm/go/cmd/compile/types" 11 ) 12 13 func isSliceSelfAssign(dst, src ir.Node) bool { 14 // Detect the following special case. 15 // 16 // func (b *Buffer) Foo() { 17 // n, m := ... 18 // b.buf = b.buf[n:m] 19 // } 20 // 21 // This assignment is a no-op for escape analysis, 22 // it does not store any new pointers into b that were not already there. 23 // However, without this special case b will escape, because we assign to OIND/ODOTPTR. 24 // Here we assume that the statement will not contain calls, 25 // that is, that order will move any calls to init. 26 // Otherwise base ONAME value could change between the moments 27 // when we evaluate it for dst and for src. 28 29 // dst is ONAME dereference. 30 var dstX ir.Node 31 switch dst.Op() { 32 default: 33 return false 34 case ir.ODEREF: 35 dst := dst.(*ir.StarExpr) 36 dstX = dst.X 37 case ir.ODOTPTR: 38 dst := dst.(*ir.SelectorExpr) 39 dstX = dst.X 40 } 41 if dstX.Op() != ir.ONAME { 42 return false 43 } 44 // src is a slice operation. 45 switch src.Op() { 46 case ir.OSLICE, ir.OSLICE3, ir.OSLICESTR: 47 // OK. 48 case ir.OSLICEARR, ir.OSLICE3ARR: 49 // Since arrays are embedded into containing object, 50 // slice of non-pointer array will introduce a new pointer into b that was not already there 51 // (pointer to b itself). After such assignment, if b contents escape, 52 // b escapes as well. If we ignore such OSLICEARR, we will conclude 53 // that b does not escape when b contents do. 54 // 55 // Pointer to an array is OK since it's not stored inside b directly. 56 // For slicing an array (not pointer to array), there is an implicit OADDR. 57 // We check that to determine non-pointer array slicing. 58 src := src.(*ir.SliceExpr) 59 if src.X.Op() == ir.OADDR { 60 return false 61 } 62 default: 63 return false 64 } 65 // slice is applied to ONAME dereference. 66 var baseX ir.Node 67 switch base := src.(*ir.SliceExpr).X; base.Op() { 68 default: 69 return false 70 case ir.ODEREF: 71 base := base.(*ir.StarExpr) 72 baseX = base.X 73 case ir.ODOTPTR: 74 base := base.(*ir.SelectorExpr) 75 baseX = base.X 76 } 77 if baseX.Op() != ir.ONAME { 78 return false 79 } 80 // dst and src reference the same base ONAME. 81 return dstX.(*ir.Name) == baseX.(*ir.Name) 82 } 83 84 // isSelfAssign reports whether assignment from src to dst can 85 // be ignored by the escape analysis as it's effectively a self-assignment. 86 func isSelfAssign(dst, src ir.Node) bool { 87 if isSliceSelfAssign(dst, src) { 88 return true 89 } 90 91 // Detect trivial assignments that assign back to the same object. 92 // 93 // It covers these cases: 94 // val.x = val.y 95 // val.x[i] = val.y[j] 96 // val.x1.x2 = val.x1.y2 97 // ... etc 98 // 99 // These assignments do not change assigned object lifetime. 100 101 if dst == nil || src == nil || dst.Op() != src.Op() { 102 return false 103 } 104 105 // The expression prefix must be both "safe" and identical. 106 switch dst.Op() { 107 case ir.ODOT, ir.ODOTPTR: 108 // Safe trailing accessors that are permitted to differ. 109 dst := dst.(*ir.SelectorExpr) 110 src := src.(*ir.SelectorExpr) 111 return ir.SameSafeExpr(dst.X, src.X) 112 case ir.OINDEX: 113 dst := dst.(*ir.IndexExpr) 114 src := src.(*ir.IndexExpr) 115 if mayAffectMemory(dst.Index) || mayAffectMemory(src.Index) { 116 return false 117 } 118 return ir.SameSafeExpr(dst.X, src.X) 119 default: 120 return false 121 } 122 } 123 124 // mayAffectMemory reports whether evaluation of n may affect the program's 125 // memory state. If the expression can't affect memory state, then it can be 126 // safely ignored by the escape analysis. 127 func mayAffectMemory(n ir.Node) bool { 128 // We may want to use a list of "memory safe" ops instead of generally 129 // "side-effect free", which would include all calls and other ops that can 130 // allocate or change global state. For now, it's safer to start with the latter. 131 // 132 // We're ignoring things like division by zero, index out of range, 133 // and nil pointer dereference here. 134 135 // TODO(rsc): It seems like it should be possible to replace this with 136 // an ir.Any looking for any op that's not the ones in the case statement. 137 // But that produces changes in the compiled output detected by buildall. 138 switch n.Op() { 139 case ir.ONAME, ir.OLITERAL, ir.ONIL: 140 return false 141 142 case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD: 143 n := n.(*ir.BinaryExpr) 144 return mayAffectMemory(n.X) || mayAffectMemory(n.Y) 145 146 case ir.OINDEX: 147 n := n.(*ir.IndexExpr) 148 return mayAffectMemory(n.X) || mayAffectMemory(n.Index) 149 150 case ir.OCONVNOP, ir.OCONV: 151 n := n.(*ir.ConvExpr) 152 return mayAffectMemory(n.X) 153 154 case ir.OLEN, ir.OCAP, ir.ONOT, ir.OBITNOT, ir.OPLUS, ir.ONEG: 155 n := n.(*ir.UnaryExpr) 156 return mayAffectMemory(n.X) 157 158 case ir.ODOT, ir.ODOTPTR: 159 n := n.(*ir.SelectorExpr) 160 return mayAffectMemory(n.X) 161 162 case ir.ODEREF: 163 n := n.(*ir.StarExpr) 164 return mayAffectMemory(n.X) 165 166 default: 167 return true 168 } 169 } 170 171 // HeapAllocReason returns the reason the given Node must be heap 172 // allocated, or the empty string if it doesn't. 173 func HeapAllocReason(n ir.Node) string { 174 if n == nil || n.Type() == nil { 175 return "" 176 } 177 178 // Parameters are always passed via the stack. 179 if n.Op() == ir.ONAME { 180 n := n.(*ir.Name) 181 if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { 182 return "" 183 } 184 } 185 186 if n.Type().Size() > ir.MaxStackVarSize { 187 return "too large for stack" 188 } 189 if n.Type().Alignment() > int64(types.PtrSize) { 190 return "too aligned for stack" 191 } 192 193 if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Size() > ir.MaxImplicitStackVarSize { 194 return "too large for stack" 195 } 196 if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Alignment() > int64(types.PtrSize) { 197 return "too aligned for stack" 198 } 199 200 if n.Op() == ir.OCLOSURE && typecheck.ClosureType(n.(*ir.ClosureExpr)).Size() > ir.MaxImplicitStackVarSize { 201 return "too large for stack" 202 } 203 if n.Op() == ir.OMETHVALUE && typecheck.MethodValueType(n.(*ir.SelectorExpr)).Size() > ir.MaxImplicitStackVarSize { 204 return "too large for stack" 205 } 206 207 if n.Op() == ir.OMAKESLICE { 208 n := n.(*ir.MakeExpr) 209 r := n.Cap 210 if r == nil { 211 r = n.Len 212 } 213 if !ir.IsSmallIntConst(r) { 214 return "non-constant size" 215 } 216 if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() { 217 return "too large for stack" 218 } 219 } 220 221 return "" 222 }