github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/escape/call.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/base" 9 "github.com/go-asm/go/cmd/compile/ir" 10 "github.com/go-asm/go/cmd/compile/typecheck" 11 "github.com/go-asm/go/cmd/compile/types" 12 "github.com/go-asm/go/cmd/src" 13 ) 14 15 // call evaluates a call expressions, including builtin calls. ks 16 // should contain the holes representing where the function callee's 17 // results flows. 18 func (e *escape) call(ks []hole, call ir.Node) { 19 argument := func(k hole, arg ir.Node) { 20 // TODO(mdempsky): Should be "call argument". 21 e.expr(k.note(call, "call parameter"), arg) 22 } 23 24 switch call.Op() { 25 default: 26 ir.Dump("esc", call) 27 base.Fatalf("unexpected call op: %v", call.Op()) 28 29 case ir.OCALLFUNC, ir.OCALLINTER: 30 call := call.(*ir.CallExpr) 31 typecheck.AssertFixedCall(call) 32 33 // Pick out the function callee, if statically known. 34 // 35 // TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some 36 // functions (e.g., runtime builtins, method wrappers, generated 37 // eq/hash functions) don't have it set. Investigate whether 38 // that's a concern. 39 var fn *ir.Name 40 switch call.Op() { 41 case ir.OCALLFUNC: 42 v := ir.StaticValue(call.Fun) 43 fn = ir.StaticCalleeName(v) 44 } 45 46 fntype := call.Fun.Type() 47 if fn != nil { 48 fntype = fn.Type() 49 } 50 51 if ks != nil && fn != nil && e.inMutualBatch(fn) { 52 for i, result := range fn.Type().Results() { 53 e.expr(ks[i], result.Nname.(*ir.Name)) 54 } 55 } 56 57 var recvArg ir.Node 58 if call.Op() == ir.OCALLFUNC { 59 // Evaluate callee function expression. 60 calleeK := e.discardHole() 61 if fn == nil { // unknown callee 62 for _, k := range ks { 63 if k.dst != &e.blankLoc { 64 // The results flow somewhere, but we don't statically 65 // know the callee function. If a closure flows here, we 66 // need to conservatively assume its results might flow to 67 // the heap. 68 calleeK = e.calleeHole().note(call, "callee operand") 69 break 70 } 71 } 72 } 73 e.expr(calleeK, call.Fun) 74 } else { 75 recvArg = call.Fun.(*ir.SelectorExpr).X 76 } 77 78 // argumentParam handles escape analysis of assigning a call 79 // argument to its corresponding parameter. 80 argumentParam := func(param *types.Field, arg ir.Node) { 81 e.rewriteArgument(arg, call, fn) 82 argument(e.tagHole(ks, fn, param), arg) 83 } 84 85 args := call.Args 86 if recvParam := fntype.Recv(); recvParam != nil { 87 if recvArg == nil { 88 // Function call using method expression. Receiver argument is 89 // at the front of the regular arguments list. 90 recvArg, args = args[0], args[1:] 91 } 92 93 argumentParam(recvParam, recvArg) 94 } 95 96 for i, param := range fntype.Params() { 97 argumentParam(param, args[i]) 98 } 99 100 case ir.OINLCALL: 101 call := call.(*ir.InlinedCallExpr) 102 e.stmts(call.Body) 103 for i, result := range call.ReturnVars { 104 k := e.discardHole() 105 if ks != nil { 106 k = ks[i] 107 } 108 e.expr(k, result) 109 } 110 111 case ir.OAPPEND: 112 call := call.(*ir.CallExpr) 113 args := call.Args 114 115 // Appendee slice may flow directly to the result, if 116 // it has enough capacity. Alternatively, a new heap 117 // slice might be allocated, and all slice elements 118 // might flow to heap. 119 appendeeK := e.teeHole(ks[0], e.mutatorHole()) 120 if args[0].Type().Elem().HasPointers() { 121 appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) 122 } 123 argument(appendeeK, args[0]) 124 125 if call.IsDDD { 126 appendedK := e.discardHole() 127 if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() { 128 appendedK = e.heapHole().deref(call, "appended slice...") 129 } 130 argument(appendedK, args[1]) 131 } else { 132 for i := 1; i < len(args); i++ { 133 argument(e.heapHole(), args[i]) 134 } 135 } 136 e.discard(call.RType) 137 138 case ir.OCOPY: 139 call := call.(*ir.BinaryExpr) 140 argument(e.mutatorHole(), call.X) 141 142 copiedK := e.discardHole() 143 if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() { 144 copiedK = e.heapHole().deref(call, "copied slice") 145 } 146 argument(copiedK, call.Y) 147 e.discard(call.RType) 148 149 case ir.OPANIC: 150 call := call.(*ir.UnaryExpr) 151 argument(e.heapHole(), call.X) 152 153 case ir.OCOMPLEX: 154 call := call.(*ir.BinaryExpr) 155 e.discard(call.X) 156 e.discard(call.Y) 157 158 case ir.ODELETE, ir.OPRINT, ir.OPRINTLN, ir.ORECOVERFP: 159 call := call.(*ir.CallExpr) 160 for _, arg := range call.Args { 161 e.discard(arg) 162 } 163 e.discard(call.RType) 164 165 case ir.OMIN, ir.OMAX: 166 call := call.(*ir.CallExpr) 167 for _, arg := range call.Args { 168 argument(ks[0], arg) 169 } 170 e.discard(call.RType) 171 172 case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: 173 call := call.(*ir.UnaryExpr) 174 e.discard(call.X) 175 176 case ir.OCLEAR: 177 call := call.(*ir.UnaryExpr) 178 argument(e.mutatorHole(), call.X) 179 180 case ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA: 181 call := call.(*ir.UnaryExpr) 182 argument(ks[0], call.X) 183 184 case ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING: 185 call := call.(*ir.BinaryExpr) 186 argument(ks[0], call.X) 187 e.discard(call.Y) 188 e.discard(call.RType) 189 } 190 } 191 192 // goDeferStmt analyzes a "go" or "defer" statement. 193 func (e *escape) goDeferStmt(n *ir.GoDeferStmt) { 194 k := e.heapHole() 195 if n.Op() == ir.ODEFER && e.loopDepth == 1 && n.DeferAt == nil { 196 // Top-level defer arguments don't escape to the heap, 197 // but they do need to last until they're invoked. 198 k = e.later(e.discardHole()) 199 200 // force stack allocation of defer record, unless 201 // open-coded defers are used (see ssa.go) 202 n.SetEsc(ir.EscNever) 203 } 204 205 // If the function is already a zero argument/result function call, 206 // just escape analyze it normally. 207 // 208 // Note that the runtime is aware of this optimization for 209 // "go" statements that start in reflect.makeFuncStub or 210 // reflect.methodValueCall. 211 212 call, ok := n.Call.(*ir.CallExpr) 213 if !ok || call.Op() != ir.OCALLFUNC { 214 base.FatalfAt(n.Pos(), "expected function call: %v", n.Call) 215 } 216 if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() != 0 { 217 base.FatalfAt(n.Pos(), "expected signature without parameters or results: %v", sig) 218 } 219 220 if clo, ok := call.Fun.(*ir.ClosureExpr); ok && n.Op() == ir.OGO { 221 clo.IsGoWrap = true 222 } 223 224 e.expr(k, call.Fun) 225 } 226 227 // rewriteArgument rewrites the argument arg of the given call expression. 228 // fn is the static callee function, if known. 229 func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) { 230 if fn == nil || fn.Func == nil { 231 return 232 } 233 pragma := fn.Func.Pragma 234 if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 { 235 return 236 } 237 238 // unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like 239 // functions, so that ptr is kept alive and/or escaped as 240 // appropriate. unsafeUintptr also reports whether it modified arg0. 241 unsafeUintptr := func(arg ir.Node) { 242 // If the argument is really a pointer being converted to uintptr, 243 // arrange for the pointer to be kept alive until the call 244 // returns, by copying it into a temp and marking that temp still 245 // alive when we pop the temp stack. 246 conv, ok := arg.(*ir.ConvExpr) 247 if !ok || conv.Op() != ir.OCONVNOP { 248 return // not a conversion 249 } 250 if !conv.X.Type().IsUnsafePtr() || !conv.Type().IsUintptr() { 251 return // not an unsafe.Pointer->uintptr conversion 252 } 253 254 // Create and declare a new pointer-typed temp variable. 255 // 256 // TODO(mdempsky): This potentially violates the Go spec's order 257 // of evaluations, by evaluating arg.X before any other 258 // operands. 259 tmp := e.copyExpr(conv.Pos(), conv.X, call.PtrInit()) 260 conv.X = tmp 261 262 k := e.mutatorHole() 263 if pragma&ir.UintptrEscapes != 0 { 264 k = e.heapHole().note(conv, "//go:uintptrescapes") 265 } 266 e.flow(k, e.oldLoc(tmp)) 267 268 if pragma&ir.UintptrKeepAlive != 0 { 269 tmp.SetAddrtaken(true) // ensure SSA keeps the tmp variable 270 call.KeepAlive = append(call.KeepAlive, tmp) 271 } 272 } 273 274 // For variadic functions, the compiler has already rewritten: 275 // 276 // f(a, b, c) 277 // 278 // to: 279 // 280 // f([]T{a, b, c}...) 281 // 282 // So we need to look into slice elements to handle uintptr(ptr) 283 // arguments to variadic syscall-like functions correctly. 284 if arg.Op() == ir.OSLICELIT { 285 list := arg.(*ir.CompLitExpr).List 286 for _, el := range list { 287 if el.Op() == ir.OKEY { 288 el = el.(*ir.KeyExpr).Value 289 } 290 unsafeUintptr(el) 291 } 292 } else { 293 unsafeUintptr(arg) 294 } 295 } 296 297 // copyExpr creates and returns a new temporary variable within fn; 298 // appends statements to init to declare and initialize it to expr; 299 // and escape analyzes the data flow. 300 func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name { 301 if ir.HasUniquePos(expr) { 302 pos = expr.Pos() 303 } 304 305 tmp := typecheck.TempAt(pos, e.curfn, expr.Type()) 306 307 stmts := []ir.Node{ 308 ir.NewDecl(pos, ir.ODCL, tmp), 309 ir.NewAssignStmt(pos, tmp, expr), 310 } 311 typecheck.Stmts(stmts) 312 init.Append(stmts...) 313 314 e.newLoc(tmp, true) 315 e.stmts(stmts) 316 317 return tmp 318 } 319 320 // tagHole returns a hole for evaluating an argument passed to param. 321 // ks should contain the holes representing where the function 322 // callee's results flows. fn is the statically-known callee function, 323 // if any. 324 func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole { 325 // If this is a dynamic call, we can't rely on param.Note. 326 if fn == nil { 327 return e.heapHole() 328 } 329 330 if e.inMutualBatch(fn) { 331 if param.Nname == nil { 332 return e.discardHole() 333 } 334 return e.addr(param.Nname.(*ir.Name)) 335 } 336 337 // Call to previously tagged function. 338 339 var tagKs []hole 340 esc := parseLeaks(param.Note) 341 342 if x := esc.Heap(); x >= 0 { 343 tagKs = append(tagKs, e.heapHole().shift(x)) 344 } 345 if x := esc.Mutator(); x >= 0 { 346 tagKs = append(tagKs, e.mutatorHole().shift(x)) 347 } 348 if x := esc.Callee(); x >= 0 { 349 tagKs = append(tagKs, e.calleeHole().shift(x)) 350 } 351 352 if ks != nil { 353 for i := 0; i < numEscResults; i++ { 354 if x := esc.Result(i); x >= 0 { 355 tagKs = append(tagKs, ks[i].shift(x)) 356 } 357 } 358 } 359 360 return e.teeHole(tagKs...) 361 }