From 38edd9bd8da9d7fc7beeba5fd4fd9d605457b04e Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Sat, 6 Aug 2022 16:40:56 -0700 Subject: [PATCH] cmd/compile/internal/noder: shape-based stenciling for unified IR This CL switches unified IR to use shape-based stenciling with runtime dictionaries, like the existing non-unified frontend. Specifically, when instantiating generic functions and types `X[T]`, we now also instantiated shaped variants `X[shapify(T)]` that can be shared by `T`'s with common underlying types. For example, for generic function `F`, `F[int](args...)` will be rewritten to `F[go.shape.int](&.dict.F[int], args...)`. For generic type `T` with method `M` and value `t` of type `T[int]`, `t.M(args...)` will be rewritten to `T[go.shape.int].M(t, &.dict.T[int], args...)`. Two notable distinctions from the non-unified frontend: 1. For simplicity, currently shaping is limited to simply converting type arguments to their underlying type. Subsequent CLs will implement more aggressive shaping. 2. For generic types, a single dictionary is generated to be shared by all methods, rather than separate dictionaries for each method. I originally went with this design because I have an idea of changing interface calls to pass the itab pointer via the closure register (which should have zero overhead), and then the interface wrappers for generic methods could use the *runtime.itab to find the runtime dictionary that corresponds to the dynamic type. This would allow emitting fewer method wrappers. However, this choice does have the consequence that currently even if a method is unused and its code is pruned by the linker, it may have produced runtime dictionary entries that need to be kept alive anyway. I'm open to changing this to generate per-method dictionaries, though this would require changing the unified IR export data format; so it would be best to make this decision before Go 1.20. The other option is making the linker smarter about pruning unneeded dictionary entries, like how it already prunes itab entries. For example, the runtime dictionary for `T[int]` could have a `R_DICTTYPE` meta-relocation against symbol `.dicttype.T[go.shape.int]` that declares it's a dictionary associated with that type; and then each method on `T[go.shape.T]` could have `R_DICTUSE` meta-relocations against `.dicttype.T[go.shape.T]+offset` indicating which fields within dictionaries of that type need to be preserved. Change-Id: I369580b1d93d19640a4b5ecada4f6231adcce3fd Reviewed-on: https://go-review.googlesource.com/c/go/+/421821 Reviewed-by: David Chase Reviewed-by: Keith Randall Run-TryBot: Matthew Dempsky Reviewed-by: Cuong Manh Le TryBot-Result: Gopher Robot --- .../internal/devirtualize/devirtualize.go | 35 + src/cmd/compile/internal/noder/reader.go | 1273 ++++++++++++++--- src/cmd/compile/internal/noder/unified.go | 54 +- src/cmd/compile/internal/noder/writer.go | 339 ++++- .../compile/internal/ssa/debug_lines_test.go | 4 +- src/cmd/compile/internal/typecheck/subr.go | 5 + src/cmd/compile/internal/walk/convert.go | 9 +- test/typeparam/nested.go | 26 +- 8 files changed, 1457 insertions(+), 288 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 60ba208d08..f64ebc87d0 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -41,6 +41,41 @@ func Call(call *ir.CallExpr) { return } + if base.Debug.Unified != 0 { + // N.B., stencil.go converts shape-typed values to interface type + // using OEFACE instead of OCONVIFACE, so devirtualization fails + // above instead. That's why this code is specific to unified IR. + + // If typ is a shape type, then it was a type argument originally + // and we'd need an indirect call through the dictionary anyway. + // We're unable to devirtualize this call. + if typ.IsShape() { + return + } + + // If typ *has* a shape type, then it's an shaped, instantiated + // type like T[go.shape.int], and its methods (may) have an extra + // dictionary parameter. We could devirtualize this call if we + // could derive an appropriate dictionary argument. + // + // TODO(mdempsky): If typ has has a promoted non-generic method, + // then that method won't require a dictionary argument. We could + // still devirtualize those calls. + // + // TODO(mdempsky): We have the *runtime.itab in recv.TypeWord. It + // should be possible to compute the represented type's runtime + // dictionary from this (e.g., by adding a pointer from T[int]'s + // *runtime._type to .dict.T[int]; or by recognizing static + // references to go:itab.T[int],iface and constructing a direct + // reference to .dict.T[int]). + if typ.HasShape() { + if base.Flag.LowerM != 0 { + base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ) + } + return + } + } + dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil) dt.SetType(typ) x := typecheck.Callee(ir.NewSelectorExpr(sel.Pos(), ir.OXDOT, dt, sel.Sel)) diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index 3ab11399a5..155244e0b5 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -5,7 +5,6 @@ package noder import ( - "bytes" "fmt" "go/constant" "internal/buildcfg" @@ -19,9 +18,11 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/reflectdata" + "cmd/compile/internal/staticinit" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/objabi" "cmd/internal/src" ) @@ -62,16 +63,22 @@ func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader { // A pkgReaderIndex compactly identifies an index (and its // corresponding dictionary) within a package's export data. type pkgReaderIndex struct { - pr *pkgReader - idx pkgbits.Index - dict *readerDict - shapedFn *ir.Func + pr *pkgReader + idx pkgbits.Index + dict *readerDict + methodSym *types.Sym + + synthetic func(pos src.XPos, r *reader) } func (pri pkgReaderIndex) asReader(k pkgbits.RelocKind, 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.shapedFn = pri.shapedFn + r.methodSym = pri.methodSym return r } @@ -101,12 +108,18 @@ type reader struct { funarghack bool - // shapedFn is the shape-typed version of curfn, if any. - shapedFn *ir.Func + // 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 @@ -137,7 +150,27 @@ type reader struct { retvars ir.Nodes } +// 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: // @@ -164,6 +197,17 @@ type readerDict struct { 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) { @@ -329,6 +373,19 @@ func (r *reader) typInfo() typeInfo { return typeInfo{idx: r.Reloc(pkgbits.RelocType), 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 @@ -545,26 +602,41 @@ 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) assert(!r.Bool()) // TODO(mdempsky): Remove; was derived func inst. idx := r.Reloc(pkgbits.RelocObj) - explicits := make([]*types.Type, r.Len()) + explicits := make([]typeInfo, r.Len()) for i := range explicits { - explicits[i] = r.typ() + explicits[i] = r.typInfo() } - var implicits []*types.Type - if r.dict != nil { - implicits = r.dict.targs - } - - return r.p.objIdx(idx, implicits, explicits) + return objInfo{idx, explicits} } -// objIdx returns the specified object from the bitstream, -// instantiated with the given type arguments, if any. -func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Type) ir.Node { +// 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 pkgbits.Index, implicits, explicits []*types.Type, shaped bool) ir.Node { rname := pr.newReader(pkgbits.RelocName, idx, pkgbits.SyncObject1) _, sym := rname.qualifiedIdent() tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj)) @@ -576,12 +648,17 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ return sym.Def.(ir.Node) } if pri, ok := objReader[sym]; ok { - return pri.pr.objIdx(pri.idx, nil, explicits) + return pri.pr.objIdx(pri.idx, nil, explicits, shaped) } base.Fatalf("unresolved stub: %v", sym) } - dict := pr.objDictIdx(sym, idx, implicits, explicits) + dict := pr.objDictIdx(sym, idx, implicits, explicits, shaped) + + sym = dict.baseSym + if !sym.IsBlank() && sym.Def != nil { + return sym.Def.(*ir.Name) + } r := pr.newReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1) rext := pr.newReader(pkgbits.RelocObjExt, idx, pkgbits.SyncObject1) @@ -589,11 +666,6 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ r.dict = dict rext.dict = dict - sym = r.mangle(sym) - if !sym.IsBlank() && sym.Def != nil { - return sym.Def.(*ir.Name) - } - do := func(op ir.Op, hasTParams bool) *ir.Name { pos := r.pos() setBasePos(pos) @@ -643,15 +715,25 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ 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) + rext.funcExt(name, nil) return name 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) @@ -662,6 +744,12 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ 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) @@ -670,7 +758,9 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ typ.Methods().Set(methods) } - r.needWrapper(typ) + if !r.dict.shaped { + r.needWrapper(typ) + } return name @@ -682,17 +772,17 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ } } -func (r *reader) mangle(sym *types.Sym) *types.Sym { - if !r.hasTypeParams() { +func (dict *readerDict) mangle(sym *types.Sym) *types.Sym { + if !dict.hasTypeParams() { return sym } - var buf bytes.Buffer + var buf strings.Builder buf.WriteString(sym.Name) buf.WriteByte('[') - for i, targ := range r.dict.targs { + for i, targ := range dict.targs { if i > 0 { - if i == r.dict.implicits { + if i == dict.implicits { buf.WriteByte(';') } else { buf.WriteByte(',') @@ -704,11 +794,40 @@ func (r *reader) mangle(sym *types.Sym) *types.Sym { return sym.Pkg.Lookup(buf.String()) } +// shapify returns the shape type for targ. +func shapify(targ *types.Type) *types.Type { + if targ.IsShape() { + return targ + } + + base.Assertf(targ.Kind() != types.TFORW, "%v is missing its underlying type", targ) + + // TODO(go.dev/issue/54513): Better shaping than merely converting + // to underlying type. E.g., shape pointer types to unsafe.Pointer + // when we know the element type doesn't matter, and then enable + // cmd/compile/internal/test.TestInst. + under := targ.Underlying() + + sym := types.ShapePkg.Lookup(under.LinkString()) + 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 pkgbits.Index, implicits, explicits []*types.Type) *readerDict { +func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) *readerDict { r := pr.newReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1) - var dict readerDict + dict := readerDict{ + shaped: shaped, + } nimplicits := r.Len() nexplicits := r.Len() @@ -720,6 +839,27 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex dict.targs = append(implicits[:nimplicits:nimplicits], explicits...) dict.implicits = nimplicits + // 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. + if dict.shaped { + for i, targ := range dict.targs { + dict.targs[i] = shapify(targ) + } + } + + dict.baseSym = dict.mangle(sym) + // For stenciling, we can just skip over the type parameters. for range dict.targs[dict.implicits:] { // Skip past bounds without actually evaluating them. @@ -732,6 +872,29 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()} } + 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 } @@ -760,9 +923,13 @@ func (r *reader) method(rext *reader) *types.Field { if r.hasTypeParams() { name.Func.SetDupok(true) + if r.dict.shaped { + typ = shapeSig(name.Func, r.dict) + setType(name, typ) + } } - rext.funcExt(name) + rext.funcExt(name, sym) meth := types.NewField(name.Func.Pos(), sym, typ) meth.Nname = name @@ -811,7 +978,7 @@ func (dict *readerDict) hasTypeParams() bool { // @@@ Compiler extensions -func (r *reader) funcExt(name *ir.Name) { +func (r *reader) funcExt(name *ir.Name, method *types.Sym) { r.Sync(pkgbits.SyncFuncExt) name.Class = 0 // so MarkFunc doesn't complain @@ -859,7 +1026,7 @@ func (r *reader) funcExt(name *ir.Name) { } } } else { - r.addBody(name.Func) + r.addBody(name.Func, method) } r.Sync(pkgbits.SyncEOF) } @@ -932,49 +1099,24 @@ func bodyReaderFor(fn *ir.Func) (pri pkgReaderIndex, ok bool) { 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) { +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.RelocBody) - var shapedFn *ir.Func - if r.hasTypeParams() && fn.OClosure == nil { - name := fn.Nname - sym := name.Sym() - - shapedSym := sym.Pkg.Lookup(sym.Name + "-shaped") - - // TODO(mdempsky): Once we actually start shaping functions, we'll - // need to deduplicate them. - shaped := ir.NewDeclNameAt(name.Pos(), ir.ONAME, shapedSym) - setType(shaped, shapeSig(fn, r.dict)) // TODO(mdempsky): Use shape types. - - shapedFn = ir.NewFunc(fn.Pos()) - shaped.Func = shapedFn - shapedFn.Nname = shaped - shapedFn.SetDupok(true) - - shaped.Class = 0 // so MarkFunc doesn't complain - ir.MarkFunc(shaped) - - shaped.Defn = shapedFn - - shapedFn.Pragma = fn.Pragma // TODO(mdempsky): How does stencil.go handle pragmas? - typecheck.Func(shapedFn) - - bodyReader[shapedFn] = pkgReaderIndex{r.p, idx, r.dict, nil} - todoBodies = append(todoBodies, shapedFn) - } - - pri := pkgReaderIndex{r.p, idx, r.dict, shapedFn} + pri := pkgReaderIndex{r.p, idx, r.dict, method, nil} bodyReader[fn] = pri if r.curfn == nil { @@ -1002,12 +1144,11 @@ func (r *reader) funcBody(fn *ir.Func) { ir.WithFunc(fn, func() { r.funcargs(fn) - if !r.Bool() { + if r.syntheticBody(fn.Pos()) { return } - if r.shapedFn != nil { - r.callShaped(fn.Pos()) + if !r.Bool() { return } @@ -1022,21 +1163,69 @@ func (r *reader) funcBody(fn *ir.Func) { 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 + // type, so we need to select it's corresponding method. + shapedFn = shapedMethodExpr(pos, shapedObj, r.methodSym) + } + + recvs, params := r.syntheticArgs(pos) + + // 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 + args.Append(recvs...) + args.Append(typecheck.Expr(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(pos src.XPos) (recvs, params ir.Nodes) { sig := r.curfn.Nname.Type() - var args ir.Nodes - - // First argument is a pointer to the -dict global variable. - args.Append(r.dictPtr()) - - // Collect the arguments to the current function, so we can pass - // them along to the shaped function. (This is unfortunately quite - // hairy.) - for _, params := range &types.RecvsParams { - for _, param := range params(sig).FieldSlice() { + inlVarIdx := 0 + addParams := func(out *ir.Nodes, params []*types.Field) { + for _, param := range params { var arg ir.Node if param.Nname != nil { name := param.Nname.(*ir.Name) @@ -1044,7 +1233,7 @@ func (r *reader) callShaped(pos src.XPos) { if r.inlCall != nil { // During inlining, we want the respective inlvar where we // assigned the callee's arguments. - arg = r.inlvars[len(args)-1] + arg = r.inlvars[inlVarIdx] } else { // Otherwise, we can use the parameter itself directly. base.AssertfAt(name.Curfn == r.curfn, name.Pos(), "%v has curfn %v, but want %v", name, name.Curfn, r.curfn) @@ -1068,18 +1257,27 @@ func (r *reader) callShaped(pos src.XPos) { arg = tmp } - args.Append(arg) + out.Append(arg) + inlVarIdx++ } } + addParams(&recvs, sig.Recvs().FieldSlice()) + addParams(¶ms, sig.Params().FieldSlice()) + return +} + +// 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(pos, r.shapedFn.Nname, args, sig.IsVariadic()).(*ir.CallExpr) + call := typecheck.Call(pos, fn, args, fn.Type().IsVariadic()).(*ir.CallExpr) var stmt ir.Node - if sig.NumResults() != 0 { + if fn.Type().NumResults() != 0 { stmt = typecheck.Stmt(ir.NewReturnStmt(pos, []ir.Node{call})) } else { stmt = call @@ -1087,67 +1285,128 @@ func (r *reader) callShaped(pos src.XPos) { r.curfn.Body.Append(stmt) } -// dictPtr returns a pointer to the runtime dictionary variable needed -// for the current function to call its shaped variant. -func (r *reader) dictPtr() ir.Node { - var fn *ir.Func - if r.inlCall != nil { - // During inlining, r.curfn is named after the caller (not the - // callee), because it's relevant to closure naming, sigh. - fn = r.inlFunc - } else { - fn = r.curfn +// 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) } - var baseSym *types.Sym - if recv := fn.Nname.Type().Recv(); recv != nil { - // All methods of a given instantiated receiver type share the - // same dictionary. - baseSym = deref(recv.Type).Sym() - } else { - baseSym = fn.Nname.Sym() + name := ir.NewNameAt(pos, sym) + 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) } - sym := baseSym.Pkg.Lookup(baseSym.Name + "-dict") + assertOffset("type param method exprs", dict.typeParamMethodExprsOffset()) + for _, info := range dict.typeParamMethodExprs { + typeParam := dict.targs[info.typeParamIdx] + method := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(typeParam), info.method)).(*ir.SelectorExpr) + assert(method.Op() == ir.OMETHEXPR) - if sym.Def == nil { - dict := ir.NewNameAt(r.curfn.Pos(), sym) - dict.Class = ir.PEXTERN + rsym := method.FuncName().Linksym() + assert(rsym.ABI() == obj.ABIInternal) // must be ABIInternal; see ir.OCFUNC in ssagen/ssa.go - lsym := dict.Linksym() - ot := 0 + ot = objw.SymPtr(lsym, ot, rsym, 0) + } - for idx, info := range r.dict.derived { - if info.needed { - typ := r.p.typIdx(typeInfo{idx: pkgbits.Index(idx), derived: true}, r.dict, false) - rtype := reflectdata.TypeLinksym(typ) - ot = objw.SymPtr(lsym, ot, rtype, 0) - } else { - // TODO(mdempsky): Compact unused runtime dictionary space. - ot = objw.Uintptr(lsym, ot, 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 *runtime._type pointers + // for typ and iface, as well as the *runtime.itab pointer for the + // pair. This is wasteful, but it simplifies worrying about tricky + // cases like instantiating type parameters with interface types. + // + // TODO(mdempsky): Add the needed *runtime._type pointers into the + // rtypes section above instead, and omit itabs entries when we + // statically know it won't be needed. + assertOffset("itabs", dict.itabsOffset()) + for _, info := range dict.itabs { + typ := pr.typIdx(info.typ, dict, true) + iface := pr.typIdx(info.iface, dict, true) + + if !iface.IsInterface() { + ot += 3 * types.PtrSize + continue } - // TODO(mdempsky): Write out more dictionary information. + ot = objw.SymPtr(lsym, ot, reflectdata.TypeLinksym(typ), 0) + ot = objw.SymPtr(lsym, ot, reflectdata.TypeLinksym(iface), 0) + if !typ.IsInterface() && !iface.IsEmptyInterface() { + ot = objw.SymPtr(lsym, ot, reflectdata.ITabLsym(typ, iface), 0) + } else { + ot += types.PtrSize + } - objw.Global(lsym, int32(ot), obj.DUPOK|obj.RODATA) - - dict.SetType(r.dict.varType()) - dict.SetTypecheck(1) - - sym.Def = dict + // TODO(mdempsky): Double check this. + reflectdata.MarkTypeUsedInInterface(typ, lsym) + reflectdata.MarkTypeUsedInInterface(iface, lsym) } - return typecheck.Expr(ir.NewAddrExpr(r.curfn.Pos(), sym.Def.(*ir.Name))) + objw.Global(lsym, int32(ot), obj.DUPOK|obj.RODATA) + + name.SetType(dict.varType()) + name.SetTypecheck(1) + + return name } -// numWords returns the number of words that dict's runtime dictionary -// variable requires. +// 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 { - var num int - num += len(dict.derivedTypes) - // TODO(mdempsky): Add space for more dictionary information. - return int64(num) + return int64(dict.itabsOffset() + 3*len(dict.itabs)) } // varType returns the type of dict's runtime dictionary variable. @@ -1215,14 +1474,20 @@ func (r *reader) addLocal(name *ir.Name, ctxt ir.Class) { if name.Sym().Name == dictParamName { r.dictParam = name } else { - 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") + if ctxt == ir.PAUTO { + 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.locals = append(r.locals, name) + + // TODO(go.dev/issue/54514): Set name.DictIndex for variables of + // derived type and enable cmd/link/internal/ld.TestDictIndex. } name.SetUsed(true) @@ -1797,7 +2062,12 @@ func (r *reader) expr() (res ir.Node) { return typecheck.Callee(r.obj()) case exprFuncInst: - return r.obj() + pos := r.pos() + wrapperFn, baseFn, dictPtr := r.funcInst(pos) + if wrapperFn != nil { + return wrapperFn + } + return r.curry(pos, false, baseFn, dictPtr, nil) case exprConst: pos := r.pos() @@ -1826,28 +2096,96 @@ func (r *reader) expr() (res ir.Node) { return typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, x, sym)).(*ir.SelectorExpr) case exprMethodVal: - x := r.expr() + recv := r.expr() pos := r.pos() - _, sym := r.selector() + wrapperFn, baseFn, dictPtr := r.methodExpr() - n := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, x, sym)).(*ir.SelectorExpr) - wrapper := methodValueWrapper{ - rcvr: n.X.Type(), - method: n.Selection, + // 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().Params().Field(0).Type + if !types.Identical(typ, recv.Type()) { + base.FatalfAt(wrapperFn.Pos(), "receiver %L does not match %L", recv, wrapperFn) + } + recv = typecheck.Expr(ir.NewConvExpr(recv.Pos(), ir.OCONVNOP, typ, recv)) + } + + n := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, recv, wrapperFn.Sel)).(*ir.SelectorExpr) + assert(n.Selection == wrapperFn.Selection) + + wrapper := methodValueWrapper{ + rcvr: n.X.Type(), + method: n.Selection, + } + + if r.importedDef() { + haveMethodValueWrappers = append(haveMethodValueWrappers, wrapper) + } else { + needMethodValueWrappers = append(needMethodValueWrappers, wrapper) + } + return n } - 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(pos, true, baseFn, recv, dictPtr) case exprMethodExpr: - typ := r.typ() - pos := r.pos() - _, sym := r.selector() + recv := r.typ() - return typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(typ), sym)).(*ir.SelectorExpr) + 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 + } + + pos := r.pos() + 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().Params().Field(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.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(recv), method.Sel)).(*ir.SelectorExpr) + } + + return r.methodExprWrap(pos, recv, implicits, deref, addr, baseFn, dictPtr) case exprIndex: x := r.expr() @@ -1928,14 +2266,49 @@ func (r *reader) expr() (res ir.Node) { return x case exprCall: - fun := r.expr() + 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.Callee(ir.NewSelectorExpr(method.Pos(), ir.OXDOT, recv, method.Sel)) + } 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() - _, sym := r.selector() - fun = typecheck.Callee(ir.NewSelectorExpr(pos, ir.OXDOT, fun, sym)) + _, shapedFn, dictPtr := r.funcInst(pos) + fun = shapedFn + args.Append(dictPtr) + } else { + fun = r.expr() } pos := r.pos() - args := r.multiExpr() + args.Append(r.multiExpr()...) dots := r.Bool() n := typecheck.Call(pos, fun, args, dots) switch n.Op() { @@ -2002,6 +2375,26 @@ func (r *reader) expr() (res ir.Node) { } n := typecheck.Expr(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 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) + } + // 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(n) { @@ -2013,6 +2406,269 @@ func (r *reader) expr() (res ir.Node) { } } +// 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().Params().Field(0).Type + dictPtr = typecheck.Expr(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(ir.NewAddrExpr(pos, dictName)) + + return +} + +func (pr *pkgReader) objDictName(idx pkgbits.Index, implicits, explicits []*types.Type) *ir.Name { + rname := pr.newReader(pkgbits.RelocName, 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 := pr.objDictIdx(sym, idx, implicits, explicits, false) + + 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(pos 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(types.NoPkg, nil, nil, params, results) + + addBody := func(pos src.XPos, r *reader, captured []ir.Node) { + recvs, params := r.syntheticArgs(pos) + assert(len(recvs) == 0) + + fun := captured[0] + + var args ir.Nodes + args.Append(captured[1:]...) + args.Append(params...) + + r.syntheticTailCall(pos, fun, args) + } + + return r.syntheticClosure(pos, 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(pos 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(types.NoPkg, nil, nil, params, results) + + addBody := func(pos src.XPos, r *reader, captured []ir.Node) { + recvs, args := r.syntheticArgs(pos) + assert(len(recvs) == 0) + + fn := captured[0] + + // Rewrite first argument based on implicits/deref/addr. + { + arg := args[0] + for _, ix := range implicits { + arg = Implicit(DotField(pos, arg, ix)) + } + if deref { + arg = Implicit(Deref(pos, arg.Type().Elem(), arg)) + } else if addr { + arg = Implicit(Addr(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(pos, typ, false, captured, addBody) +} + +// syntheticClosure constructs a synthetic function literal for +// currying dictionary arguments. pos is the position used for the +// closure. 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(pos 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 := ir.NewClosureFunc(pos, r.curfn != nil) + fn.SetWrapper(true) + clo := fn.OClosure + ir.NameClosure(clo, r.curfn) + + setType(fn.Nname, typ) + typecheck.Func(fn) + setType(clo, fn.Type()) + + var init ir.Nodes + for i, n := range captures { + if isSafe(n) { + continue // skip capture; can reference directly + } + + tmp := r.tempCopy(pos, n, &init) + ir.NewClosureVar(pos, 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(pos, ir.OCHECKNIL, ir.NewUnaryExpr(pos, ir.OITAB, tmp)) + init.Append(typecheck.Stmt(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(pos, r, captured) + }} + bodyReader[fn] = pri + pri.funcBody(fn) + + // TODO(mdempsky): Remove hard-coding of typecheck.Target. + return ir.InitExpr(init, ir.UseClosure(clo, typecheck.Target)) +} + +// 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 { + sym := param.Sym + if sym == nil || sym.Name == "_" { + sym = typecheck.LookupNum(".anon", i) + } + res[i] = types.NewField(param.Pos, sym, param.Type) + res[i].SetIsDDD(param.IsDDD()) + } + return res + } + + return clone(sig.Params().FieldSlice()), clone(sig.Results().FieldSlice()) +} + func (r *reader) optExpr() ir.Node { if r.Bool() { return r.expr() @@ -2020,6 +2676,134 @@ func (r *reader) optExpr() ir.Node { 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() + sig0 := r.signature(types.LocalPkg, nil) + pos := r.pos() + _, sym := r.selector() + + // Signature type to return (i.e., recv prepended to the method's + // normal parameters list). + sig := typecheck.NewMethodType(sig0, recv) + + 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(ir.NewConvExpr(pos, ir.OCONVNOP, sig, ir.NewAddrExpr(pos, word))) + return fn, fn, nil + } + + // TODO(mdempsky): I'm pretty sure this isn't needed: implicits is + // only relevant to locally defined types, but they can't have + // (non-promoted) methods. + 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) + + shapedObj := r.p.objIdx(info.idx, implicits, 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().Params().Field(1).Type + dictPtr := typecheck.Expr(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, implicits, explicits, true).(*ir.Name) + shapedFn := shapedMethodExpr(pos, shapedObj, sym) + + dict := r.p.objDictName(info.idx, implicits, explicits) + dictPtr := typecheck.Expr(ir.NewAddrExpr(pos, dict)) + + // Check that dictPtr matches shapedFn's dictionary parameter. + if !types.Identical(dictPtr.Type(), shapedFn.Type().Params().Field(1).Type) { + base.FatalfAt(pos, "dict %L, but shaped method %L", dict, shapedFn) + } + + // For statically known instantiations, we can take advantage of + // the stenciled wrapper. + base.AssertfAt(!recv.HasShape(), pos, "shaped receiver %v", recv) + wrapperFn := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(recv), sym)).(*ir.SelectorExpr) + base.AssertfAt(types.Identical(sig, wrapperFn.Type()), pos, "wrapper %L does not have type %v", wrapperFn, sig) + + return wrapperFn, shapedFn, dictPtr + } + + // Simple method expression; no dictionary needed. + base.AssertfAt(!recv.HasShape() || recv.IsInterface(), pos, "shaped receiver %v", recv) + fn := typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(recv), sym)).(*ir.SelectorExpr) + return fn, fn, nil +} + +// shapedMethodExpr returns the specified method on the given shaped +// type. +func shapedMethodExpr(pos src.XPos, obj *ir.Name, sym *types.Sym) *ir.SelectorExpr { + assert(obj.Op() == ir.OTYPE) + + typ := obj.Type() + assert(typ.HasShape()) + + method := func() *types.Field { + for _, method := range typ.Methods().Slice() { + if method.Sym == sym { + return method + } + } + + base.FatalfAt(pos, "failed to find method %v in shaped type %v", sym, typ) + panic("unreachable") + }() + + // Construct an OMETHEXPR node. + recv := method.Type.Recv().Type + return typecheck.Expr(ir.NewSelectorExpr(pos, ir.OXDOT, ir.TypeNode(recv), sym)).(*ir.SelectorExpr) +} + func (r *reader) multiExpr() []ir.Node { r.Sync(pkgbits.SyncMultiExpr) @@ -2072,6 +2856,49 @@ func (r *reader) temp(pos src.XPos, typ *types.Type) *ir.Name { return typecheck.TempAt(pos, 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 { + if r.curfn == nil { + // Escape analysis doesn't know how to handle package-scope + // function literals with free variables (i.e., that capture + // temporary variables added to typecheck.InitTodoFunc). + // + // stencil.go works around this limitation by spilling values to + // global variables instead, but that causes the value to stay + // alive indefinitely; see go.dev/issue/54343. + // + // This code path (which implements the same workaround) isn't + // actually needed by unified IR, because it creates uses normal + // OMETHEXPR/OMETHVALUE nodes when statically-known instantiated + // types are used. But it's kept around for now because it's handy + // for testing that the generic fallback paths work correctly. + base.Fatalf("tempCopy called at package scope") + + tmp := staticinit.StaticName(expr.Type()) + + assign := ir.NewAssignStmt(pos, tmp, expr) + assign.Def = true + tmp.Defn = assign + + typecheck.Target.Decls = append(typecheck.Target.Decls, typecheck.Stmt(assign)) + + return tmp + } + + tmp := r.temp(pos, expr.Type()) + + init.Append(typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp))) + + assign := ir.NewAssignStmt(pos, tmp, expr) + assign.Def = true + init.Append(typecheck.Stmt(ir.NewAssignStmt(pos, tmp, expr))) + + tmp.Defn = assign + + return tmp +} + func (r *reader) compLit() ir.Node { r.Sync(pkgbits.SyncCompLit) pos := r.pos() @@ -2160,7 +2987,7 @@ func (r *reader) funcLit() ir.Node { ir.NewClosureVar(param.Pos(), fn, param) } - r.addBody(fn) + r.addBody(fn, nil) // TODO(mdempsky): Remove hard-coding of typecheck.Target. return ir.UseClosure(clo, typecheck.Target) @@ -2185,68 +3012,89 @@ func (r *reader) exprs() []ir.Node { // dictWord returns an expression to return the specified // uintptr-typed word from the dictionary parameter. -func (r *reader) dictWord(pos src.XPos, idx int64) ir.Node { +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(ir.NewIndexExpr(pos, r.dictParam, ir.NewBasicLit(pos, constant.MakeInt64(idx)))) + return typecheck.Expr(ir.NewIndexExpr(pos, r.dictParam, ir.NewBasicLit(pos, constant.MakeInt64(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(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) - return r.rtypeInfo(pos, r.typInfo()) -} - -// rtypeInfo returns an expression of type *runtime._type representing -// the given decoded type reference. -func (r *reader) rtypeInfo(pos src.XPos, info typeInfo) ir.Node { - if !info.derived { - typ := r.p.typIdx(info, r.dict, true) - return reflectdata.TypePtrAt(pos, typ) + 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 } - assert(r.dict.derived[info.idx].needed) - return typecheck.Expr(ir.NewConvExpr(pos, ir.OCONVNOP, types.NewPtr(types.Types[types.TUINT8]), r.dictWord(pos, int64(info.idx)))) + + typ = r.typ() + rtype = reflectdata.TypePtrAt(pos, typ) + return } -// itabInfo returns an expression of type *runtime.itab representing -// the itab for the given decoded type and interface reference pair. -func (r *reader) itabInfo(pos src.XPos, typInfo, ifaceInfo typeInfo) ir.Node { - typ := r.p.typIdx(typInfo, r.dict, true) - iface := r.p.typIdx(ifaceInfo, r.dict, true) - lsym := reflectdata.ITabLsym(typ, iface) - return typecheck.LinksymAddr(pos, lsym, types.Types[types.TUINT8]) +func (r *reader) itab(pos src.XPos) (typ *types.Type, typRType ir.Node, iface *types.Type, ifaceRType ir.Node, itab ir.Node) { + if r.Bool() { // derived types + idx := r.Len() + info := r.dict.itabs[idx] + typ = r.p.typIdx(info.typ, r.dict, true) + typRType = r.rttiWord(pos, r.dict.itabsOffset()+3*idx) + iface = r.p.typIdx(info.iface, r.dict, true) + ifaceRType = r.rttiWord(pos, r.dict.itabsOffset()+3*idx+1) + itab = r.rttiWord(pos, r.dict.itabsOffset()+3*idx+2) + return + } + + typ = r.typ() + iface = r.typ() + if iface.IsInterface() { + typRType = reflectdata.TypePtrAt(pos, typ) + ifaceRType = reflectdata.TypePtrAt(pos, iface) + if !typ.IsInterface() && !iface.IsEmptyInterface() { + lsym := reflectdata.ITabLsym(typ, iface) + itab = typecheck.LinksymAddr(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) - srcInfo := r.typInfo() - dstInfo := r.typInfo() - - dst := r.p.typIdx(dstInfo, r.dict, true) + src, srcRType0, dst, dstRType, itab := r.itab(pos) if !dst.IsInterface() { return } - src := r.p.typIdx(srcInfo, r.dict, true) - // See reflectdata.ConvIfaceTypeWord. switch { case dst.IsEmptyInterface(): if !src.IsInterface() { - typeWord = r.rtypeInfo(pos, srcInfo) // direct eface construction + typeWord = srcRType0 // direct eface construction } case !src.IsInterface(): - typeWord = r.itabInfo(pos, srcInfo, dstInfo) // direct iface construction + typeWord = itab // direct iface construction default: - typeWord = r.rtypeInfo(pos, dstInfo) // convI2I + typeWord = dstRType // convI2I } // See reflectdata.ConvIfaceSrcRType. if !src.IsInterface() { - srcRType = r.rtypeInfo(pos, srcInfo) + srcRType = srcRType0 } return @@ -2254,32 +3102,27 @@ func (r *reader) convRTTI(pos src.XPos) (typeWord, srcRType ir.Node) { func (r *reader) exprType() ir.Node { r.Sync(pkgbits.SyncExprType) - pos := r.pos() - setBasePos(pos) + var typ *types.Type var rtype, itab ir.Node - typInfo := r.typInfo() - typ := r.p.typIdx(typInfo, r.dict, true) - if r.Bool() { - ifaceInfo := r.typInfo() - + typ, rtype, _, _, itab = r.itab(pos) if typ.IsInterface() { - rtype = r.rtypeInfo(pos, typInfo) + itab = nil } else { - itab = r.itabInfo(pos, typInfo, ifaceInfo) + rtype = nil // TODO(mdempsky): Leave set? } } else { - if !typInfo.derived { + typ, rtype = r.rtype0(pos) + + if !r.Bool() { // not derived // TODO(mdempsky): ir.TypeNode should probably return a typecheck'd node. n := ir.TypeNode(typ) n.SetTypecheck(1) return n } - - rtype = r.rtypeInfo(pos, typInfo) } dt := ir.NewDynamicType(pos, rtype) @@ -2464,7 +3307,6 @@ func InlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExp r.funcargs(fn) - assert(r.Bool()) // have body r.delayResults = fn.Inl.CanDelayResults r.retlabel = typecheck.AutoLabel(".i") @@ -2523,9 +3365,9 @@ func InlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExp nparams := len(r.curfn.Dcl) ir.WithFunc(r.curfn, func() { - if r.shapedFn != nil { - r.callShaped(call.Pos()) - } else { + if !r.syntheticBody(call.Pos()) { + assert(r.Bool()) // have body + r.curfn.Body = r.stmts() r.curfn.Endlineno = r.pos() } @@ -2993,6 +3835,9 @@ func setBasePos(pos src.XPos) { // 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 = ".dict" // shapeSig returns a copy of fn's signature, except adding a @@ -3003,21 +3848,19 @@ const dictParamName = ".dict" // fields can be initialized for use by the shape function. func shapeSig(fn *ir.Func, dict *readerDict) *types.Type { sig := fn.Nname.Type() - recv := sig.Recv() - nrecvs := 0 - if recv != nil { - nrecvs++ + oldRecv := sig.Recv() + + var recv *types.Field + if oldRecv != nil { + recv = types.NewField(oldRecv.Pos, oldRecv.Sym, oldRecv.Type) } - params := make([]*types.Field, 1+nrecvs+sig.Params().Fields().Len()) + params := make([]*types.Field, 1+sig.Params().Fields().Len()) params[0] = types.NewField(fn.Pos(), fn.Sym().Pkg.Lookup(dictParamName), types.NewPtr(dict.varType())) - if recv != nil { - params[1] = types.NewField(recv.Pos, recv.Sym, recv.Type) - } for i, param := range sig.Params().Fields().Slice() { d := types.NewField(param.Pos, param.Sym, param.Type) d.SetIsDDD(param.IsDDD()) - params[1+nrecvs+i] = d + params[1+i] = d } results := make([]*types.Field, sig.Results().Fields().Len()) @@ -3025,5 +3868,5 @@ func shapeSig(fn *ir.Func, dict *readerDict) *types.Type { results[i] = types.NewField(result.Pos, result.Sym, result.Type) } - return types.NewSignature(types.LocalPkg, nil, nil, params, results) + return types.NewSignature(types.LocalPkg, recv, nil, params, results) } diff --git a/src/cmd/compile/internal/noder/unified.go b/src/cmd/compile/internal/noder/unified.go index 922189f4d3..f5a4fbc26b 100644 --- a/src/cmd/compile/internal/noder/unified.go +++ b/src/cmd/compile/internal/noder/unified.go @@ -120,25 +120,47 @@ func unified(noders []*noder) { base.ExitIfErrors() // just in case } -// readBodies reads in bodies for any +// readBodies iteratively expands all pending dictionaries and +// function bodies. func readBodies(target *ir.Package) { // Don't use range--bodyIdx can add closures to todoBodies. - for len(todoBodies) > 0 { - // The order we expand bodies doesn't matter, so pop from the end - // to reduce todoBodies reallocations if it grows further. - fn := todoBodies[len(todoBodies)-1] - todoBodies = todoBodies[:len(todoBodies)-1] + for { + // The order we expand dictionaries and bodies doesn't matter, so + // pop from the end to reduce todoBodies reallocations if it grows + // further. + // + // However, we do at least need to flush any pending dictionaries + // before reading bodies, because bodies might reference the + // dictionaries. - pri, ok := bodyReader[fn] - assert(ok) - pri.funcBody(fn) - - // Instantiated generic function: add to Decls for typechecking - // and compilation. - if fn.OClosure == nil && len(pri.dict.targs) != 0 { - target.Decls = append(target.Decls, fn) + if len(todoDicts) > 0 { + fn := todoDicts[len(todoDicts)-1] + todoDicts = todoDicts[:len(todoDicts)-1] + fn() + continue } + + if len(todoBodies) > 0 { + fn := todoBodies[len(todoBodies)-1] + todoBodies = todoBodies[:len(todoBodies)-1] + + pri, ok := bodyReader[fn] + assert(ok) + pri.funcBody(fn) + + // Instantiated generic function: add to Decls for typechecking + // and compilation. + if fn.OClosure == nil && len(pri.dict.targs) != 0 { + target.Decls = append(target.Decls, fn) + } + + continue + } + + break } + + todoDicts = nil todoBodies = nil } @@ -247,7 +269,7 @@ func readPackage(pr *pkgReader, importpkg *types.Pkg, localStub bool) { path, name, code := r.p.PeekObj(idx) if code != pkgbits.ObjStub { - objReader[types.NewPkg(path, "").Lookup(name)] = pkgReaderIndex{pr, idx, nil, nil} + objReader[types.NewPkg(path, "").Lookup(name)] = pkgReaderIndex{pr, idx, nil, nil, nil} } } @@ -271,7 +293,7 @@ func readPackage(pr *pkgReader, importpkg *types.Pkg, localStub bool) { sym := types.NewPkg(path, "").Lookup(name) if _, ok := importBodyReader[sym]; !ok { - importBodyReader[sym] = pkgReaderIndex{pr, idx, nil, nil} + importBodyReader[sym] = pkgReaderIndex{pr, idx, nil, nil, nil} } } diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 52fa542f6b..a90b2d3bbd 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -172,12 +172,37 @@ type writerDict struct { // derivedIdx maps a Type to its corresponding index within the // derived slice, if present. derivedIdx map[types2.Type]pkgbits.Index + + // These slices correspond to entries in the runtime dictionary. + typeParamMethodExprs []writerMethodExprInfo + subdicts []objInfo + rtypes []typeInfo + itabs []itabInfo +} + +type itabInfo struct { + typ typeInfo + iface typeInfo +} + +// typeParamIndex returns the index of the given type parameter within +// the dictionary. This may differ from typ.Index() when there are +// implicit type parameters due to defined types declared within a +// generic function or method. +func (dict *writerDict) typeParamIndex(typ *types2.TypeParam) int { + for idx, implicit := range dict.implicits { + if implicit.Type().(*types2.TypeParam) == typ { + return idx + } + } + + return len(dict.implicits) + typ.Index() } // A derivedInfo represents a reference to an encoded generic Go type. type derivedInfo struct { idx pkgbits.Index - needed bool + needed bool // TODO(mdempsky): Remove. } // A typeInfo represents a reference to an encoded Go type. @@ -234,6 +259,75 @@ func (info objInfo) equals(other objInfo) bool { return true } +type writerMethodExprInfo struct { + typeParamIdx int + methodInfo selectorInfo +} + +// typeParamMethodExprIdx returns the index where the given encoded +// method expression function pointer appears within this dictionary's +// type parameters method expressions section, adding it if necessary. +func (dict *writerDict) typeParamMethodExprIdx(typeParamIdx int, methodInfo selectorInfo) int { + newInfo := writerMethodExprInfo{typeParamIdx, methodInfo} + + for idx, oldInfo := range dict.typeParamMethodExprs { + if oldInfo == newInfo { + return idx + } + } + + idx := len(dict.typeParamMethodExprs) + dict.typeParamMethodExprs = append(dict.typeParamMethodExprs, newInfo) + return idx +} + +// subdictIdx returns the index where the given encoded object's +// runtime dictionary appears within this dictionary's subdictionary +// section, adding it if necessary. +func (dict *writerDict) subdictIdx(newInfo objInfo) int { + for idx, oldInfo := range dict.subdicts { + if oldInfo.equals(newInfo) { + return idx + } + } + + idx := len(dict.subdicts) + dict.subdicts = append(dict.subdicts, newInfo) + return idx +} + +// rtypeIdx returns the index where the given encoded type's +// *runtime._type value appears within this dictionary's rtypes +// section, adding it if necessary. +func (dict *writerDict) rtypeIdx(newInfo typeInfo) int { + for idx, oldInfo := range dict.rtypes { + if oldInfo == newInfo { + return idx + } + } + + idx := len(dict.rtypes) + dict.rtypes = append(dict.rtypes, newInfo) + return idx +} + +// itabIdx returns the index where the given encoded type pair's +// *runtime.itab value appears within this dictionary's itabs section, +// adding it if necessary. +func (dict *writerDict) itabIdx(typInfo, ifaceInfo typeInfo) int { + newInfo := itabInfo{typInfo, ifaceInfo} + + for idx, oldInfo := range dict.itabs { + if oldInfo == newInfo { + return idx + } + } + + idx := len(dict.itabs) + dict.itabs = append(dict.itabs, newInfo) + return idx +} + func (pw *pkgWriter) newWriter(k pkgbits.RelocKind, marker pkgbits.SyncMarker) *writer { return &writer{ Encoder: pw.NewEncoder(k, marker), @@ -412,19 +506,9 @@ func (pw *pkgWriter) typIdx(typ types2.Type, dict *writerDict) typeInfo { w.obj(obj, targs) case *types2.TypeParam: - index := func() int { - for idx, name := range w.dict.implicits { - if name.Type().(*types2.TypeParam) == typ { - return idx - } - } - - return len(w.dict.implicits) + typ.Index() - }() - w.derived = true w.Code(pkgbits.TypeTypeParam) - w.Len(index) + w.Len(w.dict.typeParamIndex(typ)) case *types2.Array: w.Code(pkgbits.TypeArray) @@ -757,6 +841,33 @@ func (w *writer) objDict(obj types2.Object, dict *writerDict) { w.Bool(typ.needed) } + // Write runtime dictionary information. + // + // N.B., the go/types importer reads up to the section, but doesn't + // read any further, so it's safe to change. (See TODO above.) + + w.Len(len(dict.typeParamMethodExprs)) + for _, info := range dict.typeParamMethodExprs { + w.Len(info.typeParamIdx) + w.selectorInfo(info.methodInfo) + } + + w.Len(len(dict.subdicts)) + for _, info := range dict.subdicts { + w.objInfo(info) + } + + w.Len(len(dict.rtypes)) + for _, info := range dict.rtypes { + w.typInfo(info) + } + + w.Len(len(dict.itabs)) + for _, info := range dict.itabs { + w.typInfo(info.typ) + w.typInfo(info.iface) + } + assert(len(dict.derived) == nderived) } @@ -960,17 +1071,21 @@ func (w *writer) funcargs(sig *types2.Signature) { func (w *writer) funcarg(param *types2.Var, result bool) { if param.Name() != "" || result { - w.addLocal(param) + w.addLocal(param, true) } } // addLocal records the declaration of a new local variable. -func (w *writer) addLocal(obj *types2.Var) { - w.Sync(pkgbits.SyncAddLocal) +func (w *writer) addLocal(obj *types2.Var, isParam bool) { idx := len(w.localsIdx) - if w.p.SyncMarkers() { - w.Int(idx) + + if !isParam { + w.Sync(pkgbits.SyncAddLocal) + if w.p.SyncMarkers() { + w.Int(idx) + } } + if w.localsIdx == nil { w.localsIdx = make(map[*types2.Var]int) } @@ -1161,7 +1276,7 @@ func (w *writer) assign(expr syntax.Expr) { // TODO(mdempsky): Minimize locals index size by deferring // this until the variables actually come into scope. - w.addLocal(obj) + w.addLocal(obj, false) return } } @@ -1424,7 +1539,7 @@ func (w *writer) switchStmt(stmt *syntax.SwitchStmt) { obj := obj.(*types2.Var) w.typ(obj.Type()) - w.addLocal(obj) + w.addLocal(obj, false) } w.stmts(clause.Body) @@ -1494,7 +1609,8 @@ func (w *writer) expr(expr syntax.Expr) { obj := obj.(*types2.Func) w.Code(exprFuncInst) - w.obj(obj, targs) + w.pos(expr) + w.funcInst(obj, targs) return } @@ -1540,9 +1656,9 @@ func (w *writer) expr(expr syntax.Expr) { case types2.MethodVal: w.Code(exprMethodVal) - w.recvExpr(expr, sel) + typ := w.recvExpr(expr, sel) w.pos(expr) - w.selector(sel.Obj()) + w.methodExpr(expr, typ, sel) case types2.MethodExpr: w.Code(exprMethodExpr) @@ -1551,9 +1667,27 @@ func (w *writer) expr(expr syntax.Expr) { assert(ok) assert(tv.IsType()) - w.typ(tv.Type) + index := sel.Index() + implicits := index[:len(index)-1] + + typ := tv.Type + w.typ(typ) + + w.Len(len(implicits)) + for _, ix := range implicits { + w.Len(ix) + typ = deref2(typ).Underlying().(*types2.Struct).Field(ix).Type() + } + + recv := sel.Obj().(*types2.Func).Type().(*types2.Signature).Recv().Type() + if w.Bool(isPtrTo(typ, recv)) { // need deref + typ = recv + } else if w.Bool(isPtrTo(recv, typ)) { // need addr + typ = recv + } + w.pos(expr) - w.selector(sel.Obj()) + w.methodExpr(expr, typ, sel) } case *syntax.IndexExpr: @@ -1695,18 +1829,28 @@ func (w *writer) expr(expr syntax.Expr) { } writeFunExpr := func() { - if selector, ok := unparen(expr.Fun).(*syntax.SelectorExpr); ok { + fun := unparen(expr.Fun) + + if selector, ok := fun.(*syntax.SelectorExpr); ok { if sel, ok := w.p.info.Selections[selector]; ok && sel.Kind() == types2.MethodVal { - w.recvExpr(selector, sel) w.Bool(true) // method call - w.pos(selector) - w.selector(sel.Obj()) + typ := w.recvExpr(selector, sel) + w.methodExpr(selector, typ, sel) return } } - w.expr(expr.Fun) w.Bool(false) // not a method call (i.e., normal function call) + + if obj, inst := lookupObj(w.p.info, fun); w.Bool(obj != nil && inst.TypeArgs.Len() != 0) { + obj := obj.(*types2.Func) + + w.pos(fun) + w.funcInst(obj, inst.TypeArgs) + return + } + + w.expr(fun) } sigType := types2.CoreType(tv.Type).(*types2.Signature) @@ -1742,7 +1886,8 @@ func (w *writer) optExpr(expr syntax.Expr) { } // recvExpr writes out expr.X, but handles any implicit addressing, -// dereferencing, and field selections. +// dereferencing, and field selections appropriate for the method +// selection. func (w *writer) recvExpr(expr *syntax.SelectorExpr, sel *types2.Selection) types2.Type { index := sel.Index() implicits := index[:len(index)-1] @@ -1758,13 +1903,6 @@ func (w *writer) recvExpr(expr *syntax.SelectorExpr, sel *types2.Selection) type w.Len(ix) } - isPtrTo := func(from, to types2.Type) bool { - if from, ok := from.(*types2.Pointer); ok { - return types2.Identical(from.Elem(), to) - } - return false - } - recv := sel.Obj().(*types2.Func).Type().(*types2.Signature).Recv().Type() if w.Bool(isPtrTo(typ, recv)) { // needs deref typ = recv @@ -1775,6 +1913,84 @@ func (w *writer) recvExpr(expr *syntax.SelectorExpr, sel *types2.Selection) type return typ } +// funcInst writes a reference to an instantiated function. +func (w *writer) funcInst(obj *types2.Func, targs *types2.TypeList) { + info := w.p.objInstIdx(obj, targs, w.dict) + + // Type arguments list contains derived types; we can emit a static + // call to the shaped function, but need to dynamically compute the + // runtime dictionary pointer. + if w.Bool(info.anyDerived()) { + w.Len(w.dict.subdictIdx(info)) + return + } + + // Type arguments list is statically known; we can emit a static + // call with a statically reference to the respective runtime + // dictionary. + w.objInfo(info) +} + +// methodExpr writes out a reference to the method selected by +// expr. sel should be the corresponding types2.Selection, and recv +// the type produced after any implicit addressing, dereferencing, and +// field selection. (Note: recv might differ from sel.Obj()'s receiver +// parameter in the case of interface types, and is needed for +// handling type parameter methods.) +func (w *writer) methodExpr(expr *syntax.SelectorExpr, recv types2.Type, sel *types2.Selection) { + fun := sel.Obj().(*types2.Func) + sig := fun.Type().(*types2.Signature) + + w.typ(recv) + w.signature(sig) + w.pos(expr) + w.selector(fun) + + // Method on a type parameter. These require an indirect call + // through the current function's runtime dictionary. + if typeParam, ok := recv.(*types2.TypeParam); w.Bool(ok) { + typeParamIdx := w.dict.typeParamIndex(typeParam) + methodInfo := w.p.selectorIdx(fun) + + w.Len(w.dict.typeParamMethodExprIdx(typeParamIdx, methodInfo)) + return + } + + if isInterface(recv) != isInterface(sig.Recv().Type()) { + w.p.fatalf(expr, "isInterface inconsistency: %v and %v", recv, sig.Recv().Type()) + } + + if !isInterface(recv) { + if named, ok := deref2(recv).(*types2.Named); ok { + obj, targs := splitNamed(named) + info := w.p.objInstIdx(obj, targs, w.dict) + + // Method on a derived receiver type. These can be handled by a + // static call to the shaped method, but require dynamically + // looking up the appropriate dictionary argument in the current + // function's runtime dictionary. + if w.p.hasImplicitTypeParams(obj) || info.anyDerived() { + w.Bool(true) // dynamic subdictionary + w.Len(w.dict.subdictIdx(info)) + return + } + + // Method on a fully known receiver type. These can be handled + // by a static call to the shaped method, and with a static + // reference to the receiver type's dictionary. + if targs.Len() != 0 { + w.Bool(false) // no dynamic subdictionary + w.Bool(true) // static dictionary + w.objInfo(info) + return + } + } + } + + w.Bool(false) // no dynamic subdictionary + w.Bool(false) // no static dictionary +} + // multiExpr writes a sequence of expressions, where the i'th value is // implicitly converted to dstType(i). It also handles when exprs is a // single, multi-valued expression (e.g., the multi-valued argument in @@ -1930,18 +2146,34 @@ func (w *writer) exprs(exprs []syntax.Expr) { // rtype writes information so that the reader can construct an // expression of type *runtime._type representing typ. func (w *writer) rtype(typ types2.Type) { + typ = types2.Default(typ) + w.Sync(pkgbits.SyncRType) - w.typNeeded(typ) + + info := w.p.typIdx(typ, w.dict) + if w.Bool(info.derived) { + w.Len(w.dict.rtypeIdx(info)) + } else { + w.typInfo(info) + } } -// typNeeded writes a reference to typ, and records that its -// *runtime._type is needed. -func (w *writer) typNeeded(typ types2.Type) { - info := w.p.typIdx(typ, w.dict) - w.typInfo(info) +func isUntyped(typ types2.Type) bool { + basic, ok := typ.(*types2.Basic) + return ok && basic.Info()&types2.IsUntyped != 0 +} - if info.derived { - w.dict.derived[info.idx].needed = true +func (w *writer) itab(typ, iface types2.Type) { + typ = types2.Default(typ) + iface = types2.Default(iface) + + typInfo := w.p.typIdx(typ, w.dict) + ifaceInfo := w.p.typIdx(iface, w.dict) + if w.Bool(typInfo.derived || ifaceInfo.derived) { + w.Len(w.dict.itabIdx(typInfo, ifaceInfo)) + } else { + w.typInfo(typInfo) + w.typInfo(ifaceInfo) } } @@ -1949,8 +2181,7 @@ func (w *writer) typNeeded(typ types2.Type) { // expressions for converting from src to dst. func (w *writer) convRTTI(src, dst types2.Type) { w.Sync(pkgbits.SyncConvRTTI) - w.typNeeded(src) - w.typNeeded(dst) + w.itab(src, dst) } func (w *writer) exprType(iface types2.Type, typ syntax.Expr) { @@ -1963,9 +2194,13 @@ func (w *writer) exprType(iface types2.Type, typ syntax.Expr) { w.Sync(pkgbits.SyncExprType) w.pos(typ) - w.typNeeded(tv.Type) if w.Bool(iface != nil && !iface.Underlying().(*types2.Interface).Empty()) { - w.typ(iface) + w.itab(tv.Type, iface) + } else { + w.rtype(tv.Type) + + info := w.p.typIdx(tv.Type, w.dict) + w.Bool(info.derived) } } @@ -2435,3 +2670,9 @@ func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag { } return p.(*pragmas).Flag } + +// isPtrTo reports whether from is the type *to. +func isPtrTo(from, to types2.Type) bool { + ptr, ok := from.(*types2.Pointer) + return ok && types2.Identical(ptr.Elem(), to) +} diff --git a/src/cmd/compile/internal/ssa/debug_lines_test.go b/src/cmd/compile/internal/ssa/debug_lines_test.go index 47772cf1a5..7b8b5eb180 100644 --- a/src/cmd/compile/internal/ssa/debug_lines_test.go +++ b/src/cmd/compile/internal/ssa/debug_lines_test.go @@ -88,7 +88,7 @@ func TestDebugLinesPushback(t *testing.T) { fn := "(*List[go.shape.int_0]).PushBack" if buildcfg.Experiment.Unified { // Unified mangles differently - fn = "(*List[int]).PushBack-shaped" + fn = "(*List[go.shape.int]).PushBack" } testDebugLines(t, "-N -l", "pushback.go", fn, []int{17, 18, 19, 20, 21, 22, 24}, true) } @@ -105,7 +105,7 @@ func TestDebugLinesConvert(t *testing.T) { fn := "G[go.shape.int_0]" if buildcfg.Experiment.Unified { // Unified mangles differently - fn = "G[int]-shaped" + fn = "G[go.shape.int]" } testDebugLines(t, "-N -l", "convertline.go", fn, []int{9, 10, 11}, true) } diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index 8295a4e560..b932c2c444 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -382,6 +382,11 @@ func Assignop1(src, dst *types.Type) (ir.Op, string) { // don't have the methods for them. return ir.OCONVIFACE, "" } + if base.Debug.Unified != 0 && src.HasShape() { + // Unified IR uses OCONVIFACE for converting all derived types + // to interface type, not just type arguments themselves. + return ir.OCONVIFACE, "" + } if implements(src, dst, &missing, &have, &ptr) { return ir.OCONVIFACE, "" } diff --git a/src/cmd/compile/internal/walk/convert.go b/src/cmd/compile/internal/walk/convert.go index 753dbc3e88..57f28e9800 100644 --- a/src/cmd/compile/internal/walk/convert.go +++ b/src/cmd/compile/internal/walk/convert.go @@ -45,7 +45,14 @@ func walkConvInterface(n *ir.ConvExpr, init *ir.Nodes) ir.Node { toType := n.Type() if !fromType.IsInterface() && !ir.IsBlank(ir.CurFunc.Nname) { // skip unnamed functions (func _()) - reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym) + if base.Debug.Unified != 0 && fromType.HasShape() { + // Unified IR uses OCONVIFACE for converting all derived types + // to interface type. Avoid assertion failure in + // MarkTypeUsedInInterface, because we've marked used types + // separately anyway. + } else { + reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym) + } } if !fromType.IsInterface() { diff --git a/test/typeparam/nested.go b/test/typeparam/nested.go index cdb8bfb574..068e32be1d 100644 --- a/test/typeparam/nested.go +++ b/test/typeparam/nested.go @@ -104,11 +104,27 @@ func main() { F[V]() F[W]() - type X[A any] U[X[A]] - - F[X[int]]() - F[X[Int]]() - F[X[GlobalInt]]() + // TODO(go.dev/issue/54512): Restore these tests. They currently + // cause problems for shaping with unified IR. + // + // For example, instantiating X[int] requires instantiating shape + // type X[shapify(int)] == X[go.shape.int]. In turn, this requires + // instantiating U[shapify(X[go.shape.int])]. But we're still in the + // process of constructing X[go.shape.int], so we don't yet know its + // underlying type. + // + // Notably, this is a consequence of unified IR writing out type + // declarations with a reference to the full RHS expression (i.e., + // U[X[A]]) rather than its underlying type (i.e., int), which is + // necessary to support //go:notinheap. Once go.dev/issue/46731 is + // implemented and unified IR is updated, I expect this will just + // work. + // + // type X[A any] U[X[A]] + // + // F[X[int]]() + // F[X[Int]]() + // F[X[GlobalInt]]() for j, tj := range tests { for i, ti := range tests[:j+1] {