1// Copyright 2020 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 5package ir 6 7import ( 8 "cmd/compile/internal/base" 9 "cmd/compile/internal/types" 10 "cmd/internal/obj" 11 "cmd/internal/objabi" 12 "cmd/internal/src" 13 "fmt" 14 "strings" 15 "unicode/utf8" 16) 17 18// A Func corresponds to a single function in a Go program 19// (and vice versa: each function is denoted by exactly one *Func). 20// 21// There are multiple nodes that represent a Func in the IR. 22// 23// The ONAME node (Func.Nname) is used for plain references to it. 24// The ODCLFUNC node (the Func itself) is used for its declaration code. 25// The OCLOSURE node (Func.OClosure) is used for a reference to a 26// function literal. 27// 28// An imported function will have an ONAME node which points to a Func 29// with an empty body. 30// A declared function or method has an ODCLFUNC (the Func itself) and an ONAME. 31// A function literal is represented directly by an OCLOSURE, but it also 32// has an ODCLFUNC (and a matching ONAME) representing the compiled 33// underlying form of the closure, which accesses the captured variables 34// using a special data structure passed in a register. 35// 36// A method declaration is represented like functions, except f.Sym 37// will be the qualified method name (e.g., "T.m"). 38// 39// A method expression (T.M) is represented as an OMETHEXPR node, 40// in which n.Left and n.Right point to the type and method, respectively. 41// Each distinct mention of a method expression in the source code 42// constructs a fresh node. 43// 44// A method value (t.M) is represented by ODOTMETH/ODOTINTER 45// when it is called directly and by OMETHVALUE otherwise. 46// These are like method expressions, except that for ODOTMETH/ODOTINTER, 47// the method name is stored in Sym instead of Right. 48// Each OMETHVALUE ends up being implemented as a new 49// function, a bit like a closure, with its own ODCLFUNC. 50// The OMETHVALUE uses n.Func to record the linkage to 51// the generated ODCLFUNC, but there is no 52// pointer from the Func back to the OMETHVALUE. 53type Func struct { 54 miniNode 55 Body Nodes 56 57 Nname *Name // ONAME node 58 OClosure *ClosureExpr // OCLOSURE node 59 60 // ONAME nodes for all params/locals for this func/closure, does NOT 61 // include closurevars until transforming closures during walk. 62 // Names must be listed PPARAMs, PPARAMOUTs, then PAUTOs, 63 // with PPARAMs and PPARAMOUTs in order corresponding to the function signature. 64 // Anonymous and blank params are declared as ~pNN (for PPARAMs) and ~rNN (for PPARAMOUTs). 65 Dcl []*Name 66 67 // ClosureVars lists the free variables that are used within a 68 // function literal, but formally declared in an enclosing 69 // function. The variables in this slice are the closure function's 70 // own copy of the variables, which are used within its function 71 // body. They will also each have IsClosureVar set, and will have 72 // Byval set if they're captured by value. 73 ClosureVars []*Name 74 75 // Enclosed functions that need to be compiled. 76 // Populated during walk. 77 Closures []*Func 78 79 // Parents records the parent scope of each scope within a 80 // function. The root scope (0) has no parent, so the i'th 81 // scope's parent is stored at Parents[i-1]. 82 Parents []ScopeID 83 84 // Marks records scope boundary changes. 85 Marks []Mark 86 87 FieldTrack map[*obj.LSym]struct{} 88 DebugInfo interface{} 89 LSym *obj.LSym // Linker object in this function's native ABI (Func.ABI) 90 91 Inl *Inline 92 93 // RangeParent, if non-nil, is the first non-range body function containing 94 // the closure for the body of a range function. 95 RangeParent *Func 96 97 // funcLitGen, rangeLitGen and goDeferGen track how many closures have been 98 // created in this function for function literals, range-over-func loops, 99 // and go/defer wrappers, respectively. Used by closureName for creating 100 // unique function names. 101 // Tracking goDeferGen separately avoids wrappers throwing off 102 // function literal numbering (e.g., runtime/trace_test.TestTraceSymbolize.func11). 103 funcLitGen int32 104 rangeLitGen int32 105 goDeferGen int32 106 107 Label int32 // largest auto-generated label in this function 108 109 Endlineno src.XPos 110 WBPos src.XPos // position of first write barrier; see SetWBPos 111 112 Pragma PragmaFlag // go:xxx function annotations 113 114 flags bitset16 115 116 // ABI is a function's "definition" ABI. This is the ABI that 117 // this function's generated code is expecting to be called by. 118 // 119 // For most functions, this will be obj.ABIInternal. It may be 120 // a different ABI for functions defined in assembly or ABI wrappers. 121 // 122 // This is included in the export data and tracked across packages. 123 ABI obj.ABI 124 // ABIRefs is the set of ABIs by which this function is referenced. 125 // For ABIs other than this function's definition ABI, the 126 // compiler generates ABI wrapper functions. This is only tracked 127 // within a package. 128 ABIRefs obj.ABISet 129 130 NumDefers int32 // number of defer calls in the function 131 NumReturns int32 // number of explicit returns in the function 132 133 // NWBRCalls records the LSyms of functions called by this 134 // function for go:nowritebarrierrec analysis. Only filled in 135 // if nowritebarrierrecCheck != nil. 136 NWBRCalls *[]SymAndPos 137 138 // For wrapper functions, WrappedFunc point to the original Func. 139 // Currently only used for go/defer wrappers. 140 WrappedFunc *Func 141 142 // WasmImport is used by the //go:wasmimport directive to store info about 143 // a WebAssembly function import. 144 WasmImport *WasmImport 145} 146 147// WasmImport stores metadata associated with the //go:wasmimport pragma. 148type WasmImport struct { 149 Module string 150 Name string 151} 152 153// NewFunc returns a new Func with the given name and type. 154// 155// fpos is the position of the "func" token, and npos is the position 156// of the name identifier. 157// 158// TODO(mdempsky): I suspect there's no need for separate fpos and 159// npos. 160func NewFunc(fpos, npos src.XPos, sym *types.Sym, typ *types.Type) *Func { 161 name := NewNameAt(npos, sym, typ) 162 name.Class = PFUNC 163 sym.SetFunc(true) 164 165 fn := &Func{Nname: name} 166 fn.pos = fpos 167 fn.op = ODCLFUNC 168 // Most functions are ABIInternal. The importer or symabis 169 // pass may override this. 170 fn.ABI = obj.ABIInternal 171 fn.SetTypecheck(1) 172 173 name.Func = fn 174 175 return fn 176} 177 178func (f *Func) isStmt() {} 179 180func (n *Func) copy() Node { panic(n.no("copy")) } 181func (n *Func) doChildren(do func(Node) bool) bool { return doNodes(n.Body, do) } 182func (n *Func) editChildren(edit func(Node) Node) { editNodes(n.Body, edit) } 183func (n *Func) editChildrenWithHidden(edit func(Node) Node) { editNodes(n.Body, edit) } 184 185func (f *Func) Type() *types.Type { return f.Nname.Type() } 186func (f *Func) Sym() *types.Sym { return f.Nname.Sym() } 187func (f *Func) Linksym() *obj.LSym { return f.Nname.Linksym() } 188func (f *Func) LinksymABI(abi obj.ABI) *obj.LSym { return f.Nname.LinksymABI(abi) } 189 190// An Inline holds fields used for function bodies that can be inlined. 191type Inline struct { 192 Cost int32 // heuristic cost of inlining this function 193 194 // Copy of Func.Dcl for use during inlining. This copy is needed 195 // because the function's Dcl may change from later compiler 196 // transformations. This field is also populated when a function 197 // from another package is imported and inlined. 198 Dcl []*Name 199 HaveDcl bool // whether we've loaded Dcl 200 201 // Function properties, encoded as a string (these are used for 202 // making inlining decisions). See cmd/compile/internal/inline/inlheur. 203 Properties string 204 205 // CanDelayResults reports whether it's safe for the inliner to delay 206 // initializing the result parameters until immediately before the 207 // "return" statement. 208 CanDelayResults bool 209} 210 211// A Mark represents a scope boundary. 212type Mark struct { 213 // Pos is the position of the token that marks the scope 214 // change. 215 Pos src.XPos 216 217 // Scope identifies the innermost scope to the right of Pos. 218 Scope ScopeID 219} 220 221// A ScopeID represents a lexical scope within a function. 222type ScopeID int32 223 224const ( 225 funcDupok = 1 << iota // duplicate definitions ok 226 funcWrapper // hide frame from users (elide in tracebacks, don't count as a frame for recover()) 227 funcABIWrapper // is an ABI wrapper (also set flagWrapper) 228 funcNeedctxt // function uses context register (has closure variables) 229 // true if closure inside a function; false if a simple function or a 230 // closure in a global variable initialization 231 funcIsHiddenClosure 232 funcIsDeadcodeClosure // true if closure is deadcode 233 funcHasDefer // contains a defer statement 234 funcNilCheckDisabled // disable nil checks when compiling this function 235 funcInlinabilityChecked // inliner has already determined whether the function is inlinable 236 funcNeverReturns // function never returns (in most cases calls panic(), os.Exit(), or equivalent) 237 funcOpenCodedDeferDisallowed // can't do open-coded defers 238 funcClosureResultsLost // closure is called indirectly and we lost track of its results; used by escape analysis 239 funcPackageInit // compiler emitted .init func for package 240) 241 242type SymAndPos struct { 243 Sym *obj.LSym // LSym of callee 244 Pos src.XPos // line of call 245} 246 247func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 } 248func (f *Func) Wrapper() bool { return f.flags&funcWrapper != 0 } 249func (f *Func) ABIWrapper() bool { return f.flags&funcABIWrapper != 0 } 250func (f *Func) Needctxt() bool { return f.flags&funcNeedctxt != 0 } 251func (f *Func) IsHiddenClosure() bool { return f.flags&funcIsHiddenClosure != 0 } 252func (f *Func) IsDeadcodeClosure() bool { return f.flags&funcIsDeadcodeClosure != 0 } 253func (f *Func) HasDefer() bool { return f.flags&funcHasDefer != 0 } 254func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled != 0 } 255func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 } 256func (f *Func) NeverReturns() bool { return f.flags&funcNeverReturns != 0 } 257func (f *Func) OpenCodedDeferDisallowed() bool { return f.flags&funcOpenCodedDeferDisallowed != 0 } 258func (f *Func) ClosureResultsLost() bool { return f.flags&funcClosureResultsLost != 0 } 259func (f *Func) IsPackageInit() bool { return f.flags&funcPackageInit != 0 } 260 261func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) } 262func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) } 263func (f *Func) SetABIWrapper(b bool) { f.flags.set(funcABIWrapper, b) } 264func (f *Func) SetNeedctxt(b bool) { f.flags.set(funcNeedctxt, b) } 265func (f *Func) SetIsHiddenClosure(b bool) { f.flags.set(funcIsHiddenClosure, b) } 266func (f *Func) SetIsDeadcodeClosure(b bool) { f.flags.set(funcIsDeadcodeClosure, b) } 267func (f *Func) SetHasDefer(b bool) { f.flags.set(funcHasDefer, b) } 268func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled, b) } 269func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) } 270func (f *Func) SetNeverReturns(b bool) { f.flags.set(funcNeverReturns, b) } 271func (f *Func) SetOpenCodedDeferDisallowed(b bool) { f.flags.set(funcOpenCodedDeferDisallowed, b) } 272func (f *Func) SetClosureResultsLost(b bool) { f.flags.set(funcClosureResultsLost, b) } 273func (f *Func) SetIsPackageInit(b bool) { f.flags.set(funcPackageInit, b) } 274 275func (f *Func) SetWBPos(pos src.XPos) { 276 if base.Debug.WB != 0 { 277 base.WarnfAt(pos, "write barrier") 278 } 279 if !f.WBPos.IsKnown() { 280 f.WBPos = pos 281 } 282} 283 284// FuncName returns the name (without the package) of the function f. 285func FuncName(f *Func) string { 286 if f == nil || f.Nname == nil { 287 return "<nil>" 288 } 289 return f.Sym().Name 290} 291 292// PkgFuncName returns the name of the function referenced by f, with package 293// prepended. 294// 295// This differs from the compiler's internal convention where local functions 296// lack a package. This is primarily useful when the ultimate consumer of this 297// is a human looking at message. 298func PkgFuncName(f *Func) string { 299 if f == nil || f.Nname == nil { 300 return "<nil>" 301 } 302 s := f.Sym() 303 pkg := s.Pkg 304 305 return pkg.Path + "." + s.Name 306} 307 308// LinkFuncName returns the name of the function f, as it will appear in the 309// symbol table of the final linked binary. 310func LinkFuncName(f *Func) string { 311 if f == nil || f.Nname == nil { 312 return "<nil>" 313 } 314 s := f.Sym() 315 pkg := s.Pkg 316 317 return objabi.PathToPrefix(pkg.Path) + "." + s.Name 318} 319 320// ParseLinkFuncName parsers a symbol name (as returned from LinkFuncName) back 321// to the package path and local symbol name. 322func ParseLinkFuncName(name string) (pkg, sym string, err error) { 323 pkg, sym = splitPkg(name) 324 if pkg == "" { 325 return "", "", fmt.Errorf("no package path in name") 326 } 327 328 pkg, err = objabi.PrefixToPath(pkg) // unescape 329 if err != nil { 330 return "", "", fmt.Errorf("malformed package path: %v", err) 331 } 332 333 return pkg, sym, nil 334} 335 336// Borrowed from x/mod. 337func modPathOK(r rune) bool { 338 if r < utf8.RuneSelf { 339 return r == '-' || r == '.' || r == '_' || r == '~' || 340 '0' <= r && r <= '9' || 341 'A' <= r && r <= 'Z' || 342 'a' <= r && r <= 'z' 343 } 344 return false 345} 346 347func escapedImportPathOK(r rune) bool { 348 return modPathOK(r) || r == '+' || r == '/' || r == '%' 349} 350 351// splitPkg splits the full linker symbol name into package and local symbol 352// name. 353func splitPkg(name string) (pkgpath, sym string) { 354 // package-sym split is at first dot after last the / that comes before 355 // any characters illegal in a package path. 356 357 lastSlashIdx := 0 358 for i, r := range name { 359 // Catches cases like: 360 // * example.foo[sync/atomic.Uint64]. 361 // * example%2ecom.foo[sync/atomic.Uint64]. 362 // 363 // Note that name is still escaped; unescape occurs after splitPkg. 364 if !escapedImportPathOK(r) { 365 break 366 } 367 if r == '/' { 368 lastSlashIdx = i 369 } 370 } 371 for i := lastSlashIdx; i < len(name); i++ { 372 r := name[i] 373 if r == '.' { 374 return name[:i], name[i+1:] 375 } 376 } 377 378 return "", name 379} 380 381var CurFunc *Func 382 383// WithFunc invokes do with CurFunc and base.Pos set to curfn and 384// curfn.Pos(), respectively, and then restores their previous values 385// before returning. 386func WithFunc(curfn *Func, do func()) { 387 oldfn, oldpos := CurFunc, base.Pos 388 defer func() { CurFunc, base.Pos = oldfn, oldpos }() 389 390 CurFunc, base.Pos = curfn, curfn.Pos() 391 do() 392} 393 394func FuncSymName(s *types.Sym) string { 395 return s.Name + "·f" 396} 397 398// ClosureDebugRuntimeCheck applies boilerplate checks for debug flags 399// and compiling runtime. 400func ClosureDebugRuntimeCheck(clo *ClosureExpr) { 401 if base.Debug.Closure > 0 { 402 if clo.Esc() == EscHeap { 403 base.WarnfAt(clo.Pos(), "heap closure, captured vars = %v", clo.Func.ClosureVars) 404 } else { 405 base.WarnfAt(clo.Pos(), "stack closure, captured vars = %v", clo.Func.ClosureVars) 406 } 407 } 408 if base.Flag.CompilingRuntime && clo.Esc() == EscHeap && !clo.IsGoWrap { 409 base.ErrorfAt(clo.Pos(), 0, "heap-allocated closure %s, not allowed in runtime", FuncName(clo.Func)) 410 } 411} 412 413// IsTrivialClosure reports whether closure clo has an 414// empty list of captured vars. 415func IsTrivialClosure(clo *ClosureExpr) bool { 416 return len(clo.Func.ClosureVars) == 0 417} 418 419// globClosgen is like Func.Closgen, but for the global scope. 420var globClosgen int32 421 422// closureName generates a new unique name for a closure within outerfn at pos. 423func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym { 424 if outerfn != nil && outerfn.OClosure != nil && outerfn.OClosure.Func.RangeParent != nil { 425 outerfn = outerfn.OClosure.Func.RangeParent 426 } 427 pkg := types.LocalPkg 428 outer := "glob." 429 var suffix string = "." 430 switch why { 431 default: 432 base.FatalfAt(pos, "closureName: bad Op: %v", why) 433 case OCLOSURE: 434 if outerfn == nil || outerfn.OClosure == nil { 435 suffix = ".func" 436 } 437 case ORANGE: 438 suffix = "-range" 439 case OGO: 440 suffix = ".gowrap" 441 case ODEFER: 442 suffix = ".deferwrap" 443 } 444 gen := &globClosgen 445 446 // There may be multiple functions named "_". In those 447 // cases, we can't use their individual Closgens as it 448 // would lead to name clashes. 449 if outerfn != nil && !IsBlank(outerfn.Nname) { 450 pkg = outerfn.Sym().Pkg 451 outer = FuncName(outerfn) 452 453 switch why { 454 case OCLOSURE: 455 gen = &outerfn.funcLitGen 456 case ORANGE: 457 gen = &outerfn.rangeLitGen 458 default: 459 gen = &outerfn.goDeferGen 460 } 461 } 462 463 // If this closure was created due to inlining, then incorporate any 464 // inlined functions' names into the closure's linker symbol name 465 // too (#60324). 466 if inlIndex := base.Ctxt.InnermostPos(pos).Base().InliningIndex(); inlIndex >= 0 { 467 names := []string{outer} 468 base.Ctxt.InlTree.AllParents(inlIndex, func(call obj.InlinedCall) { 469 names = append(names, call.Name) 470 }) 471 outer = strings.Join(names, ".") 472 } 473 474 *gen++ 475 return pkg.Lookup(fmt.Sprintf("%s%s%d", outer, suffix, *gen)) 476} 477 478// NewClosureFunc creates a new Func to represent a function literal 479// with the given type. 480// 481// fpos the position used for the underlying ODCLFUNC and ONAME, 482// whereas cpos is the position used for the OCLOSURE. They're 483// separate because in the presence of inlining, the OCLOSURE node 484// should have an inline-adjusted position, whereas the ODCLFUNC and 485// ONAME must not. 486// 487// outerfn is the enclosing function, if any. The returned function is 488// appending to pkg.Funcs. 489// 490// why is the reason we're generating this Func. It can be OCLOSURE 491// (for a normal function literal) or OGO or ODEFER (for wrapping a 492// call expression that has parameters or results). 493func NewClosureFunc(fpos, cpos src.XPos, why Op, typ *types.Type, outerfn *Func, pkg *Package) *Func { 494 fn := NewFunc(fpos, fpos, closureName(outerfn, cpos, why), typ) 495 fn.SetIsHiddenClosure(outerfn != nil) 496 if outerfn != nil { 497 fn.SetDupok(outerfn.Dupok()) // if the outer function is dupok, so is the closure 498 } 499 500 clo := &ClosureExpr{Func: fn} 501 clo.op = OCLOSURE 502 clo.pos = cpos 503 clo.SetType(typ) 504 clo.SetTypecheck(1) 505 if why == ORANGE { 506 clo.Func.RangeParent = outerfn 507 if outerfn.OClosure != nil && outerfn.OClosure.Func.RangeParent != nil { 508 clo.Func.RangeParent = outerfn.OClosure.Func.RangeParent 509 } 510 } 511 fn.OClosure = clo 512 513 fn.Nname.Defn = fn 514 pkg.Funcs = append(pkg.Funcs, fn) 515 516 return fn 517} 518 519// IsFuncPCIntrinsic returns whether n is a direct call of internal/abi.FuncPCABIxxx functions. 520func IsFuncPCIntrinsic(n *CallExpr) bool { 521 if n.Op() != OCALLFUNC || n.Fun.Op() != ONAME { 522 return false 523 } 524 fn := n.Fun.(*Name).Sym() 525 return (fn.Name == "FuncPCABI0" || fn.Name == "FuncPCABIInternal") && 526 fn.Pkg.Path == "internal/abi" 527} 528 529// IsIfaceOfFunc inspects whether n is an interface conversion from a direct 530// reference of a func. If so, it returns referenced Func; otherwise nil. 531// 532// This is only usable before walk.walkConvertInterface, which converts to an 533// OMAKEFACE. 534func IsIfaceOfFunc(n Node) *Func { 535 if n, ok := n.(*ConvExpr); ok && n.Op() == OCONVIFACE { 536 if name, ok := n.X.(*Name); ok && name.Op() == ONAME && name.Class == PFUNC { 537 return name.Func 538 } 539 } 540 return nil 541} 542 543// FuncPC returns a uintptr-typed expression that evaluates to the PC of a 544// function as uintptr, as returned by internal/abi.FuncPC{ABI0,ABIInternal}. 545// 546// n should be a Node of an interface type, as is passed to 547// internal/abi.FuncPC{ABI0,ABIInternal}. 548// 549// TODO(prattmic): Since n is simply an interface{} there is no assertion that 550// it is actually a function at all. Perhaps we should emit a runtime type 551// assertion? 552func FuncPC(pos src.XPos, n Node, wantABI obj.ABI) Node { 553 if !n.Type().IsInterface() { 554 base.ErrorfAt(pos, 0, "internal/abi.FuncPC%s expects an interface value, got %v", wantABI, n.Type()) 555 } 556 557 if fn := IsIfaceOfFunc(n); fn != nil { 558 name := fn.Nname 559 abi := fn.ABI 560 if abi != wantABI { 561 base.ErrorfAt(pos, 0, "internal/abi.FuncPC%s expects an %v function, %s is defined as %v", wantABI, wantABI, name.Sym().Name, abi) 562 } 563 var e Node = NewLinksymExpr(pos, name.LinksymABI(abi), types.Types[types.TUINTPTR]) 564 e = NewAddrExpr(pos, e) 565 e.SetType(types.Types[types.TUINTPTR].PtrTo()) 566 e = NewConvExpr(pos, OCONVNOP, types.Types[types.TUINTPTR], e) 567 e.SetTypecheck(1) 568 return e 569 } 570 // fn is not a defined function. It must be ABIInternal. 571 // Read the address from func value, i.e. *(*uintptr)(idata(fn)). 572 if wantABI != obj.ABIInternal { 573 base.ErrorfAt(pos, 0, "internal/abi.FuncPC%s does not accept func expression, which is ABIInternal", wantABI) 574 } 575 var e Node = NewUnaryExpr(pos, OIDATA, n) 576 e.SetType(types.Types[types.TUINTPTR].PtrTo()) 577 e.SetTypecheck(1) 578 e = NewStarExpr(pos, e) 579 e.SetType(types.Types[types.TUINTPTR]) 580 e.SetTypecheck(1) 581 return e 582} 583 584// DeclareParams creates Names for all of the parameters in fn's 585// signature and adds them to fn.Dcl. 586// 587// If setNname is true, then it also sets types.Field.Nname for each 588// parameter. 589func (fn *Func) DeclareParams(setNname bool) { 590 if fn.Dcl != nil { 591 base.FatalfAt(fn.Pos(), "%v already has Dcl", fn) 592 } 593 594 declareParams := func(params []*types.Field, ctxt Class, prefix string, offset int) { 595 for i, param := range params { 596 sym := param.Sym 597 if sym == nil || sym.IsBlank() { 598 sym = fn.Sym().Pkg.LookupNum(prefix, i) 599 } 600 601 name := NewNameAt(param.Pos, sym, param.Type) 602 name.Class = ctxt 603 name.Curfn = fn 604 fn.Dcl[offset+i] = name 605 606 if setNname { 607 param.Nname = name 608 } 609 } 610 } 611 612 sig := fn.Type() 613 params := sig.RecvParams() 614 results := sig.Results() 615 616 fn.Dcl = make([]*Name, len(params)+len(results)) 617 declareParams(params, PPARAM, "~p", 0) 618 declareParams(results, PPARAMOUT, "~r", len(params)) 619} 620