| // Copyright 2021 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 noder | |
| import ( | |
| "encoding/hex" | |
| "fmt" | |
| "go/constant" | |
| "internal/buildcfg" | |
| "internal/pkgbits" | |
| "path/filepath" | |
| "slices" | |
| "strings" | |
| "cmd/compile/internal/base" | |
| "cmd/compile/internal/dwarfgen" | |
| "cmd/compile/internal/inline" | |
| "cmd/compile/internal/inline/interleaved" | |
| "cmd/compile/internal/ir" | |
| "cmd/compile/internal/objw" | |
| "cmd/compile/internal/pgoir" | |
| "cmd/compile/internal/reflectdata" | |
| "cmd/compile/internal/staticinit" | |
| "cmd/compile/internal/typecheck" | |
| "cmd/compile/internal/types" | |
| "cmd/internal/hash" | |
| "cmd/internal/obj" | |
| "cmd/internal/objabi" | |
| "cmd/internal/src" | |
| ) | |
| // This file implements cmd/compile backend's reader for the Unified | |
| // IR export data. | |
| // A pkgReader reads Unified IR export data. | |
| type pkgReader struct { | |
| pkgbits.PkgDecoder | |
| // Indices for encoded things; lazily populated as needed. | |
| // | |
| // Note: Objects (i.e., ir.Names) are lazily instantiated by | |
| // populating their types.Sym.Def; see objReader below. | |
| posBases []*src.PosBase | |
| pkgs []*types.Pkg | |
| typs []*types.Type | |
| // offset for rewriting the given (absolute!) index into the output, | |
| // but bitwise inverted so we can detect if we're missing the entry | |
| // or not. | |
| newindex []index | |
| } | |
| func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader { | |
| return &pkgReader{ | |
| PkgDecoder: pr, | |
| posBases: make([]*src.PosBase, pr.NumElems(pkgbits.SectionPosBase)), | |
| pkgs: make([]*types.Pkg, pr.NumElems(pkgbits.SectionPkg)), | |
| typs: make([]*types.Type, pr.NumElems(pkgbits.SectionType)), | |
| newindex: make([]index, pr.TotalElems()), | |
| } | |
| } | |
| // A pkgReaderIndex compactly identifies an index (and its | |
| // corresponding dictionary) within a package's export data. | |
| type pkgReaderIndex struct { | |
| pr *pkgReader | |
| idx index | |
| dict *readerDict | |
| methodSym *types.Sym | |
| synthetic func(pos src.XPos, r *reader) | |
| } | |
| func (pri pkgReaderIndex) asReader(k pkgbits.SectionKind, marker pkgbits.SyncMarker) *reader { | |
| if pri.synthetic != nil { | |
| return &reader{synthetic: pri.synthetic} | |
| } | |
| r := pri.pr.newReader(k, pri.idx, marker) | |
| r.dict = pri.dict | |
| r.methodSym = pri.methodSym | |
| return r | |
| } | |
| func (pr *pkgReader) newReader(k pkgbits.SectionKind, idx index, marker pkgbits.SyncMarker) *reader { | |
| return &reader{ | |
| Decoder: pr.NewDecoder(k, idx, marker), | |
| p: pr, | |
| } | |
| } | |
| // A reader provides APIs for reading an individual element. | |
| type reader struct { | |
| pkgbits.Decoder | |
| p *pkgReader | |
| dict *readerDict | |
| // funcLitGen is a counter for closure names. | |
| funcLitGen int | |
| // rangeLitGen is a counter for range func closure names. | |
| rangeLitGen int | |
| // TODO(mdempsky): The state below is all specific to reading | |
| // function bodies. It probably makes sense to split it out | |
| // separately so that it doesn't take up space in every reader | |
| // instance. | |
| curfn *ir.Func | |
| locals []*ir.Name | |
| closureVars []*ir.Name | |
| // funarghack is used during inlining to suppress setting | |
| // Field.Nname to the inlined copies of the parameters. This is | |
| // necessary because we reuse the same types.Type as the original | |
| // function, and most of the compiler still relies on field.Nname to | |
| // find parameters/results. | |
| funarghack bool | |
| // methodSym is the name of method's name, if reading a method. | |
| // It's nil if reading a normal function or closure body. | |
| methodSym *types.Sym | |
| // dictParam is the .dict param, if any. | |
| dictParam *ir.Name | |
| // synthetic is a callback function to construct a synthetic | |
| // function body. It's used for creating the bodies of function | |
| // literals used to curry arguments to shaped functions. | |
| synthetic func(pos src.XPos, r *reader) | |
| // scopeVars is a stack tracking the number of variables declared in | |
| // the current function at the moment each open scope was opened. | |
| scopeVars []int | |
| marker dwarfgen.ScopeMarker | |
| lastCloseScopePos src.XPos | |
| // === details for handling inline body expansion === | |
| // If we're reading in a function body because of inlining, this is | |
| // the call that we're inlining for. | |
| inlCaller *ir.Func | |
| inlCall *ir.CallExpr | |
| inlFunc *ir.Func | |
| inlTreeIndex int | |
| inlPosBases map[*src.PosBase]*src.PosBase | |
| // suppressInlPos tracks whether position base rewriting for | |
| // inlining should be suppressed. See funcLit. | |
| suppressInlPos int | |
| delayResults bool | |
| // Label to return to. | |
| retlabel *types.Sym | |
| } | |
| // A readerDict represents an instantiated "compile-time dictionary," | |
| // used for resolving any derived types needed for instantiating a | |
| // generic object. | |
| // | |
| // A compile-time dictionary can either be "shaped" or "non-shaped." | |
| // Shaped compile-time dictionaries are only used for instantiating | |
| // shaped type definitions and function bodies, while non-shaped | |
| // compile-time dictionaries are used for instantiating runtime | |
| // dictionaries. | |
| type readerDict struct { | |
| shaped bool // whether this is a shaped dictionary | |
| // baseSym is the symbol for the object this dictionary belongs to. | |
| // If the object is an instantiated function or defined type, then | |
| // baseSym is the mangled symbol, including any type arguments. | |
| baseSym *types.Sym | |
| // For non-shaped dictionaries, shapedObj is a reference to the | |
| // corresponding shaped object (always a function or defined type). | |
| shapedObj *ir.Name | |
| // targs holds the implicit and explicit type arguments in use for | |
| // reading the current object. For example: | |
| // | |
| // func F[T any]() { | |
| // type X[U any] struct { t T; u U } | |
| // var _ X[string] | |
| // } | |
| // | |
| // var _ = F[int] | |
| // | |
| // While instantiating F[int], we need to in turn instantiate | |
| // X[string]. [int] and [string] are explicit type arguments for F | |
| // and X, respectively; but [int] is also the implicit type | |
| // arguments for X. | |
| // | |
| // (As an analogy to function literals, explicits are the function | |
| // literal's formal parameters, while implicits are variables | |
| // captured by the function literal.) | |
| targs []*types.Type | |
| // implicits counts how many of types within targs are implicit type | |
| // arguments; the rest are explicit. | |
| implicits int | |
| // receivers counts how many of types within targs are receiver type | |
| // arguments; they are explicit. | |
| receivers int | |
| derived []derivedInfo // reloc index of the derived type's descriptor | |
| derivedTypes []*types.Type // slice of previously computed derived types | |
| // These slices correspond to entries in the runtime dictionary. | |
| typeParamMethodExprs []readerMethodExprInfo | |
| subdicts []objInfo | |
| rtypes []typeInfo | |
| itabs []itabInfo | |
| } | |
| type readerMethodExprInfo struct { | |
| typeParamIdx int | |
| method *types.Sym | |
| } | |
| func setType(n ir.Node, typ *types.Type) { | |
| n.SetType(typ) | |
| n.SetTypecheck(1) | |
| } | |
| func setValue(name *ir.Name, val constant.Value) { | |
| name.SetVal(val) | |
| name.Defn = nil | |
| } | |
| // @@@ Positions | |
| // pos reads a position from the bitstream. | |
| func (r *reader) pos() src.XPos { | |
| return base.Ctxt.PosTable.XPos(r.pos0()) | |
| } | |
| // origPos reads a position from the bitstream, and returns both the | |
| // original raw position and an inlining-adjusted position. | |
| func (r *reader) origPos() (origPos, inlPos src.XPos) { | |
| r.suppressInlPos++ | |
| origPos = r.pos() | |
| r.suppressInlPos-- | |
| inlPos = r.inlPos(origPos) | |
| return | |
| } | |
| func (r *reader) pos0() src.Pos { | |
| r.Sync(pkgbits.SyncPos) | |
| if !r.Bool() { | |
| return src.NoPos | |
| } | |
| posBase := r.posBase() | |
| line := r.Uint() | |
| col := r.Uint() | |
| return src.MakePos(posBase, line, col) | |
| } | |
| // posBase reads a position base from the bitstream. | |
| func (r *reader) posBase() *src.PosBase { | |
| return r.inlPosBase(r.p.posBaseIdx(r.Reloc(pkgbits.SectionPosBase))) | |
| } | |
| // posBaseIdx returns the specified position base, reading it first if | |
| // needed. | |
| func (pr *pkgReader) posBaseIdx(idx index) *src.PosBase { | |
| if b := pr.posBases[idx]; b != nil { | |
| return b | |
| } | |
| r := pr.newReader(pkgbits.SectionPosBase, idx, pkgbits.SyncPosBase) | |
| var b *src.PosBase | |
| absFilename := r.String() | |
| filename := absFilename | |
| // For build artifact stability, the export data format only | |
| // contains the "absolute" filename as returned by objabi.AbsFile. | |
| // However, some tests (e.g., test/run.go's asmcheck tests) expect | |
| // to see the full, original filename printed out. Re-expanding | |
| // "$GOROOT" to buildcfg.GOROOT is a close-enough approximation to | |
| // satisfy this. | |
| // | |
| // The export data format only ever uses slash paths | |
| // (for cross-operating-system reproducible builds), | |
| // but error messages need to use native paths (backslash on Windows) | |
| // as if they had been specified on the command line. | |
| // (The go command always passes native paths to the compiler.) | |
| const dollarGOROOT = "$GOROOT" | |
| if buildcfg.GOROOT != "" && strings.HasPrefix(filename, dollarGOROOT) { | |
| filename = filepath.FromSlash(buildcfg.GOROOT + filename[len(dollarGOROOT):]) | |
| } | |
| if r.Bool() { | |
| b = src.NewFileBase(filename, absFilename) | |
| } else { | |
| pos := r.pos0() | |
| line := r.Uint() | |
| col := r.Uint() | |
| b = src.NewLinePragmaBase(pos, filename, absFilename, line, col) | |
| } | |
| pr.posBases[idx] = b | |
| return b | |
| } | |
| // inlPosBase returns the inlining-adjusted src.PosBase corresponding | |
| // to oldBase, which must be a non-inlined position. When not | |
| // inlining, this is just oldBase. | |
| func (r *reader) inlPosBase(oldBase *src.PosBase) *src.PosBase { | |
| if index := oldBase.InliningIndex(); index >= 0 { | |
| base.Fatalf("oldBase %v already has inlining index %v", oldBase, index) | |
| } | |
| if r.inlCall == nil || r.suppressInlPos != 0 { | |
| return oldBase | |
| } | |
| if newBase, ok := r.inlPosBases[oldBase]; ok { | |
| return newBase | |
| } | |
| newBase := src.NewInliningBase(oldBase, r.inlTreeIndex) | |
| r.inlPosBases[oldBase] = newBase | |
| return newBase | |
| } | |
| // inlPos returns the inlining-adjusted src.XPos corresponding to | |
| // xpos, which must be a non-inlined position. When not inlining, this | |
| // is just xpos. | |
| func (r *reader) inlPos(xpos src.XPos) src.XPos { | |
| pos := base.Ctxt.PosTable.Pos(xpos) | |
| pos.SetBase(r.inlPosBase(pos.Base())) | |
| return base.Ctxt.PosTable.XPos(pos) | |
| } | |
| // @@@ Packages | |
| // pkg reads a package reference from the bitstream. | |
| func (r *reader) pkg() *types.Pkg { | |
| r.Sync(pkgbits.SyncPkg) | |
| return r.p.pkgIdx(r.Reloc(pkgbits.SectionPkg)) | |
| } | |
| // pkgIdx returns the specified package from the export data, reading | |
| // it first if needed. | |
| func (pr *pkgReader) pkgIdx(idx index) *types.Pkg { | |
| if pkg := pr.pkgs[idx]; pkg != nil { | |
| return pkg | |
| } | |
| pkg := pr.newReader(pkgbits.SectionPkg, idx, pkgbits.SyncPkgDef).doPkg() | |
| pr.pkgs[idx] = pkg | |
| return pkg | |
| } | |
| // doPkg reads a package definition from the bitstream. | |
| func (r *reader) doPkg() *types.Pkg { | |
| path := r.String() | |
| switch path { | |
| case "": | |
| path = r.p.PkgPath() | |
| case "builtin": | |
| return types.BuiltinPkg | |
| case "unsafe": | |
| return types.UnsafePkg | |
| } | |
| name := r.String() | |
| pkg := types.NewPkg(path, "") | |
| if pkg.Name == "" { | |
| pkg.Name = name | |
| } else { | |
| base.Assertf(pkg.Name == name, "package %q has name %q, but want %q", pkg.Path, pkg.Name, name) | |
| } | |
| return pkg | |
| } | |
| // @@@ Types | |
| func (r *reader) typ() *types.Type { | |
| return r.typWrapped(true) | |
| } | |
| // typWrapped is like typ, but allows suppressing generation of | |
| // unnecessary wrappers as a compile-time optimization. | |
| func (r *reader) typWrapped(wrapped bool) *types.Type { | |
| return r.p.typIdx(r.typInfo(), r.dict, wrapped) | |
| } | |
| func (r *reader) typInfo() typeInfo { | |
| r.Sync(pkgbits.SyncType) | |
| if r.Bool() { | |
| return typeInfo{idx: index(r.Len()), derived: true} | |
| } | |
| return typeInfo{idx: r.Reloc(pkgbits.SectionType), derived: false} | |
| } | |
| // typListIdx returns a list of the specified types, resolving derived | |
| // types within the given dictionary. | |
| func (pr *pkgReader) typListIdx(infos []typeInfo, dict *readerDict) []*types.Type { | |
| typs := make([]*types.Type, len(infos)) | |
| for i, info := range infos { | |
| typs[i] = pr.typIdx(info, dict, true) | |
| } | |
| return typs | |
| } | |
| // typIdx returns the specified type. If info specifies a derived | |
| // type, it's resolved within the given dictionary. If wrapped is | |
| // true, then method wrappers will be generated, if appropriate. | |
| func (pr *pkgReader) typIdx(info typeInfo, dict *readerDict, wrapped bool) *types.Type { | |
| idx := info.idx | |
| var where **types.Type | |
| if info.derived { | |
| where = &dict.derivedTypes[idx] | |
| idx = dict.derived[idx].idx | |
| } else { | |
| where = &pr.typs[idx] | |
| } | |
| if typ := *where; typ != nil { | |
| return typ | |
| } | |
| r := pr.newReader(pkgbits.SectionType, idx, pkgbits.SyncTypeIdx) | |
| r.dict = dict | |
| typ := r.doTyp() | |
| if typ == nil { | |
| base.Fatalf("doTyp returned nil for info=%v", info) | |
| } | |
| // For recursive type declarations involving interfaces and aliases, | |
| // above r.doTyp() call may have already set pr.typs[idx], so just | |
| // double check and return the type. | |
| // | |
| // Example: | |
| // | |
| // type F = func(I) | |
| // | |
| // type I interface { | |
| // m(F) | |
| // } | |
| // | |
| // The writer writes data types in following index order: | |
| // | |
| // 0: func(I) | |
| // 1: I | |
| // 2: interface{m(func(I))} | |
| // | |
| // The reader resolves it in following index order: | |
| // | |
| // 0 -> 1 -> 2 -> 0 -> 1 | |
| // | |
| // and can divide in logically 2 steps: | |
| // | |
| // - 0 -> 1 : first time the reader reach type I, | |
| // it creates new named type with symbol I. | |
| // | |
| // - 2 -> 0 -> 1: the reader ends up reaching symbol I again, | |
| // now the symbol I was setup in above step, so | |
| // the reader just return the named type. | |
| // | |
| // Now, the functions called return, the pr.typs looks like below: | |
| // | |
| // - 0 -> 1 -> 2 -> 0 : [<T> I <T>] | |
| // - 0 -> 1 -> 2 : [func(I) I <T>] | |
| // - 0 -> 1 : [func(I) I interface { "".m(func("".I)) }] | |
| // | |
| // The idx 1, corresponding with type I was resolved successfully | |
| // after r.doTyp() call. | |
| if prev := *where; prev != nil { | |
| return prev | |
| } | |
| if wrapped { | |
| // Only cache if we're adding wrappers, so that other callers that | |
| // find a cached type know it was wrapped. | |
| *where = typ | |
| r.needWrapper(typ) | |
| } | |
| if !typ.IsUntyped() { | |
| types.CheckSize(typ) | |
| } | |
| return typ | |
| } | |
| func (r *reader) doTyp() *types.Type { | |
| switch tag := pkgbits.CodeType(r.Code(pkgbits.SyncType)); tag { | |
| default: | |
| panic(fmt.Sprintf("unexpected type: %v", tag)) | |
| case pkgbits.TypeBasic: | |
| return *basics[r.Len()] | |
| case pkgbits.TypeNamed: | |
| obj := r.obj() | |
| assert(obj.Op() == ir.OTYPE) | |
| return obj.Type() | |
| case pkgbits.TypeTypeParam: | |
| return r.dict.targs[r.Len()] | |
| case pkgbits.TypeArray: | |
| len := int64(r.Uint64()) | |
| return types.NewArray(r.typ(), len) | |
| case pkgbits.TypeChan: | |
| dir := dirs[r.Len()] | |
| return types.NewChan(r.typ(), dir) | |
| case pkgbits.TypeMap: | |
| return types.NewMap(r.typ(), r.typ()) | |
| case pkgbits.TypePointer: | |
| return types.NewPtr(r.typ()) | |
| case pkgbits.TypeSignature: | |
| return r.signature(nil) | |
| case pkgbits.TypeSlice: | |
| return types.NewSlice(r.typ()) | |
| case pkgbits.TypeStruct: | |
| return r.structType() | |
| case pkgbits.TypeInterface: | |
| return r.interfaceType() | |
| case pkgbits.TypeUnion: | |
| return r.unionType() | |
| } | |
| } | |
| func (r *reader) unionType() *types.Type { | |
| // In the types1 universe, we only need to handle value types. | |
| // Impure interfaces (i.e., interfaces with non-trivial type sets | |
| // like "int | string") can only appear as type parameter bounds, | |
| // and this is enforced by the types2 type checker. | |
| // | |
| // However, type unions can still appear in pure interfaces if the | |
| // type union is equivalent to "any". E.g., typeparam/issue52124.go | |
| // declares variables with the type "interface { any | int }". | |
| // | |
| // To avoid needing to represent type unions in types1 (since we | |
| // don't have any uses for that today anyway), we simply fold them | |
| // to "any". | |
| // TODO(mdempsky): Restore consistency check to make sure folding to | |
| // "any" is safe. This is unfortunately tricky, because a pure | |
| // interface can reference impure interfaces too, including | |
| // cyclically (#60117). | |
| if false { | |
| pure := false | |
| for i, n := 0, r.Len(); i < n; i++ { | |
| _ = r.Bool() // tilde | |
| term := r.typ() | |
| if term.IsEmptyInterface() { | |
| pure = true | |
| } | |
| } | |
| if !pure { | |
| base.Fatalf("impure type set used in value type") | |
| } | |
| } | |
| return types.Types[types.TINTER] | |
| } | |
| func (r *reader) interfaceType() *types.Type { | |
| nmethods, nembeddeds := r.Len(), r.Len() | |
| implicit := nmethods == 0 && nembeddeds == 1 && r.Bool() | |
| assert(!implicit) // implicit interfaces only appear in constraints | |
| fields := make([]*types.Field, nmethods+nembeddeds) | |
| methods, embeddeds := fields[:nmethods], fields[nmethods:] | |
| for i := range methods { | |
| methods[i] = types.NewField(r.pos(), r.selector(), r.signature(types.FakeRecv())) | |
| } | |
| for i := range embeddeds { | |
| embeddeds[i] = types.NewField(src.NoXPos, nil, r.typ()) | |
| } | |
| if len(fields) == 0 { | |
| return types.Types[types.TINTER] // empty interface | |
| } | |
| return types.NewInterface(fields) | |
| } | |
| func (r *reader) structType() *types.Type { | |
| fields := make([]*types.Field, r.Len()) | |
| for i := range fields { | |
| field := types.NewField(r.pos(), r.selector(), r.typ()) | |
| field.Note = r.String() | |
| if r.Bool() { | |
| field.Embedded = 1 | |
| } | |
| fields[i] = field | |
| } | |
| return types.NewStruct(fields) | |
| } | |
| func (r *reader) signature(recv *types.Field) *types.Type { | |
| r.Sync(pkgbits.SyncSignature) | |
| params := r.params() | |
| results := r.params() | |
| if r.Bool() { // variadic | |
| params[len(params)-1].SetIsDDD(true) | |
| } | |
| return types.NewSignature(recv, params, results) | |
| } | |
| func (r *reader) params() []*types.Field { | |
| r.Sync(pkgbits.SyncParams) | |
| params := make([]*types.Field, r.Len()) | |
| for i := range params { | |
| params[i] = r.param() | |
| } | |
| return params | |
| } | |
| func (r *reader) param() *types.Field { | |
| r.Sync(pkgbits.SyncParam) | |
| return types.NewField(r.pos(), r.localIdent(), r.typ()) | |
| } | |
| // @@@ Objects | |
| // objReader maps qualified identifiers (represented as *types.Sym) to | |
| // a pkgReader and corresponding index that can be used for reading | |
| // that object's definition. | |
| var objReader = map[*types.Sym]pkgReaderIndex{} | |
| // obj reads an instantiated object reference from the bitstream. | |
| func (r *reader) obj() ir.Node { | |
| return r.p.objInstIdx(r.objInfo(), r.dict, false) | |
| } | |
| // objInfo reads an instantiated object reference from the bitstream | |
| // and returns the encoded reference to it, without instantiating it. | |
| func (r *reader) objInfo() objInfo { | |
| r.Sync(pkgbits.SyncObject) | |
| if r.Version().Has(pkgbits.DerivedFuncInstance) { | |
| assert(!r.Bool()) | |
| } | |
| idx := r.Reloc(pkgbits.SectionObj) | |
| explicits := make([]typeInfo, r.Len()) | |
| for i := range explicits { | |
| explicits[i] = r.typInfo() | |
| } | |
| return objInfo{idx, explicits} | |
| } | |
| // objInstIdx returns the encoded, instantiated object. If shaped is | |
| // true, then the shaped variant of the object is returned instead. | |
| func (pr *pkgReader) objInstIdx(info objInfo, dict *readerDict, shaped bool) ir.Node { | |
| explicits := pr.typListIdx(info.explicits, dict) | |
| var implicits []*types.Type | |
| if dict != nil { | |
| implicits = dict.targs | |
| } | |
| return pr.objIdx(info.idx, implicits, explicits, shaped) | |
| } | |
| // objIdx returns the specified object, instantiated with the given | |
| // type arguments, if any. | |
| // If shaped is true, then the shaped variant of the object is returned | |
| // instead. | |
| func (pr *pkgReader) objIdx(idx index, implicits, explicits []*types.Type, shaped bool) ir.Node { | |
| n, err := pr.objIdxMayFail(idx, implicits, explicits, shaped) | |
| if err != nil { | |
| base.Fatalf("%v", err) | |
| } | |
| return n | |
| } | |
| // objIdxMayFail is equivalent to objIdx, but returns an error rather than | |
| // failing the build if this object requires type arguments and the incorrect | |
| // number of type arguments were passed. | |
| // | |
| // Other sources of internal failure (such as duplicate definitions) still fail | |
| // the build. | |
| func (pr *pkgReader) objIdxMayFail(idx index, implicits, explicits []*types.Type, shaped bool) (ir.Node, error) { | |
| rname := pr.newReader(pkgbits.SectionName, idx, pkgbits.SyncObject1) | |
| _, sym := rname.qualifiedIdent() | |
| tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj)) | |
| if tag == pkgbits.ObjStub { | |
| assert(!sym.IsBlank()) | |
| switch sym.Pkg { | |
| case types.BuiltinPkg, types.UnsafePkg: | |
| return sym.Def.(ir.Node), nil | |
| } | |
| if pri, ok := objReader[sym]; ok { | |
| return pri.pr.objIdxMayFail(pri.idx, nil, explicits, shaped) | |
| } | |
| if sym.Pkg.Path == "runtime" { | |
| return typecheck.LookupRuntime(sym.Name), nil | |
| } | |
| base.Fatalf("unresolved stub: %v", sym) | |
| } | |
| dict, err := pr.objDictIdx(sym, idx, implicits, explicits, shaped) | |
| if err != nil { | |
| return nil, err | |
| } | |
| sym = dict.baseSym | |
| if !sym.IsBlank() && sym.Def != nil { | |
| return sym.Def.(*ir.Name), nil | |
| } | |
| r := pr.newReader(pkgbits.SectionObj, idx, pkgbits.SyncObject1) | |
| rext := pr.newReader(pkgbits.SectionObjExt, idx, pkgbits.SyncObject1) | |
| r.dict = dict | |
| rext.dict = dict | |
| do := func(op ir.Op, hasTParams bool) *ir.Name { | |
| pos := r.pos() | |
| setBasePos(pos) | |
| if hasTParams { | |
| r.typeParamNames() | |
| } | |
| name := ir.NewDeclNameAt(pos, op, sym) | |
| name.Class = ir.PEXTERN // may be overridden later | |
| if !sym.IsBlank() { | |
| if sym.Def != nil { | |
| base.FatalfAt(name.Pos(), "already have a definition for %v", name) | |
| } | |
| assert(sym.Def == nil) | |
| sym.Def = name | |
| } | |
| return name | |
| } | |
| switch tag { | |
| default: | |
| panic("unexpected object") | |
| case pkgbits.ObjAlias: | |
| name := do(ir.OTYPE, false) | |
| if r.Version().Has(pkgbits.AliasTypeParamNames) { | |
| r.typeParamNames() | |
| } | |
| // Clumsy dance: the r.typ() call here might recursively find this | |
| // type alias name, before we've set its type (#66873). So we | |
| // temporarily clear sym.Def and then restore it later, if still | |
| // unset. | |
| hack := sym.Def == name | |
| if hack { | |
| sym.Def = nil | |
| } | |
| typ := r.typ() | |
| if hack { | |
| if sym.Def != nil { | |
| name = sym.Def.(*ir.Name) | |
| assert(types.IdenticalStrict(name.Type(), typ)) | |
| return name, nil | |
| } | |
| sym.Def = name | |
| } | |
| setType(name, typ) | |
| name.SetAlias(true) | |
| return name, nil | |
| case pkgbits.ObjConst: | |
| name := do(ir.OLITERAL, false) | |
| typ := r.typ() | |
| val := FixValue(typ, r.Value()) | |
| setType(name, typ) | |
| setValue(name, val) | |
| return name, nil | |
| case pkgbits.ObjFunc: | |
| npos := r.pos() | |
| setBasePos(npos) | |
| var sel *types.Sym | |
| var recv *types.Field | |
| if r.Version().Has(pkgbits.GenericMethods) && r.Bool() { | |
| sel = r.selector() | |
| r.recvTypeParamNames() | |
| recv = r.param() | |
| if r.Version().Has(pkgbits.PreserveMethodOrder) { | |
| _ = r.Len() // method index not needed in compiler | |
| } | |
| } else { | |
| if sym.Name == "init" { | |
| sym = Renameinit() | |
| } | |
| } | |
| r.typeParamNames() | |
| typ := r.signature(recv) | |
| fpos := r.pos() | |
| fn := ir.NewFunc(fpos, npos, sym, typ) | |
| if r.hasTypeParams() && r.dict.shaped { | |
| typ.SetHasShape(true) | |
| } | |
| name := fn.Nname | |
| if !sym.IsBlank() { | |
| if sym.Def != nil { | |
| base.FatalfAt(name.Pos(), "already have a definition for %v", name) | |
| } | |
| assert(sym.Def == nil) | |
| sym.Def = name | |
| } | |
| if r.hasTypeParams() { | |
| name.Func.SetDupok(true) | |
| if r.dict.shaped { | |
| setType(name, shapeSig(name.Func, r.dict)) | |
| } else { | |
| todoDicts = append(todoDicts, func() { | |
| r.dict.shapedObj = pr.objIdx(idx, implicits, explicits, true).(*ir.Name) | |
| }) | |
| } | |
| } | |
| rext.funcExt(name, sel) | |
| return name, nil | |
| case pkgbits.ObjType: | |
| name := do(ir.OTYPE, true) | |
| typ := types.NewNamed(name) | |
| setType(name, typ) | |
| if r.hasTypeParams() && r.dict.shaped { | |
| typ.SetHasShape(true) | |
| } | |
| // Important: We need to do this before SetUnderlying. | |
| rext.typeExt(name) | |
| // We need to defer CheckSize until we've called SetUnderlying to | |
| // handle recursive types. | |
| types.DeferCheckSize() | |
| typ.SetUnderlying(r.typWrapped(false)) | |
| types.ResumeCheckSize() | |
| if r.hasTypeParams() && !r.dict.shaped { | |
| todoDicts = append(todoDicts, func() { | |
| r.dict.shapedObj = pr.objIdx(idx, implicits, explicits, true).(*ir.Name) | |
| }) | |
| } | |
| methods := make([]*types.Field, r.Len()) | |
| for i := range methods { | |
| methods[i] = r.method(rext) | |
| } | |
| if len(methods) != 0 { | |
| typ.SetMethods(methods) | |
| } | |
| if !r.dict.shaped { | |
| r.needWrapper(typ) | |
| } | |
| return name, nil | |
| case pkgbits.ObjVar: | |
| name := do(ir.ONAME, false) | |
| setType(name, r.typ()) | |
| rext.varExt(name) | |
| return name, nil | |
| } | |
| } | |
| // mangle shapes the non-shaped symbol sym under the current dictionary. | |
| func (dict *readerDict) mangle(sym *types.Sym) *types.Sym { | |
| if !dict.hasTypeParams() { | |
| return sym | |
| } | |
| var buf strings.Builder | |
| // If sym is a locally defined generic type, we need the suffix to | |
| // stay at the end after mangling so that types/fmt.go can strip it | |
| // out again when writing the type's runtime descriptor (#54456). | |
| n0, vsuff := types.SplitVargenSuffix(sym.Name) | |
| n1, msuff := types.SplitMethSuffix(sym.Name) | |
| // Methods are never locally defined. | |
| var n string | |
| assert(vsuff == "" || msuff == "") | |
| if vsuff != "" { | |
| n = n0 | |
| } else { | |
| n = n1 | |
| } | |
| var j int | |
| assert(dict.implicits == 0 || dict.receivers == 0) | |
| if msuff != "" { | |
| j = dict.receivers // consume receiver type arguments | |
| } else { | |
| j = len(dict.targs) // consume all type arguments | |
| } | |
| // put type arguments inside parenthesis; (*T)[int] -> (*T[int]) | |
| n, ok := strings.CutSuffix(n, ")") | |
| // type arguments, if any | |
| buf.WriteString(n) | |
| if j > 0 { | |
| buf.WriteByte('[') | |
| for i := 0; i < j; i++ { | |
| if i > 0 { | |
| if i == dict.implicits { | |
| buf.WriteByte(';') | |
| } else { | |
| buf.WriteByte(',') | |
| } | |
| } | |
| buf.WriteString(dict.targs[i].LinkString()) | |
| } | |
| buf.WriteByte(']') | |
| } | |
| if ok { | |
| buf.WriteString(")") | |
| } | |
| buf.WriteString(vsuff) | |
| buf.WriteString(msuff) | |
| // method arguments, if any | |
| if msuff != "" { | |
| buf.WriteByte('[') | |
| for i := j; i < len(dict.targs); i++ { | |
| if i > j { | |
| buf.WriteByte(',') | |
| } | |
| buf.WriteString(dict.targs[i].LinkString()) | |
| } | |
| buf.WriteByte(']') | |
| } | |
| return sym.Pkg.Lookup(buf.String()) | |
| } | |
| // Shapify returns the shape type for targ. | |
| // | |
| // If basic is true, then the type argument is used to instantiate a | |
| // type parameter whose constraint is a basic interface. | |
| func Shapify(targ *types.Type, basic bool) *types.Type { | |
| if targ.Kind() == types.TFORW { | |
| if targ.IsFullyInstantiated() { | |
| // For recursive instantiated type argument, it may still be a TFORW | |
| // when shapifying happens. If we don't have targ's underlying type, | |
| // shapify won't work. The worst case is we end up not reusing code | |
| // optimally in some tricky cases. | |
| if base.Debug.Shapify != 0 { | |
| base.Warn("skipping shaping of recursive type %v", targ) | |
| } | |
| if targ.HasShape() { | |
| return targ | |
| } | |
| } else { | |
| base.Fatalf("%v is missing its underlying type", targ) | |
| } | |
| } | |
| // For fully instantiated shape interface type, use it as-is. Otherwise, the instantiation | |
| // involved recursive generic interface may cause mismatching in function signature, see issue #65362. | |
| if targ.Kind() == types.TINTER && targ.IsFullyInstantiated() && targ.HasShape() { | |
| return targ | |
| } | |
| // When a pointer type is used to instantiate a type parameter | |
| // constrained by a basic interface, we know the pointer's element | |
| // type can't matter to the generated code. In this case, we can use | |
| // an arbitrary pointer type as the shape type. (To match the | |
| // non-unified frontend, we use `*byte`.) | |
| // | |
| // Otherwise, we simply use the type's underlying type as its shape. | |
| // | |
| // TODO(mdempsky): It should be possible to do much more aggressive | |
| // shaping still; e.g., collapsing all pointer-shaped types into a | |
| // common type, collapsing scalars of the same size/alignment into a | |
| // common type, recursively shaping the element types of composite | |
| // types, and discarding struct field names and tags. However, we'll | |
| // need to start tracking how type parameters are actually used to | |
| // implement some of these optimizations. | |
| under := targ.Underlying() | |
| if basic && targ.IsPtr() && !targ.Elem().NotInHeap() { | |
| under = types.NewPtr(types.Types[types.TUINT8]) | |
| } | |
| // Hash long type names to bound symbol name length seen by users, | |
| // particularly for large protobuf structs (#65030). | |
| uls := under.LinkString() | |
| if base.Debug.MaxShapeLen != 0 && | |
| len(uls) > base.Debug.MaxShapeLen { | |
| h := hash.Sum32([]byte(uls)) | |
| uls = hex.EncodeToString(h[:]) | |
| } | |
| sym := types.ShapePkg.Lookup(uls) | |
| if sym.Def == nil { | |
| name := ir.NewDeclNameAt(under.Pos(), ir.OTYPE, sym) | |
| typ := types.NewNamed(name) | |
| typ.SetUnderlying(under) | |
| sym.Def = typed(typ, name) | |
| } | |
| res := sym.Def.Type() | |
| assert(res.IsShape()) | |
| assert(res.HasShape()) | |
| return res | |
| } | |
| // objDictIdx reads and returns the specified object dictionary. | |
| func (pr *pkgReader) objDictIdx(sym *types.Sym, idx index, implicits, explicits []*types.Type, shaped bool) (*readerDict, error) { | |
| r := pr.newReader(pkgbits.SectionObjDict, idx, pkgbits.SyncObject1) | |
| dict := readerDict{ | |
| shaped: shaped, | |
| } | |
| nimplicits := r.Len() | |
| nreceivers := 0 | |
| if r.Version().Has(pkgbits.GenericMethods) { | |
| nreceivers = r.Len() | |
| } | |
| nexplicits := r.Len() + nreceivers | |
| if nimplicits > len(implicits) || nexplicits != len(explicits) { | |
| return nil, fmt.Errorf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits)) | |
| } | |
| dict.targs = append(implicits[:nimplicits:nimplicits], explicits...) | |
| dict.implicits = nimplicits | |
| dict.receivers = nreceivers | |
| // Within the compiler, we can just skip over the type parameters. | |
| for range dict.targs[dict.implicits:] { | |
| // Skip past bounds without actually evaluating them. | |
| r.typInfo() | |
| } | |
| dict.derived = make([]derivedInfo, r.Len()) | |
| dict.derivedTypes = make([]*types.Type, len(dict.derived)) | |
| for i := range dict.derived { | |
| dict.derived[i] = derivedInfo{idx: r.Reloc(pkgbits.SectionType)} | |
| if r.Version().Has(pkgbits.DerivedInfoNeeded) { | |
| assert(!r.Bool()) | |
| } | |
| } | |
| // Runtime dictionary information; private to the compiler. | |
| // If any type argument is already shaped, then we're constructing a | |
| // shaped object, even if not explicitly requested (i.e., calling | |
| // objIdx with shaped==true). This can happen with instantiating | |
| // types that are referenced within a function body. | |
| for _, targ := range dict.targs { | |
| if targ.HasShape() { | |
| dict.shaped = true | |
| break | |
| } | |
| } | |
| // And if we're constructing a shaped object, then shapify all type | |
| // arguments. | |
| for i, targ := range dict.targs { | |
| basic := r.Bool() | |
| if dict.shaped { | |
| dict.targs[i] = Shapify(targ, basic) | |
| } | |
| } | |
| dict.baseSym = dict.mangle(sym) | |
| dict.typeParamMethodExprs = make([]readerMethodExprInfo, r.Len()) | |
| for i := range dict.typeParamMethodExprs { | |
| typeParamIdx := r.Len() | |
| method := r.selector() | |
| dict.typeParamMethodExprs[i] = readerMethodExprInfo{typeParamIdx, method} | |
| } | |
| dict.subdicts = make([]objInfo, r.Len()) | |
| for i := range dict.subdicts { | |
| dict.subdicts[i] = r.objInfo() | |
| } | |
| dict.rtypes = make([]typeInfo, r.Len()) | |
| for i := range dict.rtypes { | |
| dict.rtypes[i] = r.typInfo() | |
| } | |
| dict.itabs = make([]itabInfo, r.Len()) | |
| for i := range dict.itabs { | |
| dict.itabs[i] = itabInfo{typ: r.typInfo(), iface: r.typInfo()} | |
| } | |
| return &dict, nil | |
| } | |
| func (r *reader) recvTypeParamNames() { | |
| r.Sync(pkgbits.SyncTypeParamNames) | |
| for range r.dict.targs[r.dict.implicits : r.dict.implicits+r.dict.receivers] { | |
| r.pos() | |
| r.localIdent() | |
| } | |
| } | |
| func (r *reader) typeParamNames() { | |
| r.Sync(pkgbits.SyncTypeParamNames) | |
| for range r.dict.targs[r.dict.implicits+r.dict.receivers:] { | |
| r.pos() | |
| r.localIdent() | |
| } | |
| } | |
| func (r *reader) method(rext *reader) *types.Field { | |
| r.Sync(pkgbits.SyncMethod) | |
| if r.Version().Has(pkgbits.PreserveMethodOrder) { | |
| _ = r.Len() // method index not needed in compiler | |
| } | |
| npos := r.pos() | |
| sym := r.selector() | |
| r.typeParamNames() | |
| recv := r.param() | |
| typ := r.signature(recv) | |
| fpos := r.pos() | |
| fn := ir.NewFunc(fpos, npos, ir.ReceiverMethodSym(recv.Type, sym), typ) | |
| name := fn.Nname | |
| if r.hasTypeParams() { | |
| name.Func.SetDupok(true) | |
| if r.dict.shaped { | |
| typ = shapeSig(name.Func, r.dict) | |
| setType(name, typ) | |
| } | |
| } | |
| rext.funcExt(name, sym) | |
| meth := types.NewField(name.Func.Pos(), sym, typ) | |
| meth.Nname = name | |
| meth.SetNointerface(name.Func.Pragma&ir.Nointerface != 0) | |
| return meth | |
| } | |
| func (r *reader) qualifiedIdent() (pkg *types.Pkg, sym *types.Sym) { | |
| r.Sync(pkgbits.SyncSym) | |
| pkg = r.pkg() | |
| if name := r.String(); name != "" { | |
| sym = pkg.Lookup(name) | |
| } | |
| return | |
| } | |
| func (r *reader) localIdent() *types.Sym { | |
| r.Sync(pkgbits.SyncLocalIdent) | |
| pkg := r.pkg() | |
| if name := r.String(); name != "" { | |
| return pkg.Lookup(name) | |
| } | |
| return nil | |
| } | |
| func (r *reader) selector() *types.Sym { | |
| r.Sync(pkgbits.SyncSelector) | |
| pkg := r.pkg() | |
| name := r.String() | |
| if types.IsExported(name) { | |
| pkg = types.LocalPkg | |
| } | |
| return pkg.Lookup(name) | |
| } | |
| func (r *reader) hasTypeParams() bool { | |
| return r.dict.hasTypeParams() | |
| } | |
| func (dict *readerDict) hasTypeParams() bool { | |
| return dict != nil && len(dict.targs) != 0 | |
| } | |
| // @@@ Compiler extensions | |
| func (r *reader) funcExt(name *ir.Name, method *types.Sym) { | |
| r.Sync(pkgbits.SyncFuncExt) | |
| fn := name.Func | |
| // XXX: Workaround because linker doesn't know how to copy Pos. | |
| if !fn.Pos().IsKnown() { | |
| fn.SetPos(name.Pos()) | |
| } | |
| // Normally, we only compile local functions, which saves redundant compilation work. | |
| // n.Defn is not nil for local functions, and is nil for imported function. But for | |
| // generic functions, we might have an instantiation that no other package has seen before. | |
| // So we need to be conservative and compile it again. | |
| // | |
| // That's why name.Defn is set here, so ir.VisitFuncsBottomUp can analyze function. | |
| // TODO(mdempsky,cuonglm): find a cleaner way to handle this. | |
| if name.Sym().Pkg == types.LocalPkg || r.hasTypeParams() { | |
| name.Defn = fn | |
| } | |
| fn.Pragma = r.pragmaFlag() | |
| r.linkname(name) | |
| if buildcfg.GOARCH == "wasm" { | |
| importmod := r.String() | |
| importname := r.String() | |
| exportname := r.String() | |
| if importmod != "" && importname != "" { | |
| fn.WasmImport = &ir.WasmImport{ | |
| Module: importmod, | |
| Name: importname, | |
| } | |
| } | |
| if exportname != "" { | |
| if method != nil { | |
| base.ErrorfAt(fn.Pos(), 0, "cannot use //go:wasmexport on a method") | |
| } | |
| fn.WasmExport = &ir.WasmExport{Name: exportname} | |
| } | |
| } | |
| if r.Bool() { | |
| assert(name.Defn == nil) | |
| fn.ABI = obj.ABI(r.Uint64()) | |
| // Escape analysis. | |
| for _, f := range name.Type().RecvParams() { | |
| f.Note = r.String() | |
| } | |
| if r.Bool() { | |
| fn.Inl = &ir.Inline{ | |
| Cost: int32(r.Len()), | |
| CanDelayResults: r.Bool(), | |
| } | |
| if buildcfg.Experiment.NewInliner { | |
| fn.Inl.Properties = r.String() | |
| } | |
| } | |
| } else { | |
| r.addBody(name.Func, method) | |
| } | |
| r.Sync(pkgbits.SyncEOF) | |
| } | |
| func (r *reader) typeExt(name *ir.Name) { | |
| r.Sync(pkgbits.SyncTypeExt) | |
| typ := name.Type() | |
| if r.hasTypeParams() { | |
| // Mark type as fully instantiated to ensure the type descriptor is written | |
| // out as DUPOK and method wrappers are generated even for imported types. | |
| typ.SetIsFullyInstantiated(true) | |
| // HasShape should be set if any type argument is or has a shape type. | |
| for _, targ := range r.dict.targs { | |
| if targ.HasShape() { | |
| typ.SetHasShape(true) | |
| break | |
| } | |
| } | |
| } | |
| name.SetPragma(r.pragmaFlag()) | |
| typecheck.SetBaseTypeIndex(typ, r.Int64(), r.Int64()) | |
| } | |
| func (r *reader) varExt(name *ir.Name) { | |
| r.Sync(pkgbits.SyncVarExt) | |
| r.linkname(name) | |
| } | |
| func (r *reader) linkname(name *ir.Name) { | |
| assert(name.Op() == ir.ONAME) | |
| r.Sync(pkgbits.SyncLinkname) | |
| if idx := r.Int64(); idx >= 0 { | |
| lsym := name.Linksym() | |
| lsym.SymIdx = int32(idx) | |
| lsym.Set(obj.AttrIndexed, true) | |
| } else { | |
| linkname := r.String() | |
| std := r.Bool() | |
| sym := name.Sym() | |
| sym.Linkname = linkname | |
| if sym.Pkg == types.LocalPkg && linkname != "" { | |
| // Mark linkname in the current package. We don't mark the | |
| // ones that are imported and propagated (e.g. through | |
| // inlining or instantiation, which are marked in their | |
| // corresponding packages). So we can tell in which package | |
| // the linkname is used (pulled), and the linker can | |
| // make a decision for allowing or disallowing it. | |
| if std { | |
| sym.Linksym().Set(obj.AttrLinknameStd, true) | |
| } else { | |
| sym.Linksym().Set(obj.AttrLinkname, true) | |
| } | |
| } | |
| } | |
| } | |
| func (r *reader) pragmaFlag() ir.PragmaFlag { | |
| r.Sync(pkgbits.SyncPragma) | |
| return ir.PragmaFlag(r.Int()) | |
| } | |
| // @@@ Function bodies | |
| // bodyReader tracks where the serialized IR for a local or imported, | |
| // generic function's body can be found. | |
| var bodyReader = map[*ir.Func]pkgReaderIndex{} | |
| // importBodyReader tracks where the serialized IR for an imported, | |
| // static (i.e., non-generic) function body can be read. | |
| var importBodyReader = map[*types.Sym]pkgReaderIndex{} | |
| // bodyReaderFor returns the pkgReaderIndex for reading fn's | |
| // serialized IR, and whether one was found. | |
| func bodyReaderFor(fn *ir.Func) (pri pkgReaderIndex, ok bool) { | |
| if fn.Nname.Defn != nil { | |
| pri, ok = bodyReader[fn] | |
| base.AssertfAt(ok, base.Pos, "must have bodyReader for %v", fn) // must always be available | |
| } else { | |
| pri, ok = importBodyReader[fn.Sym()] | |
| } | |
| return | |
| } | |
| // todoDicts holds the list of dictionaries that still need their | |
| // runtime dictionary objects constructed. | |
| var todoDicts []func() | |
| // todoBodies holds the list of function bodies that still need to be | |
| // constructed. | |
| var todoBodies []*ir.Func | |
| // addBody reads a function body reference from the element bitstream, | |
| // and associates it with fn. | |
| func (r *reader) addBody(fn *ir.Func, method *types.Sym) { | |
| // addBody should only be called for local functions or imported | |
| // generic functions; see comment in funcExt. | |
| assert(fn.Nname.Defn != nil) | |
| idx := r.Reloc(pkgbits.SectionBody) | |
| pri := pkgReaderIndex{r.p, idx, r.dict, method, nil} | |
| bodyReader[fn] = pri | |
| if r.curfn == nil { | |
| todoBodies = append(todoBodies, fn) | |
| return | |
| } | |
| pri.funcBody(fn) | |
| } | |
| func (pri pkgReaderIndex) funcBody(fn *ir.Func) { | |
| r := pri.asReader(pkgbits.SectionBody, pkgbits.SyncFuncBody) | |
| panicking := true | |
| defer func() { | |
| if panicking { | |
| // TODO not sure what the best way to print in this context is. | |
| // If code panics in unified IR reading, you want *something* like this. | |
| // Whoever ends up debugging the next unified IR failure, please | |
| // improve this (base.Warnf?) if you can figure out how. | |
| fmt.Printf("****** panic traversed funcBody of %v\n", fn) | |
| } | |
| }() | |
| r.funcBody(fn) | |
| panicking = false | |
| } | |
| // funcBody reads a function body definition from the element | |
| // bitstream, and populates fn with it. | |
| func (r *reader) funcBody(fn *ir.Func) { | |
| r.curfn = fn | |
| r.closureVars = fn.ClosureVars | |
| if len(r.closureVars) != 0 && r.hasTypeParams() { | |
| r.dictParam = r.closureVars[len(r.closureVars)-1] // dictParam is last; see reader.funcLit | |
| } | |
| ir.WithPos(fn, func() { | |
| r.declareParams() | |
| if r.syntheticBody(fn.Pos()) { | |
| return | |
| } | |
| if !r.Bool() { | |
| return | |
| } | |
| body := r.stmts() | |
| if body == nil { | |
| body = []ir.Node{typecheck.Stmt(r.curfn, ir.NewBlockStmt(src.NoXPos, nil))} | |
| } | |
| fn.Body = body | |
| fn.Endlineno = r.pos() | |
| }) | |
| r.marker.WriteTo(fn) | |
| } | |
| // syntheticBody adds a synthetic body to r.curfn if appropriate, and | |
| // reports whether it did. | |
| func (r *reader) syntheticBody(pos src.XPos) bool { | |
| if r.synthetic != nil { | |
| r.synthetic(pos, r) | |
| return true | |
| } | |
| // If this function has type parameters and isn't shaped, then we | |
| // just tail call its corresponding shaped variant. | |
| if r.hasTypeParams() && !r.dict.shaped { | |
| r.callShaped(pos) | |
| return true | |
| } | |
| return false | |
| } | |
| // callShaped emits a tail call to r.shapedFn, passing along the | |
| // arguments to the current function. | |
| func (r *reader) callShaped(pos src.XPos) { | |
| shapedObj := r.dict.shapedObj | |
| assert(shapedObj != nil) | |
| var shapedFn ir.Node | |
| if r.methodSym == nil { | |
| // Instantiating a generic function; shapedObj is the shaped function itself. | |
| assert(shapedObj.Op() == ir.ONAME && shapedObj.Class == ir.PFUNC) | |
| shapedFn = shapedObj | |
| } else { | |
| // Instantiating a generic type's method; shapedObj is the shaped method itself | |
| // if the method is generic — else, it is the shaped type declaring the method. | |
| shapedFn = shapedMethodExpr(pos, shapedObj, r.methodSym) | |
| } | |
| params := r.syntheticArgs() | |
| // Construct the arguments list: receiver (if any), then runtime | |
| // dictionary, and finally normal parameters. | |
| // | |
| // Note: For simplicity, shaped methods are added as normal methods | |
| // on their shaped types. So existing code (e.g., packages ir and | |
| // typecheck) expects the shaped type to appear as the receiver | |
| // parameter (or first parameter, as a method expression). Hence | |
| // putting the dictionary parameter after that is the least invasive | |
| // solution at the moment. | |
| var args ir.Nodes | |
| if r.methodSym != nil { | |
| args.Append(params[0]) | |
| params = params[1:] | |
| } | |
| args.Append(typecheck.Expr(r.curfn, ir.NewAddrExpr(pos, r.p.dictNameOf(r.dict)))) | |
| args.Append(params...) | |
| r.syntheticTailCall(pos, shapedFn, args) | |
| } | |
| // syntheticArgs returns the recvs and params arguments passed to the | |
| // current function. | |
| func (r *reader) syntheticArgs() ir.Nodes { | |
| sig := r.curfn.Nname.Type() | |
| return ir.ToNodes(r.curfn.Dcl[:sig.NumRecvs()+sig.NumParams()]) | |
| } | |
| // syntheticTailCall emits a tail call to fn, passing the given | |
| // arguments list. | |
| func (r *reader) syntheticTailCall(pos src.XPos, fn ir.Node, args ir.Nodes) { | |
| // Mark the function as a wrapper so it doesn't show up in stack | |
| // traces. | |
| r.curfn.SetWrapper(true) | |
| call := typecheck.Call(r.curfn, pos, fn, args, fn.Type().IsVariadic()).(*ir.CallExpr) | |
| var stmt ir.Node | |
| if fn.Type().NumResults() != 0 { | |
| stmt = typecheck.Stmt(r.curfn, ir.NewReturnStmt(pos, []ir.Node{call})) | |
| } else { | |
| stmt = call | |
| } | |
| r.curfn.Body.Append(stmt) | |
| } | |
| // dictNameOf returns the runtime dictionary corresponding to dict. | |
| func (pr *pkgReader) dictNameOf(dict *readerDict) *ir.Name { | |
| pos := base.AutogeneratedPos | |
| // Check that we only instantiate runtime dictionaries with real types. | |
| base.AssertfAt(!dict.shaped, pos, "runtime dictionary of shaped object %v", dict.baseSym) | |
| sym := dict.baseSym.Pkg.Lookup(objabi.GlobalDictPrefix + "." + dict.baseSym.Name) | |
| if sym.Def != nil { | |
| return sym.Def.(*ir.Name) | |
| } | |
| name := ir.NewNameAt(pos, sym, dict.varType()) | |
| name.Class = ir.PEXTERN | |
| sym.Def = name // break cycles with mutual subdictionaries | |
| lsym := name.Linksym() | |
| ot := 0 | |
| assertOffset := func(section string, offset int) { | |
| base.AssertfAt(ot == offset*types.PtrSize, pos, "writing section %v at offset %v, but it should be at %v*%v", section, ot, offset, types.PtrSize) | |
| } | |
| assertOffset("type param method exprs", dict.typeParamMethodExprsOffset()) | |
| for _, info := range dict.typeParamMethodExprs { | |
| typeParam := dict.targs[info.typeParamIdx] | |
| method := typecheck.NewMethodExpr(pos, typeParam, info.method) | |
| rsym := method.FuncName().Linksym() | |
| assert(rsym.ABI() == obj.ABIInternal) // must be ABIInternal; see ir.OCFUNC in ssagen/ssa.go | |
| ot = objw.SymPtr(lsym, ot, rsym, 0) | |
| } | |
| assertOffset("subdictionaries", dict.subdictsOffset()) | |
| for _, info := range dict.subdicts { | |
| explicits := pr.typListIdx(info.explicits, dict) | |
| // Careful: Due to subdictionary cycles, name may not be fully | |
| // initialized yet. | |
| name := pr.objDictName(info.idx, dict.targs, explicits) | |
| ot = objw.SymPtr(lsym, ot, name.Linksym(), 0) | |
| } | |
| assertOffset("rtypes", dict.rtypesOffset()) | |
| for _, info := range dict.rtypes { | |
| typ := pr.typIdx(info, dict, true) | |
| ot = objw.SymPtr(lsym, ot, reflectdata.TypeLinksym(typ), 0) | |
| // TODO(mdempsky): Double check this. | |
| reflectdata.MarkTypeUsedInInterface(typ, lsym) | |
| } | |
| // For each (typ, iface) pair, we write the *runtime.itab pointer | |
| // for the pair. For pairs that don't actually require an itab | |
| // (i.e., typ is an interface, or iface is an empty interface), we | |
| // write a nil pointer instead. This is wasteful, but rare in | |
| // practice (e.g., instantiating a type parameter with an interface | |
| // type). | |
| assertOffset("itabs", dict.itabsOffset()) | |
| for _, info := range dict.itabs { | |
| typ := pr.typIdx(info.typ, dict, true) | |
| iface := pr.typIdx(info.iface, dict, true) | |
| if !typ.IsInterface() && iface.IsInterface() && !iface.IsEmptyInterface() { | |
| ot = objw.SymPtr(lsym, ot, reflectdata.ITabLsym(typ, iface), 0) | |
| } else { | |
| ot += types.PtrSize | |
| } | |
| // TODO(mdempsky): Double check this. | |
| reflectdata.MarkTypeUsedInInterface(typ, lsym) | |
| reflectdata.MarkTypeUsedInInterface(iface, lsym) | |
| } | |
| objw.Global(lsym, int32(ot), obj.DUPOK|obj.RODATA) | |
| return name | |
| } | |
| // typeParamMethodExprsOffset returns the offset of the runtime | |
| // dictionary's type parameter method expressions section, in words. | |
| func (dict *readerDict) typeParamMethodExprsOffset() int { | |
| return 0 | |
| } | |
| // subdictsOffset returns the offset of the runtime dictionary's | |
| // subdictionary section, in words. | |
| func (dict *readerDict) subdictsOffset() int { | |
| return dict.typeParamMethodExprsOffset() + len(dict.typeParamMethodExprs) | |
| } | |
| // rtypesOffset returns the offset of the runtime dictionary's rtypes | |
| // section, in words. | |
| func (dict *readerDict) rtypesOffset() int { | |
| return dict.subdictsOffset() + len(dict.subdicts) | |
| } | |
| // itabsOffset returns the offset of the runtime dictionary's itabs | |
| // section, in words. | |
| func (dict *readerDict) itabsOffset() int { | |
| return dict.rtypesOffset() + len(dict.rtypes) | |
| } | |
| // numWords returns the total number of words that comprise dict's | |
| // runtime dictionary variable. | |
| func (dict *readerDict) numWords() int64 { | |
| return int64(dict.itabsOffset() + len(dict.itabs)) | |
| } | |
| // varType returns the type of dict's runtime dictionary variable. | |
| func (dict *readerDict) varType() *types.Type { | |
| return types.NewArray(types.Types[types.TUINTPTR], dict.numWords()) | |
| } | |
| func (r *reader) declareParams() { | |
| r.curfn.DeclareParams(!r.funarghack) | |
| for _, name := range r.curfn.Dcl { | |
| if name.Sym().Name == dictParamName { | |
| r.dictParam = name | |
| continue | |
| } | |
| r.addLocal(name) | |
| } | |
| } | |
| func (r *reader) addLocal(name *ir.Name) { | |
| if r.synthetic == nil { | |
| r.Sync(pkgbits.SyncAddLocal) | |
| if r.p.SyncMarkers() { | |
| want := r.Int() | |
| if have := len(r.locals); have != want { | |
| base.FatalfAt(name.Pos(), "locals table has desynced") | |
| } | |
| } | |
| r.varDictIndex(name) | |
| } | |
| r.locals = append(r.locals, name) | |
| } | |
| func (r *reader) useLocal() *ir.Name { | |
| r.Sync(pkgbits.SyncUseObjLocal) | |
| if r.Bool() { | |
| return r.locals[r.Len()] | |
| } | |
| return r.closureVars[r.Len()] | |
| } | |
| func (r *reader) openScope() { | |
| r.Sync(pkgbits.SyncOpenScope) | |
| pos := r.pos() | |
| if base.Flag.Dwarf { | |
| r.scopeVars = append(r.scopeVars, len(r.curfn.Dcl)) | |
| r.marker.Push(pos) | |
| } | |
| } | |
| func (r *reader) closeScope() { | |
| r.Sync(pkgbits.SyncCloseScope) | |
| r.lastCloseScopePos = r.pos() | |
| r.closeAnotherScope() | |
| } | |
| // closeAnotherScope is like closeScope, but it reuses the same mark | |
| // position as the last closeScope call. This is useful for "for" and | |
| // "if" statements, as their implicit blocks always end at the same | |
| // position as an explicit block. | |
| func (r *reader) closeAnotherScope() { | |
| r.Sync(pkgbits.SyncCloseAnotherScope) | |
| if base.Flag.Dwarf { | |
| scopeVars := r.scopeVars[len(r.scopeVars)-1] | |
| r.scopeVars = r.scopeVars[:len(r.scopeVars)-1] | |
| // Quirkish: noder decides which scopes to keep before | |
| // typechecking, whereas incremental typechecking during IR | |
| // construction can result in new autotemps being allocated. To | |
| // produce identical output, we ignore autotemps here for the | |
| // purpose of deciding whether to retract the scope. | |
| // | |
| // This is important for net/http/fcgi, because it contains: | |
| // | |
| // var body io.ReadCloser | |
| // if len(content) > 0 { | |
| // body, req.pw = io.Pipe() | |
| // } else { … } | |
| // | |
| // Notably, io.Pipe is inlinable, and inlining it introduces a ~R0 | |
| // variable at the call site. | |
| // | |
| // Noder does not preserve the scope where the io.Pipe() call | |
| // resides, because it doesn't contain any declared variables in | |
| // source. So the ~R0 variable ends up being assigned to the | |
| // enclosing scope instead. | |
| // | |
| // However, typechecking this assignment also introduces | |
| // autotemps, because io.Pipe's results need conversion before | |
| // they can be assigned to their respective destination variables. | |
| // | |
| // TODO(mdempsky): We should probably just keep all scopes, and | |
| // let dwarfgen take care of pruning them instead. | |
| retract := true | |
| for _, n := range r.curfn.Dcl[scopeVars:] { | |
| if !n.AutoTemp() { | |
| retract = false | |
| break | |
| } | |
| } | |
| if retract { | |
| // no variables were declared in this scope, so we can retract it. | |
| r.marker.Unpush() | |
| } else { | |
| r.marker.Pop(r.lastCloseScopePos) | |
| } | |
| } | |
| } | |
| // @@@ Statements | |
| func (r *reader) stmt() ir.Node { | |
| return block(r.stmts()) | |
| } | |
| func block(stmts []ir.Node) ir.Node { | |
| switch len(stmts) { | |
| case 0: | |
| return nil | |
| case 1: | |
| return stmts[0] | |
| default: | |
| return ir.NewBlockStmt(stmts[0].Pos(), stmts) | |
| } | |
| } | |
| func (r *reader) stmts() ir.Nodes { | |
| assert(r.curfn == r.curfn) | |
| var res ir.Nodes | |
| r.Sync(pkgbits.SyncStmts) | |
| for { | |
| tag := codeStmt(r.Code(pkgbits.SyncStmt1)) | |
| if tag == stmtEnd { | |
| r.Sync(pkgbits.SyncStmtsEnd) | |
| return res | |
| } | |
| if n := r.stmt1(tag, &res); n != nil { | |
| res.Append(typecheck.Stmt(r.curfn, n)) | |
| } | |
| } | |
| } | |
| func (r *reader) stmt1(tag codeStmt, out *ir.Nodes) ir.Node { | |
| var label *types.Sym | |
| if n := len(*out); n > 0 { | |
| if ls, ok := (*out)[n-1].(*ir.LabelStmt); ok { | |
| label = ls.Label | |
| } | |
| } | |
| switch tag { | |
| default: | |
| panic("unexpected statement") | |
| case stmtAssign: | |
| pos := r.pos() | |
| names, lhs := r.assignList() | |
| rhs := r.multiExpr() | |
| if len(rhs) == 0 { | |
| for _, name := range names { | |
| as := ir.NewAssignStmt(pos, name, nil) | |
| as.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, name)) | |
| out.Append(typecheck.Stmt(r.curfn, as)) | |
| } | |
| return nil | |
| } | |
| if len(lhs) == 1 && len(rhs) == 1 { | |
| n := ir.NewAssignStmt(pos, lhs[0], rhs[0]) | |
| n.Def = r.initDefn(n, names) | |
| return n | |
| } | |
| n := ir.NewAssignListStmt(pos, ir.OAS2, lhs, rhs) | |
| n.Def = r.initDefn(n, names) | |
| return n | |
| case stmtAssignOp: | |
| op := r.op() | |
| lhs := r.expr() | |
| pos := r.pos() | |
| rhs := r.expr() | |
| return ir.NewAssignOpStmt(pos, op, lhs, rhs) | |
| case stmtIncDec: | |
| op := r.op() | |
| lhs := r.expr() | |
| pos := r.pos() | |
| n := ir.NewAssignOpStmt(pos, op, lhs, ir.NewOne(pos, lhs.Type())) | |
| n.IncDec = true | |
| return n | |
| case stmtBlock: | |
| out.Append(r.blockStmt()...) | |
| return nil | |
| case stmtBranch: | |
| pos := r.pos() | |
| op := r.op() | |
| sym := r.optLabel() | |
| return ir.NewBranchStmt(pos, op, sym) | |
| case stmtCall: | |
| pos := r.pos() | |
| op := r.op() | |
| call := r.expr() | |
| stmt := ir.NewGoDeferStmt(pos, op, call) | |
| if op == ir.ODEFER { | |
| x := r.optExpr() | |
| if x != nil { | |
| stmt.DeferAt = x.(ir.Expr) | |
| } | |
| } | |
| return stmt | |
| case stmtExpr: | |
| return r.expr() | |
| case stmtFor: | |
| return r.forStmt(label) | |
| case stmtIf: | |
| return r.ifStmt() | |
| case stmtLabel: | |
| pos := r.pos() | |
| sym := r.label() | |
| return ir.NewLabelStmt(pos, sym) | |
| case stmtReturn: | |
| pos := r.pos() | |
| results := r.multiExpr() | |
| return ir.NewReturnStmt(pos, results) | |
| case stmtSelect: | |
| return r.selectStmt(label) | |
| case stmtSend: | |
| pos := r.pos() | |
| ch := r.expr() | |
| value := r.expr() | |
| return ir.NewSendStmt(pos, ch, value) | |
| case stmtSwitch: | |
| return r.switchStmt(label) | |
| } | |
| } | |
| func (r *reader) assignList() ([]*ir.Name, []ir.Node) { | |
| lhs := make([]ir.Node, r.Len()) | |
| var names []*ir.Name | |
| for i := range lhs { | |
| expr, def := r.assign() | |
| lhs[i] = expr | |
| if def { | |
| names = append(names, expr.(*ir.Name)) | |
| } | |
| } | |
| return names, lhs | |
| } | |
| // assign returns an assignee expression. It also reports whether the | |
| // returned expression is a newly declared variable. | |
| func (r *reader) assign() (ir.Node, bool) { | |
| switch tag := codeAssign(r.Code(pkgbits.SyncAssign)); tag { | |
| default: | |
| panic("unhandled assignee expression") | |
| case assignBlank: | |
| return typecheck.AssignExpr(r.curfn, ir.BlankNode), false | |
| case assignDef: | |
| pos := r.pos() | |
| setBasePos(pos) // test/fixedbugs/issue49767.go depends on base.Pos being set for the r.typ() call here, ugh | |
| name := r.curfn.NewLocal(pos, r.localIdent(), r.typ()) | |
| r.addLocal(name) | |
| return name, true | |
| case assignExpr: | |
| return r.expr(), false | |
| } | |
| } | |
| func (r *reader) blockStmt() []ir.Node { | |
| r.Sync(pkgbits.SyncBlockStmt) | |
| r.openScope() | |
| stmts := r.stmts() | |
| r.closeScope() | |
| return stmts | |
| } | |
| func (r *reader) forStmt(label *types.Sym) ir.Node { | |
| r.Sync(pkgbits.SyncForStmt) | |
| r.openScope() | |
| if r.Bool() { | |
| pos := r.pos() | |
| rang := ir.NewRangeStmt(pos, nil, nil, nil, nil, false) | |
| rang.Label = label | |
| names, lhs := r.assignList() | |
| if len(lhs) >= 1 { | |
| rang.Key = lhs[0] | |
| if len(lhs) >= 2 { | |
| rang.Value = lhs[1] | |
| } | |
| } | |
| rang.Def = r.initDefn(rang, names) | |
| rang.X = r.expr() | |
| if rang.X.Type().IsMap() { | |
| rang.RType = r.rtype(pos) | |
| } | |
| if rang.Key != nil && !ir.IsBlank(rang.Key) { | |
| rang.KeyTypeWord, rang.KeySrcRType = r.convRTTI(pos) | |
| } | |
| if rang.Value != nil && !ir.IsBlank(rang.Value) { | |
| rang.ValueTypeWord, rang.ValueSrcRType = r.convRTTI(pos) | |
| } | |
| rang.Body = r.blockStmt() | |
| rang.DistinctVars = r.Bool() | |
| r.closeAnotherScope() | |
| return rang | |
| } | |
| pos := r.pos() | |
| init := r.stmt() | |
| cond := r.optExpr() | |
| post := r.stmt() | |
| body := r.blockStmt() | |
| perLoopVars := r.Bool() | |
| r.closeAnotherScope() | |
| if ir.IsConst(cond, constant.Bool) && !ir.BoolVal(cond) { | |
| return init // simplify "for init; false; post { ... }" into "init" | |
| } | |
| stmt := ir.NewForStmt(pos, init, cond, post, body, perLoopVars) | |
| stmt.Label = label | |
| return stmt | |
| } | |
| func (r *reader) ifStmt() ir.Node { | |
| r.Sync(pkgbits.SyncIfStmt) | |
| r.openScope() | |
| pos := r.pos() | |
| init := r.stmts() | |
| cond := r.expr() | |
| staticCond := r.Int() | |
| var then, els []ir.Node | |
| if staticCond >= 0 { | |
| then = r.blockStmt() | |
| } else { | |
| r.lastCloseScopePos = r.pos() | |
| } | |
| if staticCond <= 0 { | |
| els = r.stmts() | |
| } | |
| r.closeAnotherScope() | |
| if staticCond != 0 { | |
| // We may have removed a dead return statement, which can trip up | |
| // later passes (#62211). To avoid confusion, we instead flatten | |
| // the if statement into a block. | |
| if cond.Op() != ir.OLITERAL { | |
| init.Append(typecheck.Stmt(r.curfn, ir.NewAssignStmt(pos, ir.BlankNode, cond))) // for side effects | |
| } | |
| init.Append(then...) | |
| init.Append(els...) | |
| return block(init) | |
| } | |
| n := ir.NewIfStmt(pos, cond, then, els) | |
| n.SetInit(init) | |
| return n | |
| } | |
| func (r *reader) selectStmt(label *types.Sym) ir.Node { | |
| r.Sync(pkgbits.SyncSelectStmt) | |
| pos := r.pos() | |
| clauses := make([]*ir.CommClause, r.Len()) | |
| for i := range clauses { | |
| if i > 0 { | |
| r.closeScope() | |
| } | |
| r.openScope() | |
| pos := r.pos() | |
| comm := r.stmt() | |
| body := r.stmts() | |
| // "case i = <-c: ..." may require an implicit conversion (e.g., | |
| // see fixedbugs/bug312.go). Currently, typecheck throws away the | |
| // implicit conversion and relies on it being reinserted later, | |
| // but that would lose any explicit RTTI operands too. To preserve | |
| // RTTI, we rewrite this as "case tmp := <-c: i = tmp; ...". | |
| if as, ok := comm.(*ir.AssignStmt); ok && as.Op() == ir.OAS && !as.Def { | |
| if conv, ok := as.Y.(*ir.ConvExpr); ok && conv.Op() == ir.OCONVIFACE { | |
| base.AssertfAt(conv.Implicit(), conv.Pos(), "expected implicit conversion: %v", conv) | |
| recv := conv.X | |
| base.AssertfAt(recv.Op() == ir.ORECV, recv.Pos(), "expected receive expression: %v", recv) | |
| tmp := r.temp(pos, recv.Type()) | |
| // Replace comm with `tmp := <-c`. | |
| tmpAs := ir.NewAssignStmt(pos, tmp, recv) | |
| tmpAs.Def = true | |
| tmpAs.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, tmp)) | |
| comm = tmpAs | |
| // Change original assignment to `i = tmp`, and prepend to body. | |
| conv.X = tmp | |
| body = append([]ir.Node{as}, body...) | |
| } | |
| } | |
| // multiExpr will have desugared a comma-ok receive expression | |
| // into a separate statement. However, the rest of the compiler | |
| // expects comm to be the OAS2RECV statement itself, so we need to | |
| // shuffle things around to fit that pattern. | |
| if as2, ok := comm.(*ir.AssignListStmt); ok && as2.Op() == ir.OAS2 { | |
| init := ir.TakeInit(as2.Rhs[0]) | |
| base.AssertfAt(len(init) == 1 && init[0].Op() == ir.OAS2RECV, as2.Pos(), "unexpected assignment: %+v", as2) | |
| comm = init[0] | |
| body = append([]ir.Node{as2}, body...) | |
| } | |
| clauses[i] = ir.NewCommStmt(pos, comm, body) | |
| } | |
| if len(clauses) > 0 { | |
| r.closeScope() | |
| } | |
| n := ir.NewSelectStmt(pos, clauses) | |
| n.Label = label | |
| return n | |
| } | |
| func (r *reader) switchStmt(label *types.Sym) ir.Node { | |
| r.Sync(pkgbits.SyncSwitchStmt) | |
| r.openScope() | |
| pos := r.pos() | |
| init := r.stmt() | |
| var tag ir.Node | |
| var ident *ir.Ident | |
| var iface *types.Type | |
| if r.Bool() { | |
| pos := r.pos() | |
| if r.Bool() { | |
| ident = ir.NewIdent(r.pos(), r.localIdent()) | |
| } | |
| x := r.expr() | |
| iface = x.Type() | |
| tag = ir.NewTypeSwitchGuard(pos, ident, x) | |
| } else { | |
| tag = r.optExpr() | |
| } | |
| clauses := make([]*ir.CaseClause, r.Len()) | |
| for i := range clauses { | |
| if i > 0 { | |
| r.closeScope() | |
| } | |
| r.openScope() | |
| pos := r.pos() | |
| var cases, rtypes []ir.Node | |
| if iface != nil { | |
| cases = make([]ir.Node, r.Len()) | |
| if len(cases) == 0 { | |
| cases = nil // TODO(mdempsky): Unclear if this matters. | |
| } | |
| for i := range cases { | |
| if r.Bool() { // case nil | |
| cases[i] = typecheck.Expr(r.curfn, types.BuiltinPkg.Lookup("nil").Def.(*ir.NilExpr)) | |
| } else { | |
| cases[i] = r.exprType() | |
| } | |
| } | |
| } else { | |
| cases = r.exprList() | |
| // For `switch { case any(true): }` (e.g., issue 3980 in | |
| // test/switch.go), the backend still creates a mixed bool/any | |
| // comparison, and we need to explicitly supply the RTTI for the | |
| // comparison. | |
| // | |
| // TODO(mdempsky): Change writer.go to desugar "switch {" into | |
| // "switch true {", which we already handle correctly. | |
| if tag == nil { | |
| for i, cas := range cases { | |
| if cas.Type().IsEmptyInterface() { | |
| for len(rtypes) < i { | |
| rtypes = append(rtypes, nil) | |
| } | |
| rtypes = append(rtypes, reflectdata.TypePtrAt(r.curfn, cas.Pos(), types.Types[types.TBOOL])) | |
| } | |
| } | |
| } | |
| } | |
| clause := ir.NewCaseStmt(pos, cases, nil) | |
| clause.RTypes = rtypes | |
| if ident != nil { | |
| name := r.curfn.NewLocal(r.pos(), ident.Sym(), r.typ()) | |
| r.addLocal(name) | |
| clause.Var = name | |
| name.Defn = tag | |
| } | |
| clause.Body = r.stmts() | |
| clauses[i] = clause | |
| } | |
| if len(clauses) > 0 { | |
| r.closeScope() | |
| } | |
| r.closeScope() | |
| n := ir.NewSwitchStmt(pos, tag, clauses) | |
| n.Label = label | |
| if init != nil { | |
| n.SetInit([]ir.Node{init}) | |
| } | |
| return n | |
| } | |
| func (r *reader) label() *types.Sym { | |
| r.Sync(pkgbits.SyncLabel) | |
| name := r.String() | |
| if r.inlCall != nil && name != "_" { | |
| name = fmt.Sprintf("~%s·%d", name, inlgen) | |
| } | |
| return typecheck.Lookup(name) | |
| } | |
| func (r *reader) optLabel() *types.Sym { | |
| r.Sync(pkgbits.SyncOptLabel) | |
| if r.Bool() { | |
| return r.label() | |
| } | |
| return nil | |
| } | |
| // initDefn marks the given names as declared by defn and populates | |
| // its Init field with ODCL nodes. It then reports whether any names | |
| // were so declared, which can be used to initialize defn.Def. | |
| func (r *reader) initDefn(defn ir.InitNode, names []*ir.Name) bool { | |
| if len(names) == 0 { | |
| return false | |
| } | |
| init := make([]ir.Node, len(names)) | |
| for i, name := range names { | |
| name.Defn = defn | |
| init[i] = ir.NewDecl(name.Pos(), ir.ODCL, name) | |
| } | |
| defn.SetInit(init) | |
| return true | |
| } | |
| // @@@ Expressions | |
| // expr reads and returns a typechecked expression. | |
| func (r *reader) expr() (res ir.Node) { | |
| defer func() { | |
| if res != nil && res.Typecheck() == 0 { | |
| base.FatalfAt(res.Pos(), "%v missed typecheck", res) | |
| } | |
| }() | |
| switch tag := codeExpr(r.Code(pkgbits.SyncExpr)); tag { | |
| default: | |
| panic("unhandled expression") | |
| case exprLocal: | |
| return typecheck.Expr(r.curfn, r.useLocal()) | |
| case exprGlobal: | |
| // Callee instead of Expr allows builtins | |
| // TODO(mdempsky): Handle builtins directly in exprCall, like method calls? | |
| return typecheck.Callee(r.curfn, r.obj()) | |
| case exprFuncInst: | |
| origPos, pos := r.origPos() | |
| wrapperFn, baseFn, dictPtr := r.funcInst(pos) | |
| if wrapperFn != nil { | |
| return wrapperFn | |
| } | |
| return r.curry(origPos, false, baseFn, dictPtr, nil) | |
| case exprConst: | |
| pos := r.pos() | |
| typ := r.typ() | |
| val := FixValue(typ, r.Value()) | |
| return ir.NewBasicLit(pos, typ, val) | |
| case exprZero: | |
| pos := r.pos() | |
| typ := r.typ() | |
| return ir.NewZero(pos, typ) | |
| case exprCompLit: | |
| return r.compLit() | |
| case exprFuncLit: | |
| return r.funcLit() | |
| case exprFieldVal: | |
| x := r.expr() | |
| pos := r.pos() | |
| sym := r.selector() | |
| return typecheck.XDotField(r.curfn, pos, x, sym) | |
| case exprMethodVal: | |
| recv := r.expr() | |
| origPos, pos := r.origPos() | |
| wrapperFn, baseFn, dictPtr := r.methodExpr() | |
| // For simple wrapperFn values, the existing machinery for creating | |
| // and deduplicating wrapperFn value wrappers still works fine. | |
| if wrapperFn, ok := wrapperFn.(*ir.SelectorExpr); ok && wrapperFn.Op() == ir.OMETHEXPR { | |
| // The receiver expression we constructed may have a shape type. | |
| // For example, in fixedbugs/issue54343.go, `New[int]()` is | |
| // constructed as `New[go.shape.int](&.dict.New[int])`, which | |
| // has type `*T[go.shape.int]`, not `*T[int]`. | |
| // | |
| // However, the method we want to select here is `(*T[int]).M`, | |
| // not `(*T[go.shape.int]).M`, so we need to manually convert | |
| // the type back so that the OXDOT resolves correctly. | |
| // | |
| // TODO(mdempsky): Logically it might make more sense for | |
| // exprCall to take responsibility for setting a non-shaped | |
| // result type, but this is the only place where we care | |
| // currently. And only because existing ir.OMETHVALUE backend | |
| // code relies on n.X.Type() instead of n.Selection.Recv().Type | |
| // (because the latter is types.FakeRecvType() in the case of | |
| // interface method values). | |
| // | |
| if recv.Type().HasShape() { | |
| typ := wrapperFn.Type().Param(0).Type | |
| if !types.Identical(typ, recv.Type()) { | |
| base.FatalfAt(wrapperFn.Pos(), "receiver %L does not match %L", recv, wrapperFn) | |
| } | |
| recv = typecheck.Expr(r.curfn, ir.NewConvExpr(recv.Pos(), ir.OCONVNOP, typ, recv)) | |
| } | |
| n := typecheck.XDotMethod(r.curfn, pos, recv, wrapperFn.Sel, false) | |
| // As a consistency check here, we make sure "n" selected the | |
| // same method (represented by a types.Field) that wrapperFn | |
| // selected. However, for anonymous receiver types, there can be | |
| // multiple such types.Field instances (#58563). So we may need | |
| // to fallback to making sure Sym and Type (including the | |
| // receiver parameter's type) match. | |
| if n.Selection != wrapperFn.Selection { | |
| assert(n.Selection.Sym == wrapperFn.Selection.Sym) | |
| assert(types.Identical(n.Selection.Type, wrapperFn.Selection.Type)) | |
| assert(types.Identical(n.Selection.Type.Recv().Type, wrapperFn.Selection.Type.Recv().Type)) | |
| } | |
| wrapper := methodValueWrapper{ | |
| rcvr: n.X.Type(), | |
| method: n.Selection, | |
| } | |
| if r.importedDef() { | |
| haveMethodValueWrappers = append(haveMethodValueWrappers, wrapper) | |
| } else { | |
| needMethodValueWrappers = append(needMethodValueWrappers, wrapper) | |
| } | |
| return n | |
| } | |
| // For more complicated method expressions, we construct a | |
| // function literal wrapper. | |
| return r.curry(origPos, true, baseFn, recv, dictPtr) | |
| case exprMethodExpr: | |
| recv := r.typ() | |
| implicits := make([]int, r.Len()) | |
| for i := range implicits { | |
| implicits[i] = r.Len() | |
| } | |
| var deref, addr bool | |
| if r.Bool() { | |
| deref = true | |
| } else if r.Bool() { | |
| addr = true | |
| } | |
| origPos, pos := r.origPos() | |
| wrapperFn, baseFn, dictPtr := r.methodExpr() | |
| // If we already have a wrapper and don't need to do anything with | |
| // it, we can just return the wrapper directly. | |
| // | |
| // N.B., we use implicits/deref/addr here as the source of truth | |
| // rather than types.Identical, because the latter can be confused | |
| // by tricky promoted methods (e.g., typeparam/mdempsky/21.go). | |
| if wrapperFn != nil && len(implicits) == 0 && !deref && !addr { | |
| if !types.Identical(recv, wrapperFn.Type().Param(0).Type) { | |
| base.FatalfAt(pos, "want receiver type %v, but have method %L", recv, wrapperFn) | |
| } | |
| return wrapperFn | |
| } | |
| // Otherwise, if the wrapper function is a static method | |
| // expression (OMETHEXPR) and the receiver type is unshaped, then | |
| // we can rely on a statically generated wrapper being available. | |
| if method, ok := wrapperFn.(*ir.SelectorExpr); ok && method.Op() == ir.OMETHEXPR && !recv.HasShape() { | |
| return typecheck.NewMethodExpr(pos, recv, method.Sel) | |
| } | |
| return r.methodExprWrap(origPos, recv, implicits, deref, addr, baseFn, dictPtr) | |
| case exprIndex: | |
| x := r.expr() | |
| pos := r.pos() | |
| index := r.expr() | |
| n := typecheck.Expr(r.curfn, ir.NewIndexExpr(pos, x, index)) | |
| switch n.Op() { | |
| case ir.OINDEXMAP: | |
| n := n.(*ir.IndexExpr) | |
| n.RType = r.rtype(pos) | |
| } | |
| return n | |
| case exprSlice: | |
| x := r.expr() | |
| pos := r.pos() | |
| var index [3]ir.Node | |
| for i := range index { | |
| index[i] = r.optExpr() | |
| } | |
| op := ir.OSLICE | |
| if index[2] != nil { | |
| op = ir.OSLICE3 | |
| } | |
| return typecheck.Expr(r.curfn, ir.NewSliceExpr(pos, op, x, index[0], index[1], index[2])) | |
| case exprAssert: | |
| x := r.expr() | |
| pos := r.pos() | |
| typ := r.exprType() | |
| srcRType := r.rtype(pos) | |
| // TODO(mdempsky): Always emit ODYNAMICDOTTYPE for uniformity? | |
| if typ, ok := typ.(*ir.DynamicType); ok && typ.Op() == ir.ODYNAMICTYPE { | |
| assert := ir.NewDynamicTypeAssertExpr(pos, ir.ODYNAMICDOTTYPE, x, typ.RType) | |
| assert.SrcRType = srcRType | |
| assert.ITab = typ.ITab | |
| return typed(typ.Type(), assert) | |
| } | |
| return typecheck.Expr(r.curfn, ir.NewTypeAssertExpr(pos, x, typ.Type())) | |
| case exprUnaryOp: | |
| op := r.op() | |
| pos := r.pos() | |
| x := r.expr() | |
| switch op { | |
| case ir.OADDR: | |
| return typecheck.Expr(r.curfn, typecheck.NodAddrAt(r.curfn, pos, x)) | |
| case ir.ODEREF: | |
| return typecheck.Expr(r.curfn, ir.NewStarExpr(pos, x)) | |
| } | |
| return typecheck.Expr(r.curfn, ir.NewUnaryExpr(pos, op, x)) | |
| case exprBinaryOp: | |
| op := r.op() | |
| x := r.expr() | |
| pos := r.pos() | |
| y := r.expr() | |
| switch op { | |
| case ir.OANDAND, ir.OOROR: | |
| return typecheck.Expr(r.curfn, ir.NewLogicalExpr(pos, op, x, y)) | |
| case ir.OLSH, ir.ORSH: | |
| // Untyped rhs of non-constant shift, e.g. x << 1.0. | |
| // If we have a constant value, it must be an int >= 0. | |
| if ir.IsConstNode(y) { | |
| val := constant.ToInt(y.Val()) | |
| assert(val.Kind() == constant.Int && constant.Sign(val) >= 0) | |
| } | |
| } | |
| return typecheck.Expr(r.curfn, ir.NewBinaryExpr(pos, op, x, y)) | |
| case exprRecv: | |
| x := r.expr() | |
| pos := r.pos() | |
| for i, n := 0, r.Len(); i < n; i++ { | |
| x = Implicit(typecheck.DotField(pos, x, r.Len())) | |
| } | |
| if r.Bool() { // needs deref | |
| x = Implicit(Deref(pos, x.Type().Elem(), x)) | |
| } else if r.Bool() { // needs addr | |
| x = Implicit(Addr(r.curfn, pos, x)) | |
| } | |
| return x | |
| case exprCall: | |
| var fun ir.Node | |
| var args ir.Nodes | |
| if r.Bool() { // method call | |
| recv := r.expr() | |
| _, method, dictPtr := r.methodExpr() | |
| if recv.Type().IsInterface() && method.Op() == ir.OMETHEXPR { | |
| method := method.(*ir.SelectorExpr) | |
| // The compiler backend (e.g., devirtualization) handle | |
| // OCALLINTER/ODOTINTER better than OCALLFUNC/OMETHEXPR for | |
| // interface calls, so we prefer to continue constructing | |
| // calls that way where possible. | |
| // | |
| // There are also corner cases where semantically it's perhaps | |
| // significant; e.g., fixedbugs/issue15975.go, #38634, #52025. | |
| fun = typecheck.XDotMethod(r.curfn, method.Pos(), recv, method.Sel, true) | |
| } else { | |
| if recv.Type().IsInterface() { | |
| // N.B., this happens currently for typeparam/issue51521.go | |
| // and typeparam/typeswitch3.go. | |
| if base.Flag.LowerM != 0 { | |
| base.WarnfAt(method.Pos(), "imprecise interface call") | |
| } | |
| } | |
| fun = method | |
| args.Append(recv) | |
| } | |
| if dictPtr != nil { | |
| args.Append(dictPtr) | |
| } | |
| } else if r.Bool() { // call to instanced function | |
| pos := r.pos() | |
| _, shapedFn, dictPtr := r.funcInst(pos) | |
| fun = shapedFn | |
| args.Append(dictPtr) | |
| } else { | |
| fun = r.expr() | |
| } | |
| pos := r.pos() | |
| args.Append(r.multiExpr()...) | |
| dots := r.Bool() | |
| n := typecheck.Call(r.curfn, pos, fun, args, dots) | |
| switch n.Op() { | |
| case ir.OAPPEND: | |
| n := n.(*ir.CallExpr) | |
| n.RType = r.rtype(pos) | |
| // For append(a, b...), we don't need the implicit conversion. The typechecker already | |
| // ensured that a and b are both slices with the same base type, or []byte and string. | |
| if n.IsDDD { | |
| if conv, ok := n.Args[1].(*ir.ConvExpr); ok && conv.Op() == ir.OCONVNOP && conv.Implicit() { | |
| n.Args[1] = conv.X | |
| } | |
| } | |
| case ir.OCOPY: | |
| n := n.(*ir.BinaryExpr) | |
| n.RType = r.rtype(pos) | |
| case ir.ODELETE: | |
| n := n.(*ir.CallExpr) | |
| n.RType = r.rtype(pos) | |
| case ir.OUNSAFESLICE: | |
| n := n.(*ir.BinaryExpr) | |
| n.RType = r.rtype(pos) | |
| } | |
| return n | |
| case exprMake: | |
| pos := r.pos() | |
| typ := r.exprType() | |
| extra := r.exprs() | |
| n := typecheck.Expr(r.curfn, ir.NewCallExpr(pos, ir.OMAKE, nil, append([]ir.Node{typ}, extra...))).(*ir.MakeExpr) | |
| n.RType = r.rtype(pos) | |
| return n | |
| case exprNew: | |
| pos := r.pos() | |
| if r.Bool() { | |
| // new(expr) -> tmp := expr; &tmp | |
| x := r.expr() | |
| x = typecheck.DefaultLit(x, nil) // See TODO in exprConvert case. | |
| var init ir.Nodes | |
| addr := ir.NewAddrExpr(pos, r.tempCopy(pos, x, &init)) | |
| addr.SetInit(init) | |
| return typecheck.Expr(r.curfn, addr) | |
| } | |
| // new(T) | |
| return typecheck.Expr(r.curfn, ir.NewUnaryExpr(pos, ir.ONEW, r.exprType())) | |
| case exprSizeof: | |
| return ir.NewUintptr(r.pos(), r.typ().Size()) | |
| case exprAlignof: | |
| return ir.NewUintptr(r.pos(), r.typ().Alignment()) | |
| case exprOffsetof: | |
| pos := r.pos() | |
| typ := r.typ() | |
| types.CalcSize(typ) | |
| var offset int64 | |
| for i := r.Len(); i >= 0; i-- { | |
| field := typ.Field(r.Len()) | |
| offset += field.Offset | |
| typ = field.Type | |
| } | |
| return ir.NewUintptr(pos, offset) | |
| case exprReshape: | |
| typ := r.typ() | |
| x := r.expr() | |
| if types.IdenticalStrict(x.Type(), typ) { | |
| return x | |
| } | |
| // Comparison expressions are constructed as "untyped bool" still. | |
| // | |
| // TODO(mdempsky): It should be safe to reshape them here too, but | |
| // maybe it's better to construct them with the proper type | |
| // instead. | |
| if x.Type() == types.UntypedBool && typ.IsBoolean() { | |
| return x | |
| } | |
| base.AssertfAt(x.Type().HasShape() || typ.HasShape(), x.Pos(), "%L and %v are not shape types", x, typ) | |
| base.AssertfAt(types.Identical(x.Type(), typ), x.Pos(), "%L is not shape-identical to %v", x, typ) | |
| // We use ir.HasUniquePos here as a check that x only appears once | |
| // in the AST, so it's okay for us to call SetType without | |
| // breaking any other uses of it. | |
| // | |
| // Notably, any ONAMEs should already have the exactly right shape | |
| // type and been caught by types.IdenticalStrict above. | |
| base.AssertfAt(ir.HasUniquePos(x), x.Pos(), "cannot call SetType(%v) on %L", typ, x) | |
| if base.Debug.Reshape != 0 { | |
| base.WarnfAt(x.Pos(), "reshaping %L to %v", x, typ) | |
| } | |
| x.SetType(typ) | |
| if call, ok := x.(*ir.CallExpr); ok { | |
| call.Reshape = true | |
| } | |
| return x | |
| case exprConvert: | |
| implicit := r.Bool() | |
| typ := r.typ() | |
| pos := r.pos() | |
| typeWord, srcRType := r.convRTTI(pos) | |
| dstTypeParam := r.Bool() | |
| identical := r.Bool() | |
| x := r.expr() | |
| // spec: "If the type is a type parameter, the constant is converted | |
| // into a non-constant value of the type parameter." | |
| if dstTypeParam && ir.IsConstNode(x) { | |
| // ConvertVal only handles conversions to constant types. | |
| if v := typecheck.ConvertVal(x.Val(), typ, false); v.Kind() != constant.Unknown { | |
| x = ir.NewBasicLit(x.Pos(), typ, v) | |
| // Wrap in an OCONVNOP node to ensure result is non-constant. | |
| n := Implicit(ir.NewConvExpr(pos, ir.OCONVNOP, typ, x)) | |
| n.SetTypecheck(1) | |
| return n | |
| } | |
| // A Go language constant could be converted to a non-constant value, | |
| // like converting string to []byte/[]rune. In this case, just construct | |
| // the conversion expression as usual, see #79960. | |
| } | |
| // TODO(mdempsky): Stop constructing expressions of untyped type. | |
| x = typecheck.DefaultLit(x, typ) | |
| ce := ir.NewConvExpr(pos, ir.OCONV, typ, x) | |
| ce.TypeWord, ce.SrcRType = typeWord, srcRType | |
| if implicit { | |
| ce.SetImplicit(true) | |
| } | |
| n := typecheck.Expr(r.curfn, ce) | |
| // Conversions between non-identical, non-empty interfaces always | |
| // requires a runtime call, even if they have identical underlying | |
| // interfaces. This is because we create separate itab instances | |
| // for each unique interface type, not merely each unique | |
| // interface shape. | |
| // | |
| // However, due to shape types, typecheck.Expr might mistakenly | |
| // think a conversion between two non-empty interfaces are | |
| // identical and set ir.OCONVNOP, instead of ir.OCONVIFACE. To | |
| // ensure we update the itab field appropriately, we force it to | |
| // ir.OCONVIFACE instead when shape types are involved. | |
| // | |
| // TODO(mdempsky): Are there other places we might get this wrong? | |
| // Should this be moved down into typecheck.{Assign,Convert}op? | |
| // This would be a non-issue if itabs were unique for each | |
| // *underlying* interface type instead. | |
| if !identical { | |
| if n, ok := n.(*ir.ConvExpr); ok && n.Op() == ir.OCONVNOP && n.Type().IsInterface() && !n.Type().IsEmptyInterface() && (n.Type().HasShape() || n.X.Type().HasShape()) { | |
| n.SetOp(ir.OCONVIFACE) | |
| } | |
| } | |
| return n | |
| case exprRuntimeBuiltin: | |
| builtin := typecheck.LookupRuntime(r.String()) | |
| return builtin | |
| } | |
| } | |
| // funcInst reads an instantiated function reference, and returns | |
| // three (possibly nil) expressions related to it: | |
| // | |
| // baseFn is always non-nil: it's either a function of the appropriate | |
| // type already, or it has an extra dictionary parameter as the first | |
| // parameter. | |
| // | |
| // If dictPtr is non-nil, then it's a dictionary argument that must be | |
| // passed as the first argument to baseFn. | |
| // | |
| // If wrapperFn is non-nil, then it's either the same as baseFn (if | |
| // dictPtr is nil), or it's semantically equivalent to currying baseFn | |
| // to pass dictPtr. (wrapperFn is nil when dictPtr is an expression | |
| // that needs to be computed dynamically.) | |
| // | |
| // For callers that are creating a call to the returned function, it's | |
| // best to emit a call to baseFn, and include dictPtr in the arguments | |
| // list as appropriate. | |
| // | |
| // For callers that want to return the function without invoking it, | |
| // they may return wrapperFn if it's non-nil; but otherwise, they need | |
| // to create their own wrapper. | |
| func (r *reader) funcInst(pos src.XPos) (wrapperFn, baseFn, dictPtr ir.Node) { | |
| // Like in methodExpr, I'm pretty sure this isn't needed. | |
| var implicits []*types.Type | |
| if r.dict != nil { | |
| implicits = r.dict.targs | |
| } | |
| if r.Bool() { // dynamic subdictionary | |
| idx := r.Len() | |
| info := r.dict.subdicts[idx] | |
| explicits := r.p.typListIdx(info.explicits, r.dict) | |
| baseFn = r.p.objIdx(info.idx, implicits, explicits, true).(*ir.Name) | |
| // TODO(mdempsky): Is there a more robust way to get the | |
| // dictionary pointer type here? | |
| dictPtrType := baseFn.Type().Param(0).Type | |
| dictPtr = typecheck.Expr(r.curfn, ir.NewConvExpr(pos, ir.OCONVNOP, dictPtrType, r.dictWord(pos, r.dict.subdictsOffset()+idx))) | |
| return | |
| } | |
| info := r.objInfo() | |
| explicits := r.p.typListIdx(info.explicits, r.dict) | |
| wrapperFn = r.p.objIdx(info.idx, implicits, explicits, false).(*ir.Name) | |
| baseFn = r.p.objIdx(info.idx, implicits, explicits, true).(*ir.Name) | |
| dictName := r.p.objDictName(info.idx, implicits, explicits) | |
| dictPtr = typecheck.Expr(r.curfn, ir.NewAddrExpr(pos, dictName)) | |
| return | |
| } | |
| func (pr *pkgReader) objDictName(idx index, implicits, explicits []*types.Type) *ir.Name { | |
| rname := pr.newReader(pkgbits.SectionName, idx, pkgbits.SyncObject1) | |
| _, sym := rname.qualifiedIdent() | |
| tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj)) | |
| if tag == pkgbits.ObjStub { | |
| assert(!sym.IsBlank()) | |
| if pri, ok := objReader[sym]; ok { | |
| return pri.pr.objDictName(pri.idx, nil, explicits) | |
| } | |
| base.Fatalf("unresolved stub: %v", sym) | |
| } | |
| dict, err := pr.objDictIdx(sym, idx, implicits, explicits, false) | |
| if err != nil { | |
| base.Fatalf("%v", err) | |
| } | |
| return pr.dictNameOf(dict) | |
| } | |
| // curry returns a function literal that calls fun with arg0 and | |
| // (optionally) arg1, accepting additional arguments to the function | |
| // literal as necessary to satisfy fun's signature. | |
| // | |
| // If nilCheck is true and arg0 is an interface value, then it's | |
| // checked to be non-nil as an initial step at the point of evaluating | |
| // the function literal itself. | |
| func (r *reader) curry(origPos src.XPos, ifaceHack bool, fun ir.Node, arg0, arg1 ir.Node) ir.Node { | |
| var captured ir.Nodes | |
| captured.Append(fun, arg0) | |
| if arg1 != nil { | |
| captured.Append(arg1) | |
| } | |
| params, results := syntheticSig(fun.Type()) | |
| params = params[len(captured)-1:] // skip curried parameters | |
| typ := types.NewSignature(nil, params, results) | |
| addBody := func(pos src.XPos, r *reader, captured []ir.Node) { | |
| fun := captured[0] | |
| var args ir.Nodes | |
| args.Append(captured[1:]...) | |
| args.Append(r.syntheticArgs()...) | |
| r.syntheticTailCall(pos, fun, args) | |
| } | |
| return r.syntheticClosure(origPos, typ, ifaceHack, captured, addBody) | |
| } | |
| // methodExprWrap returns a function literal that changes method's | |
| // first parameter's type to recv, and uses implicits/deref/addr to | |
| // select the appropriate receiver parameter to pass to method. | |
| func (r *reader) methodExprWrap(origPos src.XPos, recv *types.Type, implicits []int, deref, addr bool, method, dictPtr ir.Node) ir.Node { | |
| var captured ir.Nodes | |
| captured.Append(method) | |
| params, results := syntheticSig(method.Type()) | |
| // Change first parameter to recv. | |
| params[0].Type = recv | |
| // If we have a dictionary pointer argument to pass, then omit the | |
| // underlying method expression's dictionary parameter from the | |
| // returned signature too. | |
| if dictPtr != nil { | |
| captured.Append(dictPtr) | |
| params = append(params[:1], params[2:]...) | |
| } | |
| typ := types.NewSignature(nil, params, results) | |
| addBody := func(pos src.XPos, r *reader, captured []ir.Node) { | |
| fn := captured[0] | |
| args := r.syntheticArgs() | |
| // Rewrite first argument based on implicits/deref/addr. | |
| { | |
| arg := args[0] | |
| for _, ix := range implicits { | |
| arg = Implicit(typecheck.DotField(pos, arg, ix)) | |
| } | |
| if deref { | |
| arg = Implicit(Deref(pos, arg.Type().Elem(), arg)) | |
| } else if addr { | |
| arg = Implicit(Addr(r.curfn, pos, arg)) | |
| } | |
| args[0] = arg | |
| } | |
| // Insert dictionary argument, if provided. | |
| if dictPtr != nil { | |
| newArgs := make([]ir.Node, len(args)+1) | |
| newArgs[0] = args[0] | |
| newArgs[1] = captured[1] | |
| copy(newArgs[2:], args[1:]) | |
| args = newArgs | |
| } | |
| r.syntheticTailCall(pos, fn, args) | |
| } | |
| return r.syntheticClosure(origPos, typ, false, captured, addBody) | |
| } | |
| // syntheticClosure constructs a synthetic function literal for | |
| // currying dictionary arguments. origPos is the position used for the | |
| // closure, which must be a non-inlined position. typ is the function | |
| // literal's signature type. | |
| // | |
| // captures is a list of expressions that need to be evaluated at the | |
| // point of function literal evaluation and captured by the function | |
| // literal. If ifaceHack is true and captures[1] is an interface type, | |
| // it's checked to be non-nil after evaluation. | |
| // | |
| // addBody is a callback function to populate the function body. The | |
| // list of captured values passed back has the captured variables for | |
| // use within the function literal, corresponding to the expressions | |
| // in captures. | |
| func (r *reader) syntheticClosure(origPos src.XPos, typ *types.Type, ifaceHack bool, captures ir.Nodes, addBody func(pos src.XPos, r *reader, captured []ir.Node)) ir.Node { | |
| // isSafe reports whether n is an expression that we can safely | |
| // defer to evaluating inside the closure instead, to avoid storing | |
| // them into the closure. | |
| // | |
| // In practice this is always (and only) the wrappee function. | |
| isSafe := func(n ir.Node) bool { | |
| if n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PFUNC { | |
| return true | |
| } | |
| if n.Op() == ir.OMETHEXPR { | |
| return true | |
| } | |
| return false | |
| } | |
| fn := r.inlClosureFunc(origPos, typ, ir.OCLOSURE) | |
| fn.SetWrapper(true) | |
| clo := fn.OClosure | |
| inlPos := clo.Pos() | |
| var init ir.Nodes | |
| for i, n := range captures { | |
| if isSafe(n) { | |
| continue // skip capture; can reference directly | |
| } | |
| tmp := r.tempCopy(inlPos, n, &init) | |
| ir.NewClosureVar(origPos, fn, tmp) | |
| // We need to nil check interface receivers at the point of method | |
| // value evaluation, ugh. | |
| if ifaceHack && i == 1 && n.Type().IsInterface() { | |
| check := ir.NewUnaryExpr(inlPos, ir.OCHECKNIL, ir.NewUnaryExpr(inlPos, ir.OITAB, tmp)) | |
| init.Append(typecheck.Stmt(r.curfn, check)) | |
| } | |
| } | |
| pri := pkgReaderIndex{synthetic: func(pos src.XPos, r *reader) { | |
| captured := make([]ir.Node, len(captures)) | |
| next := 0 | |
| for i, n := range captures { | |
| if isSafe(n) { | |
| captured[i] = n | |
| } else { | |
| captured[i] = r.closureVars[next] | |
| next++ | |
| } | |
| } | |
| assert(next == len(r.closureVars)) | |
| addBody(origPos, r, captured) | |
| }} | |
| bodyReader[fn] = pri | |
| pri.funcBody(fn) | |
| return ir.InitExpr(init, clo) | |
| } | |
| // syntheticSig duplicates and returns the params and results lists | |
| // for sig, but renaming anonymous parameters so they can be assigned | |
| // ir.Names. | |
| func syntheticSig(sig *types.Type) (params, results []*types.Field) { | |
| clone := func(params []*types.Field) []*types.Field { | |
| res := make([]*types.Field, len(params)) | |
| for i, param := range params { | |
| // TODO(mdempsky): It would be nice to preserve the original | |
| // parameter positions here instead, but at least | |
| // typecheck.NewMethodType replaces them with base.Pos, making | |
| // them useless. Worse, the positions copied from base.Pos may | |
| // have inlining contexts, which we definitely don't want here | |
| // (e.g., #54625). | |
| res[i] = types.NewField(base.AutogeneratedPos, param.Sym, param.Type) | |
| res[i].SetIsDDD(param.IsDDD()) | |
| } | |
| return res | |
| } | |
| return clone(sig.Params()), clone(sig.Results()) | |
| } | |
| func (r *reader) optExpr() ir.Node { | |
| if r.Bool() { | |
| return r.expr() | |
| } | |
| return nil | |
| } | |
| // methodExpr reads a method expression reference, and returns three | |
| // (possibly nil) expressions related to it: | |
| // | |
| // baseFn is always non-nil: it's either a function of the appropriate | |
| // type already, or it has an extra dictionary parameter as the second | |
| // parameter (i.e., immediately after the promoted receiver | |
| // parameter). | |
| // | |
| // If dictPtr is non-nil, then it's a dictionary argument that must be | |
| // passed as the second argument to baseFn. | |
| // | |
| // If wrapperFn is non-nil, then it's either the same as baseFn (if | |
| // dictPtr is nil), or it's semantically equivalent to currying baseFn | |
| // to pass dictPtr. (wrapperFn is nil when dictPtr is an expression | |
| // that needs to be computed dynamically.) | |
| // | |
| // For callers that are creating a call to the returned method, it's | |
| // best to emit a call to baseFn, and include dictPtr in the arguments | |
| // list as appropriate. | |
| // | |
| // For callers that want to return a method expression without | |
| // invoking it, they may return wrapperFn if it's non-nil; but | |
| // otherwise, they need to create their own wrapper. | |
| func (r *reader) methodExpr() (wrapperFn, baseFn, dictPtr ir.Node) { | |
| recv := r.typ() | |
| var sig *types.Type | |
| generic := r.Version().Has(pkgbits.GenericMethods) && r.Bool() | |
| if !generic { | |
| // Signature type to return (i.e., recv prepended to the method's | |
| // normal parameters list). | |
| sig = typecheck.NewMethodType(r.typ(), recv) | |
| } | |
| pos := r.pos() | |
| sym := r.selector() | |
| if r.Bool() { // type parameter method expression | |
| idx := r.Len() | |
| word := r.dictWord(pos, r.dict.typeParamMethodExprsOffset()+idx) | |
| // TODO(mdempsky): If the type parameter was instantiated with an | |
| // interface type (i.e., embed.IsInterface()), then we could | |
| // return the OMETHEXPR instead and save an indirection. | |
| // We wrote the method expression's entry point PC into the | |
| // dictionary, but for Go `func` values we need to return a | |
| // closure (i.e., pointer to a structure with the PC as the first | |
| // field). Because method expressions don't have any closure | |
| // variables, we pun the dictionary entry as the closure struct. | |
| fn := typecheck.Expr(r.curfn, ir.NewConvExpr(pos, ir.OCONVNOP, sig, ir.NewAddrExpr(pos, word))) | |
| return fn, fn, nil | |
| } | |
| if r.Bool() { // dynamic subdictionary | |
| idx := r.Len() | |
| info := r.dict.subdicts[idx] | |
| explicits := r.p.typListIdx(info.explicits, r.dict) | |
| shapedObj := r.p.objIdx(info.idx, nil, explicits, true).(*ir.Name) | |
| shapedFn := shapedMethodExpr(pos, shapedObj, sym) | |
| // TODO(mdempsky): Is there a more robust way to get the | |
| // dictionary pointer type here? | |
| dictPtrType := shapedFn.Type().Param(1).Type | |
| dictPtr := typecheck.Expr(r.curfn, ir.NewConvExpr(pos, ir.OCONVNOP, dictPtrType, r.dictWord(pos, r.dict.subdictsOffset()+idx))) | |
| return nil, shapedFn, dictPtr | |
| } | |
| if r.Bool() { // static dictionary | |
| info := r.objInfo() | |
| explicits := r.p.typListIdx(info.explicits, r.dict) | |
| shapedObj := r.p.objIdx(info.idx, nil, explicits, true).(*ir.Name) | |
| shapedFn := shapedMethodExpr(pos, shapedObj, sym) | |
| dict := r.p.objDictName(info.idx, nil, explicits) | |
| dictPtr := typecheck.Expr(r.curfn, ir.NewAddrExpr(pos, dict)) | |
| // Check that dictPtr matches shapedFn's dictionary parameter. | |
| if !types.Identical(dictPtr.Type(), shapedFn.Type().Param(1).Type) { | |
| base.FatalfAt(pos, "dict %L, but shaped method %L", dict, shapedFn) | |
| } | |
| if !generic { | |
| // For statically known instantiations, we can take advantage of | |
| // the stenciled wrapper. | |
| base.AssertfAt(!recv.HasShape(), pos, "shaped receiver %v", recv) | |
| wrapperFn := typecheck.NewMethodExpr(pos, recv, sym) | |
| base.AssertfAt(types.Identical(sig, wrapperFn.Type()), pos, "wrapper %L does not have type %v", wrapperFn, sig) | |
| return wrapperFn, shapedFn, dictPtr | |
| } else { | |
| // Also statically known, but there is a good amount of existing | |
| // machinery downstream which makes assumptions about method | |
| // wrapper functions. It's safest not to emit them for now. | |
| // TODO(mark): Emit wrapper functions for generic methods. | |
| return nil, shapedFn, dictPtr | |
| } | |
| } | |
| // Simple method expression; no dictionary needed. | |
| base.AssertfAt(!recv.HasShape() || recv.IsInterface(), pos, "shaped receiver %v", recv) | |
| fn := typecheck.NewMethodExpr(pos, recv, sym) | |
| return fn, fn, nil | |
| } | |
| // shapedMethodExpr creates an OMETHEXPR for obj using sym. | |
| // | |
| // If obj is an OTYPE, it must refer to a generic type. If obj is an ONAME, | |
| // it must refer to a generic method. In either case, sym.Name must be the | |
| // unqualified name of the method. | |
| // | |
| // For example, given: | |
| // | |
| // package p | |
| // | |
| // type T[P any] struct {} | |
| // | |
| // func (T[P]) m() {} | |
| // func (T[P]) n[Q any]() {} | |
| // | |
| // then, using S as go.shape.int: | |
| // - in T[int].m, obj is T[S] and sym.Name is "m". | |
| // - in T[int].n[int], obj is T[S].n[S] and sym.Name is "n". | |
| // | |
| // Note that we could have pushed dictionaries down to methods in every case, | |
| // but since non-generic methods will always share the same "type environment" | |
| // as their defining type, we can optimize by reusing the type's dictionary. | |
| func shapedMethodExpr(pos src.XPos, obj *ir.Name, sym *types.Sym) ir.Node { | |
| if obj.Op() == ir.OTYPE { | |
| // non-generic method on generic type | |
| typ := obj.Type() | |
| assert(typ.HasShape()) | |
| method := func() *types.Field { | |
| for _, m := range typ.Methods() { | |
| if m.Sym == sym { | |
| return m | |
| } | |
| } | |
| base.FatalfAt(pos, "failed to find method %v in shaped type %v", sym, typ) | |
| panic("unreachable") | |
| }() | |
| return typecheck.NewMethodExpr(pos, method.Type.Recv().Type, sym) | |
| } else { | |
| // generic method on possibly generic type | |
| assert(obj.Op() == ir.ONAME && obj.Class == ir.PFUNC) | |
| typ := obj.Type() | |
| assert(typ.HasShape()) | |
| // OMETHEXPR assumes that the linker symbol to call looks like "<type sym>.<method sym>". | |
| // This works because non-generic method symbols are relative to their type. But generic | |
| // methods use fully-qualified names, so this won't work. | |
| // | |
| // To use OMETHEXPR for generic methods, we craft a dummy field on the type by removing | |
| // the qualifier; OMETHEXPR will put it back later. | |
| lsym := obj.Linksym().Name | |
| // Since the method is generic, we know the method name must be followed by a bracket. | |
| // TODO(mark): It's not ideal to rely on string naming here. Find a more robust solution. | |
| idx := func() int { // Find the index of the bracket following the method name. | |
| depth, i := 0, len(lsym)-1 | |
| for { | |
| switch lsym[i] { | |
| case ']': | |
| depth++ | |
| case '[': | |
| depth-- | |
| } | |
| if depth == 0 { | |
| return i | |
| } | |
| i-- | |
| } | |
| }() | |
| msym := sym.Pkg.Lookup(lsym[idx-len(sym.Name):]) | |
| // Note that the field name here includes the type arguments; while also not ideal, the | |
| // types package does not seem to complain. | |
| m := types.NewField(obj.Pos(), msym, typ) | |
| m.Nname = obj | |
| n := ir.NewSelectorExpr(pos, ir.OMETHEXPR, ir.TypeNode(typ.Recv().Type), msym) | |
| n.Selection = m | |
| n.SetType(typecheck.NewMethodType(typ, typ.Recv().Type)) | |
| n.SetTypecheck(1) | |
| return n | |
| } | |
| } | |
| func (r *reader) multiExpr() []ir.Node { | |
| r.Sync(pkgbits.SyncMultiExpr) | |
| if r.Bool() { // N:1 | |
| pos := r.pos() | |
| expr := r.expr() | |
| results := make([]ir.Node, r.Len()) | |
| as := ir.NewAssignListStmt(pos, ir.OAS2, nil, []ir.Node{expr}) | |
| as.Def = true | |
| for i := range results { | |
| tmp := r.temp(pos, r.typ()) | |
| tmp.Defn = as | |
| as.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, tmp)) | |
| as.Lhs.Append(tmp) | |
| res := ir.Node(tmp) | |
| if r.Bool() { | |
| n := ir.NewConvExpr(pos, ir.OCONV, r.typ(), res) | |
| n.TypeWord, n.SrcRType = r.convRTTI(pos) | |
| n.SetImplicit(true) | |
| res = typecheck.Expr(r.curfn, n) | |
| } | |
| results[i] = res | |
| } | |
| // TODO(mdempsky): Could use ir.InlinedCallExpr instead? | |
| results[0] = ir.InitExpr([]ir.Node{typecheck.Stmt(r.curfn, as)}, results[0]) | |
| return results | |
| } | |
| // N:N | |
| exprs := make([]ir.Node, r.Len()) | |
| if len(exprs) == 0 { | |
| return nil | |
| } | |
| for i := range exprs { | |
| exprs[i] = r.expr() | |
| } | |
| return exprs | |
| } | |
| // temp returns a new autotemp of the specified type. | |
| func (r *reader) temp(pos src.XPos, typ *types.Type) *ir.Name { | |
| return typecheck.TempAt(pos, r.curfn, typ) | |
| } | |
| // tempCopy declares and returns a new autotemp initialized to the | |
| // value of expr. | |
| func (r *reader) tempCopy(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name { | |
| tmp := r.temp(pos, expr.Type()) | |
| init.Append(typecheck.Stmt(r.curfn, ir.NewDecl(pos, ir.ODCL, tmp))) | |
| assign := ir.NewAssignStmt(pos, tmp, expr) | |
| assign.Def = true | |
| init.Append(typecheck.Stmt(r.curfn, assign)) | |
| tmp.Defn = assign | |
| return tmp | |
| } | |
| func (r *reader) compLit() ir.Node { | |
| r.Sync(pkgbits.SyncCompLit) | |
| pos := r.pos() | |
| typ0 := r.typ() | |
| typ := typ0 | |
| if typ.IsPtr() { | |
| typ = typ.Elem() | |
| } | |
| if typ.Kind() == types.TFORW { | |
| base.FatalfAt(pos, "unresolved composite literal type: %v", typ) | |
| } | |
| var rtype ir.Node | |
| if typ.IsMap() { | |
| rtype = r.rtype(pos) | |
| } | |
| var elems []ir.Node | |
| if r.Version().Has(pkgbits.CompactCompLiterals) { | |
| n := r.Int() | |
| elems = make([]ir.Node, max(n, -n) /* abs(n) */) | |
| switch typ.Kind() { | |
| default: | |
| base.FatalfAt(pos, "unexpected composite literal type: %v", typ) | |
| case types.TARRAY: | |
| r.arrayElems(n >= 0, elems) | |
| case types.TMAP: | |
| r.mapElems(elems) | |
| case types.TSLICE: | |
| r.arrayElems(n >= 0, elems) | |
| case types.TSTRUCT: | |
| r.structElems(typ, n >= 0, elems) | |
| } | |
| } else { | |
| elems = make([]ir.Node, r.Len()) | |
| isStruct := typ.Kind() == types.TSTRUCT | |
| for i := range elems { | |
| elemp := &elems[i] | |
| if isStruct { | |
| sk := ir.NewStructKeyExpr(r.pos(), typ.Field(r.Len()), nil) | |
| *elemp, elemp = sk, &sk.Value | |
| } else if r.Bool() { | |
| kv := ir.NewKeyExpr(r.pos(), r.expr(), nil) | |
| *elemp, elemp = kv, &kv.Value | |
| } | |
| *elemp = r.expr() | |
| } | |
| } | |
| lit := typecheck.Expr(r.curfn, ir.NewCompLitExpr(pos, ir.OCOMPLIT, typ, elems)) | |
| if rtype != nil { | |
| lit := lit.(*ir.CompLitExpr) | |
| lit.RType = rtype | |
| } | |
| if typ0.IsPtr() { | |
| lit = typecheck.Expr(r.curfn, typecheck.NodAddrAt(r.curfn, pos, lit)) | |
| lit.SetType(typ0) | |
| } | |
| return lit | |
| } | |
| func (r *reader) arrayElems(valuesOnly bool, elems []ir.Node) { | |
| if valuesOnly { | |
| for i := range elems { | |
| elems[i] = r.expr() | |
| } | |
| return | |
| } | |
| // some elements may have a key | |
| for i := range elems { | |
| if r.Bool() { | |
| kv := ir.NewKeyExpr(r.pos(), r.expr(), nil) | |
| kv.Value = r.expr() | |
| elems[i] = kv | |
| } else { | |
| elems[i] = r.expr() | |
| } | |
| } | |
| } | |
| func (r *reader) mapElems(elems []ir.Node) { | |
| // all elements have a key | |
| for i := range elems { | |
| kv := ir.NewKeyExpr(r.pos(), r.expr(), nil) | |
| kv.Value = r.expr() | |
| elems[i] = kv | |
| } | |
| } | |
| func (r *reader) structElems(typ *types.Type, valuesOnly bool, elems []ir.Node) { | |
| if valuesOnly { | |
| for i := range elems { | |
| sk := ir.NewStructKeyExpr(r.pos(), typ.Field(i), nil) | |
| sk.Value = r.expr() | |
| elems[i] = sk | |
| } | |
| return | |
| } | |
| // all elements have a key | |
| for i := range elems { | |
| pos := r.pos() | |
| var fld *types.Field | |
| if n := r.Int(); n < 0 { | |
| // embedded field | |
| typ := typ // don't modify the original typ | |
| for range -n { | |
| fld = typ.Field(r.Int()) | |
| typ = fld.Type | |
| } | |
| } else { // n >= 0 | |
| fld = typ.Field(n) | |
| } | |
| sk := ir.NewStructKeyExpr(pos, fld, nil) | |
| sk.Value = r.expr() | |
| elems[i] = sk | |
| } | |
| } | |
| func (r *reader) funcLit() ir.Node { | |
| r.Sync(pkgbits.SyncFuncLit) | |
| // The underlying function declaration (including its parameters' | |
| // positions, if any) need to remain the original, uninlined | |
| // positions. This is because we track inlining-context on nodes so | |
| // we can synthesize the extra implied stack frames dynamically when | |
| // generating tracebacks, whereas those stack frames don't make | |
| // sense *within* the function literal. (Any necessary inlining | |
| // adjustments will have been applied to the call expression | |
| // instead.) | |
| // | |
| // This is subtle, and getting it wrong leads to cycles in the | |
| // inlining tree, which lead to infinite loops during stack | |
| // unwinding (#46234, #54625). | |
| // | |
| // Note that we *do* want the inline-adjusted position for the | |
| // OCLOSURE node, because that position represents where any heap | |
| // allocation of the closure is credited (#49171). | |
| r.suppressInlPos++ | |
| origPos := r.pos() | |
| sig := r.signature(nil) | |
| r.suppressInlPos-- | |
| why := ir.OCLOSURE | |
| if r.Bool() { | |
| why = ir.ORANGE | |
| } | |
| fn := r.inlClosureFunc(origPos, sig, why) | |
| fn.ClosureVars = make([]*ir.Name, 0, r.Len()) | |
| for len(fn.ClosureVars) < cap(fn.ClosureVars) { | |
| // TODO(mdempsky): I think these should be original positions too | |
| // (i.e., not inline-adjusted). | |
| ir.NewClosureVar(r.pos(), fn, r.useLocal()) | |
| } | |
| if param := r.dictParam; param != nil { | |
| // If we have a dictionary parameter, capture it too. For | |
| // simplicity, we capture it last and unconditionally. | |
| ir.NewClosureVar(param.Pos(), fn, param) | |
| } | |
| r.addBody(fn, nil) | |
| return fn.OClosure | |
| } | |
| // inlClosureFunc constructs a new closure function, but correctly | |
| // handles inlining. | |
| func (r *reader) inlClosureFunc(origPos src.XPos, sig *types.Type, why ir.Op) *ir.Func { | |
| curfn := r.inlCaller | |
| if curfn == nil { | |
| curfn = r.curfn | |
| } | |
| var gen int | |
| if why == ir.ORANGE { | |
| r.rangeLitGen++ | |
| gen = r.rangeLitGen | |
| } else { | |
| r.funcLitGen++ | |
| gen = r.funcLitGen | |
| } | |
| // TODO(mdempsky): Remove hard-coding of typecheck.Target. | |
| return ir.NewClosureFunc(origPos, r.inlPos(origPos), why, sig, curfn, typecheck.Target, gen) | |
| } | |
| func (r *reader) exprList() []ir.Node { | |
| r.Sync(pkgbits.SyncExprList) | |
| return r.exprs() | |
| } | |
| func (r *reader) exprs() []ir.Node { | |
| r.Sync(pkgbits.SyncExprs) | |
| nodes := make([]ir.Node, r.Len()) | |
| if len(nodes) == 0 { | |
| return nil // TODO(mdempsky): Unclear if this matters. | |
| } | |
| for i := range nodes { | |
| nodes[i] = r.expr() | |
| } | |
| return nodes | |
| } | |
| // dictWord returns an expression to return the specified | |
| // uintptr-typed word from the dictionary parameter. | |
| func (r *reader) dictWord(pos src.XPos, idx int) ir.Node { | |
| base.AssertfAt(r.dictParam != nil, pos, "expected dictParam in %v", r.curfn) | |
| return typecheck.Expr(r.curfn, ir.NewIndexExpr(pos, r.dictParam, ir.NewInt(pos, int64(idx)))) | |
| } | |
| // rttiWord is like dictWord, but converts it to *byte (the type used | |
| // internally to represent *runtime._type and *runtime.itab). | |
| func (r *reader) rttiWord(pos src.XPos, idx int) ir.Node { | |
| return typecheck.Expr(r.curfn, ir.NewConvExpr(pos, ir.OCONVNOP, types.NewPtr(types.Types[types.TUINT8]), r.dictWord(pos, idx))) | |
| } | |
| // rtype reads a type reference from the element bitstream, and | |
| // returns an expression of type *runtime._type representing that | |
| // type. | |
| func (r *reader) rtype(pos src.XPos) ir.Node { | |
| _, rtype := r.rtype0(pos) | |
| return rtype | |
| } | |
| func (r *reader) rtype0(pos src.XPos) (typ *types.Type, rtype ir.Node) { | |
| r.Sync(pkgbits.SyncRType) | |
| if r.Bool() { // derived type | |
| idx := r.Len() | |
| info := r.dict.rtypes[idx] | |
| typ = r.p.typIdx(info, r.dict, true) | |
| rtype = r.rttiWord(pos, r.dict.rtypesOffset()+idx) | |
| return | |
| } | |
| typ = r.typ() | |
| rtype = reflectdata.TypePtrAt(r.curfn, pos, typ) | |
| return | |
| } | |
| // varDictIndex populates name.DictIndex if name is a derived type. | |
| func (r *reader) varDictIndex(name *ir.Name) { | |
| if r.Bool() { | |
| idx := 1 + r.dict.rtypesOffset() + r.Len() | |
| if int(uint16(idx)) != idx { | |
| base.FatalfAt(name.Pos(), "DictIndex overflow for %v: %v", name, idx) | |
| } | |
| name.DictIndex = uint16(idx) | |
| } | |
| } | |
| // itab returns a (typ, iface) pair of types. | |
| // | |
| // typRType and ifaceRType are expressions that evaluate to the | |
| // *runtime._type for typ and iface, respectively. | |
| // | |
| // If typ is a concrete type and iface is a non-empty interface type, | |
| // then itab is an expression that evaluates to the *runtime.itab for | |
| // the pair. Otherwise, itab is nil. | |
| func (r *reader) itab(pos src.XPos) (typ *types.Type, typRType ir.Node, iface *types.Type, ifaceRType ir.Node, itab ir.Node) { | |
| typ, typRType = r.rtype0(pos) | |
| iface, ifaceRType = r.rtype0(pos) | |
| idx := -1 | |
| if r.Bool() { | |
| idx = r.Len() | |
| } | |
| if !typ.IsInterface() && iface.IsInterface() && !iface.IsEmptyInterface() { | |
| if idx >= 0 { | |
| itab = r.rttiWord(pos, r.dict.itabsOffset()+idx) | |
| } else { | |
| base.AssertfAt(!typ.HasShape(), pos, "%v is a shape type", typ) | |
| base.AssertfAt(!iface.HasShape(), pos, "%v is a shape type", iface) | |
| lsym := reflectdata.ITabLsym(typ, iface) | |
| itab = typecheck.LinksymAddr(r.curfn, pos, lsym, types.Types[types.TUINT8]) | |
| } | |
| } | |
| return | |
| } | |
| // convRTTI returns expressions appropriate for populating an | |
| // ir.ConvExpr's TypeWord and SrcRType fields, respectively. | |
| func (r *reader) convRTTI(pos src.XPos) (typeWord, srcRType ir.Node) { | |
| r.Sync(pkgbits.SyncConvRTTI) | |
| src, srcRType0, dst, dstRType, itab := r.itab(pos) | |
| if !dst.IsInterface() { | |
| return | |
| } | |
| // See reflectdata.ConvIfaceTypeWord. | |
| switch { | |
| case dst.IsEmptyInterface(): | |
| if !src.IsInterface() { | |
| typeWord = srcRType0 // direct eface construction | |
| } | |
| case !src.IsInterface(): | |
| typeWord = itab // direct iface construction | |
| default: | |
| typeWord = dstRType // convI2I | |
| } | |
| // See reflectdata.ConvIfaceSrcRType. | |
| if !src.IsInterface() { | |
| srcRType = srcRType0 | |
| } | |
| return | |
| } | |
| func (r *reader) exprType() ir.Node { | |
| r.Sync(pkgbits.SyncExprType) | |
| pos := r.pos() | |
| var typ *types.Type | |
| var rtype, itab ir.Node | |
| if r.Bool() { | |
| // non-empty interface | |
| typ, rtype, _, _, itab = r.itab(pos) | |
| if !typ.IsInterface() { | |
| rtype = nil // TODO(mdempsky): Leave set? | |
| } | |
| } else { | |
| typ, rtype = r.rtype0(pos) | |
| if !r.Bool() { // not derived | |
| return ir.TypeNode(typ) | |
| } | |
| } | |
| dt := ir.NewDynamicType(pos, rtype) | |
| dt.ITab = itab | |
| dt = typed(typ, dt).(*ir.DynamicType) | |
| if st := dt.ToStatic(); st != nil { | |
| return st | |
| } | |
| return dt | |
| } | |
| func (r *reader) op() ir.Op { | |
| r.Sync(pkgbits.SyncOp) | |
| return ir.Op(r.Len()) | |
| } | |
| // @@@ Package initialization | |
| func (r *reader) pkgInit(self *types.Pkg, target *ir.Package) { | |
| cgoPragmas := make([][]string, r.Len()) | |
| for i := range cgoPragmas { | |
| cgoPragmas[i] = r.Strings() | |
| } | |
| target.CgoPragmas = cgoPragmas | |
| r.pkgInitOrder(target) | |
| r.pkgDecls(target) | |
| r.Sync(pkgbits.SyncEOF) | |
| } | |
| // pkgInitOrder creates a synthetic init function to handle any | |
| // package-scope initialization statements. | |
| func (r *reader) pkgInitOrder(target *ir.Package) { | |
| initOrder := make([]ir.Node, r.Len()) | |
| if len(initOrder) == 0 { | |
| return | |
| } | |
| // Make a function that contains all the initialization statements. | |
| pos := base.AutogeneratedPos | |
| base.Pos = pos | |
| fn := ir.NewFunc(pos, pos, typecheck.Lookup("init"), types.NewSignature(nil, nil, nil)) | |
| fn.SetIsPackageInit(true) | |
| fn.SetInlinabilityChecked(true) // suppress useless "can inline" diagnostics | |
| typecheck.DeclFunc(fn) | |
| r.curfn = fn | |
| var varInitFns []*ir.Func | |
| if len(initOrder) <= maxInitStatements { | |
| fn.Body = r.doPkgInitOrder(initOrder) | |
| } else { | |
| varInitFns = r.splitLargeInitOrder(initOrder) | |
| calls := make([]ir.Node, len(varInitFns)) | |
| for i, varInitFn := range varInitFns { | |
| ir.WithPos(fn, func() { | |
| calls[i] = typecheck.Call(r.curfn, varInitFn.Pos(), varInitFn.Nname, nil, false) | |
| }) | |
| } | |
| fn.Body = calls | |
| } | |
| r.curfn = nil | |
| r.locals = nil | |
| // Outline (if legal/profitable) global map inits. | |
| staticinit.OutlineMapInits(fn) | |
| for _, varInitFn := range varInitFns { | |
| staticinit.OutlineMapInits(varInitFn) | |
| } | |
| target.Inits = append(target.Inits, fn) | |
| } | |
| const maxInitStatements = 1000 | |
| func (r *reader) generateVarInitFunc(body []ir.Node) *ir.Func { | |
| fn := staticinit.GenerateVarInitFunc() | |
| typecheck.DeclFunc(fn) | |
| old := r.curfn | |
| r.curfn = fn | |
| fn.Body = r.doPkgInitOrder(body) | |
| r.curfn = old | |
| return fn | |
| } | |
| func (r *reader) doPkgInitOrder(initOrder []ir.Node) []ir.Node { | |
| for i := range initOrder { | |
| lhs := make([]ir.Node, r.Len()) | |
| for j := range lhs { | |
| lhs[j] = r.obj() | |
| } | |
| rhs := r.expr() | |
| pos := lhs[0].Pos() | |
| var as ir.Node | |
| if len(lhs) == 1 { | |
| as = typecheck.Stmt(r.curfn, ir.NewAssignStmt(pos, lhs[0], rhs)) | |
| } else { | |
| as = typecheck.Stmt(r.curfn, ir.NewAssignListStmt(pos, ir.OAS2, lhs, []ir.Node{rhs})) | |
| } | |
| for _, v := range lhs { | |
| v.(*ir.Name).Defn = as | |
| } | |
| initOrder[i] = as | |
| } | |
| return initOrder | |
| } | |
| func (r *reader) splitLargeInitOrder(initOrder []ir.Node) []*ir.Func { | |
| var initFuncs []*ir.Func | |
| for chunk := range slices.Chunk(initOrder, maxInitStatements) { | |
| initFuncs = append(initFuncs, r.generateVarInitFunc(chunk)) | |
| } | |
| return initFuncs | |
| } | |
| func (r *reader) pkgDecls(target *ir.Package) { | |
| r.Sync(pkgbits.SyncDecls) | |
| for { | |
| switch code := codeDecl(r.Code(pkgbits.SyncDecl)); code { | |
| default: | |
| panic(fmt.Sprintf("unhandled decl: %v", code)) | |
| case declEnd: | |
| return | |
| case declFunc: | |
| names := r.pkgObjs(target) | |
| assert(len(names) == 1) | |
| target.Funcs = append(target.Funcs, names[0].Func) | |
| case declMethod: | |
| typ := r.typ() | |
| sym := r.selector() | |
| method := typecheck.Lookdot1(nil, sym, typ, typ.Methods(), 0) | |
| target.Funcs = append(target.Funcs, method.Nname.(*ir.Name).Func) | |
| case declVar: | |
| names := r.pkgObjs(target) | |
| if n := r.Len(); n > 0 { | |
| assert(len(names) == 1) | |
| embeds := make([]ir.Embed, n) | |
| for i := range embeds { | |
| embeds[i] = ir.Embed{Pos: r.pos(), Patterns: r.Strings()} | |
| } | |
| names[0].Embed = &embeds | |
| target.Embeds = append(target.Embeds, names[0]) | |
| } | |
| case declOther: | |
| r.pkgObjs(target) | |
| } | |
| } | |
| } | |
| func (r *reader) pkgObjs(target *ir.Package) []*ir.Name { | |
| r.Sync(pkgbits.SyncDeclNames) | |
| nodes := make([]*ir.Name, r.Len()) | |
| for i := range nodes { | |
| r.Sync(pkgbits.SyncDeclName) | |
| name := r.obj().(*ir.Name) | |
| nodes[i] = name | |
| sym := name.Sym() | |
| if sym.IsBlank() { | |
| continue | |
| } | |
| switch name.Class { | |
| default: | |
| base.FatalfAt(name.Pos(), "unexpected class: %v", name.Class) | |
| case ir.PEXTERN: | |
| target.Externs = append(target.Externs, name) | |
| case ir.PFUNC: | |
| assert(name.Type().Recv() == nil) | |
| // TODO(mdempsky): Cleaner way to recognize init? | |
| if strings.HasPrefix(sym.Name, "init.") { | |
| target.Inits = append(target.Inits, name.Func) | |
| } | |
| } | |
| if base.Ctxt.Flag_dynlink && types.LocalPkg.Name == "main" && types.IsExported(sym.Name) && name.Op() == ir.ONAME { | |
| assert(!sym.OnExportList()) | |
| target.PluginExports = append(target.PluginExports, name) | |
| sym.SetOnExportList(true) | |
| } | |
| if base.Flag.AsmHdr != "" && (name.Op() == ir.OLITERAL || name.Op() == ir.OTYPE) { | |
| assert(!sym.Asm()) | |
| target.AsmHdrDecls = append(target.AsmHdrDecls, name) | |
| sym.SetAsm(true) | |
| } | |
| } | |
| return nodes | |
| } | |
| // @@@ Inlining | |
| // unifiedHaveInlineBody reports whether we have the function body for | |
| // fn, so we can inline it. | |
| func unifiedHaveInlineBody(fn *ir.Func) bool { | |
| if fn.Inl == nil { | |
| return false | |
| } | |
| _, ok := bodyReaderFor(fn) | |
| return ok | |
| } | |
| var inlgen = 0 | |
| // unifiedInlineCall implements inline.NewInline by re-reading the function | |
| // body from its Unified IR export data. | |
| func unifiedInlineCall(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlIndex int, profile *pgoir.Profile) *ir.InlinedCallExpr { | |
| pri, ok := bodyReaderFor(fn) | |
| if !ok { | |
| base.FatalfAt(call.Pos(), "cannot inline call to %v: missing inline body", fn) | |
| } | |
| if !fn.Inl.HaveDcl { | |
| expandInline(fn, pri) | |
| } | |
| r := pri.asReader(pkgbits.SectionBody, pkgbits.SyncFuncBody) | |
| tmpfn := ir.NewFunc(fn.Pos(), fn.Nname.Pos(), callerfn.Sym(), fn.Type()) | |
| r.curfn = tmpfn | |
| r.inlCaller = callerfn | |
| r.inlCall = call | |
| r.inlFunc = fn | |
| r.inlTreeIndex = inlIndex | |
| r.inlPosBases = make(map[*src.PosBase]*src.PosBase) | |
| r.funarghack = true | |
| r.closureVars = make([]*ir.Name, len(r.inlFunc.ClosureVars)) | |
| for i, cv := range r.inlFunc.ClosureVars { | |
| // TODO(mdempsky): It should be possible to support this case, but | |
| // for now we rely on the inliner avoiding it. | |
| if cv.Outer.Curfn != callerfn { | |
| base.FatalfAt(call.Pos(), "inlining closure call across frames") | |
| } | |
| r.closureVars[i] = cv.Outer | |
| } | |
| if len(r.closureVars) != 0 && r.hasTypeParams() { | |
| r.dictParam = r.closureVars[len(r.closureVars)-1] // dictParam is last; see reader.funcLit | |
| } | |
| r.declareParams() | |
| var inlvars, retvars []*ir.Name | |
| { | |
| sig := r.curfn.Type() | |
| endParams := sig.NumRecvs() + sig.NumParams() | |
| endResults := endParams + sig.NumResults() | |
| inlvars = r.curfn.Dcl[:endParams] | |
| retvars = r.curfn.Dcl[endParams:endResults] | |
| } | |
| r.delayResults = fn.Inl.CanDelayResults | |
| r.retlabel = typecheck.AutoLabel(callerfn, ".i") | |
| inlgen++ | |
| init := ir.TakeInit(call) | |
| // For normal function calls, the function callee expression | |
| // may contain side effects. Make sure to preserve these, | |
| // if necessary (#42703). | |
| if call.Op() == ir.OCALLFUNC { | |
| inline.CalleeEffects(&init, call.Fun) | |
| } | |
| var args ir.Nodes | |
| if call.Op() == ir.OCALLMETH { | |
| base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck") | |
| } | |
| args.Append(call.Args...) | |
| // Create assignment to declare and initialize inlvars. | |
| as2 := ir.NewAssignListStmt(call.Pos(), ir.OAS2, ir.ToNodes(inlvars), args) | |
| as2.Def = true | |
| var as2init ir.Nodes | |
| for _, name := range inlvars { | |
| if ir.IsBlank(name) { | |
| continue | |
| } | |
| // TODO(mdempsky): Use inlined position of name.Pos() instead? | |
| as2init.Append(ir.NewDecl(call.Pos(), ir.ODCL, name)) | |
| name.Defn = as2 | |
| } | |
| as2.SetInit(as2init) | |
| init.Append(typecheck.Stmt(callerfn, as2)) | |
| if !r.delayResults { | |
| // If not delaying retvars, declare and zero initialize the | |
| // result variables now. | |
| for _, name := range retvars { | |
| // TODO(mdempsky): Use inlined position of name.Pos() instead? | |
| init.Append(ir.NewDecl(call.Pos(), ir.ODCL, name)) | |
| ras := ir.NewAssignStmt(call.Pos(), name, nil) | |
| init.Append(typecheck.Stmt(callerfn, ras)) | |
| } | |
| } | |
| // Add an inline mark just before the inlined body. | |
| // This mark is inline in the code so that it's a reasonable spot | |
| // to put a breakpoint. Not sure if that's really necessary or not | |
| // (in which case it could go at the end of the function instead). | |
| // Note issue 28603. | |
| init.Append(ir.NewInlineMarkStmt(call.Pos().WithIsStmt(), int64(r.inlTreeIndex))) | |
| ir.WithPos(r.curfn, func() { | |
| if !r.syntheticBody(call.Pos()) { | |
| assert(r.Bool()) // have body | |
| r.curfn.Body = r.stmts() | |
| r.curfn.Endlineno = r.pos() | |
| } | |
| // TODO(mdempsky): This shouldn't be necessary. Inlining might | |
| // read in new function/method declarations, which could | |
| // potentially be recursively inlined themselves; but we shouldn't | |
| // need to read in the non-inlined bodies for the declarations | |
| // themselves. But currently it's an easy fix to #50552. | |
| readBodies(typecheck.Target, true, profile) | |
| // Replace any "return" statements within the function body. | |
| var edit func(ir.Node) ir.Node | |
| edit = func(n ir.Node) ir.Node { | |
| if ret, ok := n.(*ir.ReturnStmt); ok { | |
| n = typecheck.Stmt(callerfn, r.inlReturn(ret, retvars)) | |
| } | |
| ir.EditChildren(n, edit) | |
| return n | |
| } | |
| edit(r.curfn) | |
| }) | |
| body := r.curfn.Body | |
| // Reparent any declarations into the caller function. | |
| for _, name := range r.curfn.Dcl { | |
| name.Curfn = callerfn | |
| if name.Class != ir.PAUTO { | |
| name.SetPos(r.inlPos(name.Pos())) | |
| name.SetInlFormal(true) | |
| name.Class = ir.PAUTO | |
| } else { | |
| name.SetInlLocal(true) | |
| } | |
| } | |
| callerfn.Dcl = append(callerfn.Dcl, r.curfn.Dcl...) | |
| body.Append(ir.NewLabelStmt(call.Pos(), r.retlabel)) | |
| res := ir.NewInlinedCallExpr(call.Pos(), body, ir.ToNodes(retvars)) | |
| res.SetInit(init) | |
| res.SetType(call.Type()) | |
| res.SetTypecheck(1) | |
| res.Reshape = call.Reshape | |
| // Inlining shouldn't add any functions to todoBodies. | |
| assert(len(todoBodies) == 0) | |
| return res | |
| } | |
| // inlReturn returns a statement that can substitute for the given | |
| // return statement when inlining. | |
| func (r *reader) inlReturn(ret *ir.ReturnStmt, retvars []*ir.Name) *ir.BlockStmt { | |
| pos := r.inlCall.Pos() | |
| block := ir.TakeInit(ret) | |
| if results := ret.Results; len(results) != 0 { | |
| assert(len(retvars) == len(results)) | |
| as2 := ir.NewAssignListStmt(pos, ir.OAS2, ir.ToNodes(retvars), ret.Results) | |
| if r.delayResults { | |
| for _, name := range retvars { | |
| // TODO(mdempsky): Use inlined position of name.Pos() instead? | |
| block.Append(ir.NewDecl(pos, ir.ODCL, name)) | |
| name.Defn = as2 | |
| } | |
| } | |
| block.Append(as2) | |
| } | |
| block.Append(ir.NewBranchStmt(pos, ir.OGOTO, r.retlabel)) | |
| return ir.NewBlockStmt(pos, block) | |
| } | |
| // expandInline reads in an extra copy of IR to populate | |
| // fn.Inl.Dcl. | |
| func expandInline(fn *ir.Func, pri pkgReaderIndex) { | |
| // TODO(mdempsky): Remove this function. It's currently needed by | |
| // dwarfgen/dwarf.go:preInliningDcls, which requires fn.Inl.Dcl to | |
| // create abstract function DIEs. But we should be able to provide it | |
| // with the same information some other way. | |
| fndcls := len(fn.Dcl) | |
| topdcls := len(typecheck.Target.Funcs) | |
| tmpfn := ir.NewFunc(fn.Pos(), fn.Nname.Pos(), fn.Sym(), fn.Type()) | |
| tmpfn.ClosureVars = fn.ClosureVars | |
| { | |
| r := pri.asReader(pkgbits.SectionBody, pkgbits.SyncFuncBody) | |
| // Don't change parameter's Sym/Nname fields. | |
| r.funarghack = true | |
| r.funcBody(tmpfn) | |
| } | |
| // Move tmpfn's params to fn.Inl.Dcl, and reparent under fn. | |
| for _, name := range tmpfn.Dcl { | |
| name.Curfn = fn | |
| } | |
| fn.Inl.Dcl = tmpfn.Dcl | |
| fn.Inl.HaveDcl = true | |
| // Double check that we didn't change fn.Dcl by accident. | |
| assert(fndcls == len(fn.Dcl)) | |
| // typecheck.Stmts may have added function literals to | |
| // typecheck.Target.Decls. Remove them again so we don't risk trying | |
| // to compile them multiple times. | |
| typecheck.Target.Funcs = typecheck.Target.Funcs[:topdcls] | |
| } | |
| // @@@ Method wrappers | |
| // | |
| // Here we handle constructing "method wrappers," alternative entry | |
| // points that adapt methods to different calling conventions. Given a | |
| // user-declared method "func (T) M(i int) bool { ... }", there are a | |
| // few wrappers we may need to construct: | |
| // | |
| // - Implicit dereferencing. Methods declared with a value receiver T | |
| // are also included in the method set of the pointer type *T, so | |
| // we need to construct a wrapper like "func (recv *T) M(i int) | |
| // bool { return (*recv).M(i) }". | |
| // | |
| // - Promoted methods. If struct type U contains an embedded field of | |
| // type T or *T, we need to construct a wrapper like "func (recv U) | |
| // M(i int) bool { return recv.T.M(i) }". | |
| // | |
| // - Method values. If x is an expression of type T, then "x.M" is | |
| // roughly "tmp := x; func(i int) bool { return tmp.M(i) }". | |
| // | |
| // At call sites, we always prefer to call the user-declared method | |
| // directly, if known, so wrappers are only needed for indirect calls | |
| // (for example, interface method calls that can't be devirtualized). | |
| // Consequently, we can save some compile time by skipping | |
| // construction of wrappers that are never needed. | |
| // | |
| // Alternatively, because the linker doesn't care which compilation | |
| // unit constructed a particular wrapper, we can instead construct | |
| // them as needed. However, if a wrapper is needed in multiple | |
| // downstream packages, we may end up needing to compile it multiple | |
| // times, costing us more compile time and object file size. (We mark | |
| // the wrappers as DUPOK, so the linker doesn't complain about the | |
| // duplicate symbols.) | |
| // | |
| // The current heuristics we use to balance these trade offs are: | |
| // | |
| // - For a (non-parameterized) defined type T, we construct wrappers | |
| // for *T and any promoted methods on T (and *T) in the same | |
| // compilation unit as the type declaration. | |
| // | |
| // - For a parameterized defined type, we construct wrappers in the | |
| // compilation units in which the type is instantiated. We | |
| // similarly handle wrappers for anonymous types with methods and | |
| // compilation units where their type literals appear in source. | |
| // | |
| // - Method value expressions are relatively uncommon, so we | |
| // construct their wrappers in the compilation units that they | |
| // appear in. | |
| // | |
| // Finally, as an opportunistic compile-time optimization, if we know | |
| // a wrapper was constructed in any imported package's compilation | |
| // unit, then we skip constructing a duplicate one. However, currently | |
| // this is only done on a best-effort basis. | |
| // needWrapperTypes lists types for which we may need to generate | |
| // method wrappers. | |
| var needWrapperTypes []*types.Type | |
| // haveWrapperTypes lists types for which we know we already have | |
| // method wrappers, because we found the type in an imported package. | |
| var haveWrapperTypes []*types.Type | |
| // needMethodValueWrappers lists methods for which we may need to | |
| // generate method value wrappers. | |
| var needMethodValueWrappers []methodValueWrapper | |
| // haveMethodValueWrappers lists methods for which we know we already | |
| // have method value wrappers, because we found it in an imported | |
| // package. | |
| var haveMethodValueWrappers []methodValueWrapper | |
| type methodValueWrapper struct { | |
| rcvr *types.Type | |
| method *types.Field | |
| } | |
| // needWrapper records that wrapper methods may be needed at link | |
| // time. | |
| func (r *reader) needWrapper(typ *types.Type) { | |
| if typ.IsPtr() || typ.IsKind(types.TFORW) { | |
| return | |
| } | |
| // Special case: runtime must define error even if imported packages mention it (#29304). | |
| forceNeed := typ == types.ErrorType && base.Ctxt.Pkgpath == "runtime" | |
| // If a type was found in an imported package, then we can assume | |
| // that package (or one of its transitive dependencies) already | |
| // generated method wrappers for it. | |
| if r.importedDef() && !forceNeed { | |
| haveWrapperTypes = append(haveWrapperTypes, typ) | |
| } else { | |
| needWrapperTypes = append(needWrapperTypes, typ) | |
| } | |
| } | |
| // importedDef reports whether r is reading from an imported and | |
| // non-generic element. | |
| // | |
| // If a type was found in an imported package, then we can assume that | |
| // package (or one of its transitive dependencies) already generated | |
| // method wrappers for it. | |
| // | |
| // Exception: If we're instantiating an imported generic type or | |
| // function, we might be instantiating it with type arguments not | |
| // previously seen before. | |
| // | |
| // TODO(mdempsky): Distinguish when a generic function or type was | |
| // instantiated in an imported package so that we can add types to | |
| // haveWrapperTypes instead. | |
| func (r *reader) importedDef() bool { | |
| return r.p != localPkgReader && !r.hasTypeParams() | |
| } | |
| // MakeWrappers constructs all wrapper methods needed for the target | |
| // compilation unit. | |
| func MakeWrappers(target *ir.Package) { | |
| // always generate a wrapper for error.Error (#29304) | |
| needWrapperTypes = append(needWrapperTypes, types.ErrorType) | |
| seen := make(map[string]*types.Type) | |
| for _, typ := range haveWrapperTypes { | |
| wrapType(typ, target, seen, false) | |
| } | |
| haveWrapperTypes = nil | |
| for _, typ := range needWrapperTypes { | |
| wrapType(typ, target, seen, true) | |
| } | |
| needWrapperTypes = nil | |
| for _, wrapper := range haveMethodValueWrappers { | |
| wrapMethodValue(wrapper.rcvr, wrapper.method, target, false) | |
| } | |
| haveMethodValueWrappers = nil | |
| for _, wrapper := range needMethodValueWrappers { | |
| wrapMethodValue(wrapper.rcvr, wrapper.method, target, true) | |
| } | |
| needMethodValueWrappers = nil | |
| } | |
| func wrapType(typ *types.Type, target *ir.Package, seen map[string]*types.Type, needed bool) { | |
| key := typ.LinkString() | |
| if prev := seen[key]; prev != nil { | |
| if !types.Identical(typ, prev) { | |
| base.Fatalf("collision: types %v and %v have link string %q", typ, prev, key) | |
| } | |
| // Wrapper generation is shared by structurally identical types. | |
| // Share the cached TFlag too, without computing duplicate method sets. | |
| typ.CopyTFlagFrom(prev) | |
| return | |
| } | |
| seen[key] = typ | |
| if !needed { | |
| // Only called to add to 'seen'. | |
| return | |
| } | |
| if !typ.IsInterface() { | |
| typecheck.CalcMethods(typ) | |
| } | |
| if !typ.IsUntyped() { | |
| typ.TFlag() | |
| } | |
| for _, meth := range typ.AllMethods() { | |
| if meth.Sym.IsBlank() || !meth.IsMethod() { | |
| base.FatalfAt(meth.Pos, "invalid method: %v", meth) | |
| } | |
| methodWrapper(0, typ, meth, target) | |
| // For non-interface types, we also want *T wrappers. | |
| if !typ.IsInterface() { | |
| methodWrapper(1, typ, meth, target) | |
| // For not-in-heap types, *T is a scalar, not pointer shaped, | |
| // so the interface wrappers use **T. | |
| if typ.NotInHeap() { | |
| methodWrapper(2, typ, meth, target) | |
| } | |
| } | |
| } | |
| } | |
| func methodWrapper(derefs int, tbase *types.Type, method *types.Field, target *ir.Package) { | |
| wrapper := tbase | |
| for i := 0; i < derefs; i++ { | |
| wrapper = types.NewPtr(wrapper) | |
| } | |
| wrappee := method.Type.Recv().Type | |
| if types.Identical(wrapper, wrappee) || | |
| !types.IsMethodApplicable(wrapper, method) || | |
| !reflectdata.NeedEmit(tbase) { | |
| return | |
| } | |
| sym, sharedPromoted := ir.MethodSym(wrapper, method) | |
| if sym.Siggen() { | |
| if sharedPromoted { | |
| return | |
| } | |
| base.Fatalf("already generated wrapper %v", sym) | |
| } | |
| sym.SetSiggen(true) | |
| // TODO(mdempsky): Use method.Pos instead? | |
| pos := base.AutogeneratedPos | |
| fn := newWrapperFunc(pos, sym, wrapper, method) | |
| var recv ir.Node = fn.Nname.Type().Recv().Nname.(*ir.Name) | |
| // For simple *T wrappers around T methods, panicwrap produces a | |
| // nicer panic message. | |
| if wrapper.IsPtr() && types.Identical(wrapper.Elem(), wrappee) { | |
| cond := ir.NewBinaryExpr(pos, ir.OEQ, recv, types.BuiltinPkg.Lookup("nil").Def.(ir.Node)) | |
| then := []ir.Node{ir.NewCallExpr(pos, ir.OCALL, typecheck.LookupRuntime("panicwrap"), nil)} | |
| fn.Body.Append(ir.NewIfStmt(pos, cond, then, nil)) | |
| } | |
| // typecheck will add one implicit deref, if necessary, | |
| // but not-in-heap types require more for their **T wrappers. | |
| for i := 1; i < derefs; i++ { | |
| recv = Implicit(ir.NewStarExpr(pos, recv)) | |
| } | |
| addTailCall(pos, fn, recv, method) | |
| finishWrapperFunc(fn, target) | |
| } | |
| func wrapMethodValue(recvType *types.Type, method *types.Field, target *ir.Package, needed bool) { | |
| sym := ir.ReceiverMethodSymSuffix(recvType, method.Sym, "-fm") | |
| if sym.Uniq() { | |
| return | |
| } | |
| sym.SetUniq(true) | |
| // TODO(mdempsky): Use method.Pos instead? | |
| pos := base.AutogeneratedPos | |
| fn := newWrapperFunc(pos, sym, nil, method) | |
| sym.Def = fn.Nname | |
| // Declare and initialize variable holding receiver. | |
| recv := ir.NewHiddenParam(pos, fn, typecheck.Lookup(".this"), recvType) | |
| if !needed { | |
| return | |
| } | |
| addTailCall(pos, fn, recv, method) | |
| finishWrapperFunc(fn, target) | |
| } | |
| func newWrapperFunc(pos src.XPos, sym *types.Sym, wrapper *types.Type, method *types.Field) *ir.Func { | |
| sig := newWrapperType(wrapper, method) | |
| fn := ir.NewFunc(pos, pos, sym, sig) | |
| fn.DeclareParams(true) | |
| fn.SetDupok(true) // TODO(mdempsky): Leave unset for local, non-generic wrappers? | |
| return fn | |
| } | |
| func finishWrapperFunc(fn *ir.Func, target *ir.Package) { | |
| ir.WithPos(fn, func() { | |
| typecheck.Stmts(fn, fn.Body) | |
| }) | |
| // We generate wrappers after the global inlining pass, | |
| // so we're responsible for applying inlining ourselves here. | |
| // TODO(prattmic): plumb PGO. | |
| interleaved.DevirtualizeAndInlineFunc(fn, nil) | |
| // The body of wrapper function after inlining may reveal new ir.OMETHVALUE node, | |
| // we don't know whether wrapper function has been generated for it or not, so | |
| // generate one immediately here. | |
| // | |
| // Further, after CL 492017, function that construct closures is allowed to be inlined, | |
| // even though the closure itself can't be inline. So we also need to visit body of any | |
| // closure that we see when visiting body of the wrapper function. | |
| ir.VisitFuncAndClosures(fn, func(n ir.Node) { | |
| if n, ok := n.(*ir.SelectorExpr); ok && n.Op() == ir.OMETHVALUE { | |
| wrapMethodValue(n.X.Type(), n.Selection, target, true) | |
| } | |
| }) | |
| fn.Nname.Defn = fn | |
| target.Funcs = append(target.Funcs, fn) | |
| } | |
| // newWrapperType returns a copy of the given signature type, but with | |
| // the receiver parameter type substituted with recvType. | |
| // If recvType is nil, newWrapperType returns a signature | |
| // without a receiver parameter. | |
| func newWrapperType(recvType *types.Type, method *types.Field) *types.Type { | |
| clone := func(params []*types.Field) []*types.Field { | |
| res := make([]*types.Field, len(params)) | |
| for i, param := range params { | |
| res[i] = types.NewField(param.Pos, param.Sym, param.Type) | |
| res[i].SetIsDDD(param.IsDDD()) | |
| } | |
| return res | |
| } | |
| sig := method.Type | |
| var recv *types.Field | |
| if recvType != nil { | |
| recv = types.NewField(sig.Recv().Pos, sig.Recv().Sym, recvType) | |
| } | |
| params := clone(sig.Params()) | |
| results := clone(sig.Results()) | |
| return types.NewSignature(recv, params, results) | |
| } | |
| func addTailCall(pos src.XPos, fn *ir.Func, recv ir.Node, method *types.Field) { | |
| sig := fn.Nname.Type() | |
| args := make([]ir.Node, sig.NumParams()) | |
| for i, param := range sig.Params() { | |
| args[i] = param.Nname.(*ir.Name) | |
| } | |
| dot := typecheck.XDotMethod(fn, pos, recv, method.Sym, true) | |
| call := typecheck.Call(fn, pos, dot, args, method.Type.IsVariadic()).(*ir.CallExpr) | |
| if recv.Type() != nil && recv.Type().IsPtr() && method.Type.Recv().Type.IsPtr() && | |
| method.Embedded != 0 && | |
| (types.IsInterfaceMethod(method.Type) && base.Ctxt.Arch.Name != "wasm" || | |
| !types.IsInterfaceMethod(method.Type) && !unifiedHaveInlineBody(ir.MethodExprName(dot).Func)) && | |
| // TODO: implement wasm indirect tail calls | |
| // TODO: do we need the ppc64le/dynlink restriction for interface tail calls? | |
| !((base.Ctxt.Arch.Name == "ppc64le" || base.Ctxt.Arch.Name == "ppc64") && base.Ctxt.Flag_dynlink) { | |
| if base.Debug.TailCall != 0 { | |
| base.WarnfAt(fn.Nname.Type().Recv().Type.Elem().Pos(), "tail call emitted for the method %v wrapper", method.Nname) | |
| } | |
| // Prefer OTAILCALL to reduce code size (except the case when the called method can be inlined). | |
| fn.Body.Append(ir.NewTailCallStmt(pos, call)) | |
| return | |
| } | |
| fn.SetWrapper(true) | |
| if method.Type.NumResults() == 0 { | |
| fn.Body.Append(call) | |
| return | |
| } | |
| ret := ir.NewReturnStmt(pos, nil) | |
| ret.Results = []ir.Node{call} | |
| fn.Body.Append(ret) | |
| } | |
| func setBasePos(pos src.XPos) { | |
| // Set the position for any error messages we might print (e.g. too large types). | |
| base.Pos = pos | |
| } | |
| // dictParamName is the name of the synthetic dictionary parameter | |
| // added to shaped functions. | |
| // | |
| // N.B., this variable name is known to Delve: | |
| // https://github.com/go-delve/delve/blob/cb91509630529e6055be845688fd21eb89ae8714/pkg/proc/eval.go#L28 | |
| const dictParamName = typecheck.LocalDictName | |
| // shapeSig returns a copy of fn's signature, except adding a | |
| // dictionary parameter and promoting the receiver parameter (if any) | |
| // to a normal parameter. | |
| // | |
| // The parameter types.Fields are all copied too, so their Nname | |
| // fields can be initialized for use by the shape function. | |
| // | |
| // All signatures returned by shapeSig are marked as shaped. | |
| func shapeSig(fn *ir.Func, dict *readerDict) *types.Type { | |
| sig := fn.Nname.Type() | |
| oldRecv := sig.Recv() | |
| var recv *types.Field | |
| if oldRecv != nil { | |
| recv = types.NewField(oldRecv.Pos, oldRecv.Sym, oldRecv.Type) | |
| } | |
| params := make([]*types.Field, 1+sig.NumParams()) | |
| params[0] = types.NewField(fn.Pos(), fn.Sym().Pkg.Lookup(dictParamName), types.NewPtr(dict.varType())) | |
| for i, param := range sig.Params() { | |
| d := types.NewField(param.Pos, param.Sym, param.Type) | |
| d.SetIsDDD(param.IsDDD()) | |
| params[1+i] = d | |
| } | |
| results := make([]*types.Field, sig.NumResults()) | |
| for i, result := range sig.Results() { | |
| results[i] = types.NewField(result.Pos, result.Sym, result.Type) | |
| } | |
| typ := types.NewSignature(recv, params, results) | |
| typ.SetHasShape(true) | |
| return typ | |
| } |