From 9371a6558412f621b5fbb54c42b43b93f8080e68 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Tue, 12 Jul 2022 11:04:44 -0700 Subject: internal/pkgbits: change EnableSync into a dynamic knob Rather than requiring users to recompile the compiler and all tools to enable/disable sync markers, this CL adds a flag word into the Unified IR file format to allow indicating whether they're enabled or not. This in turn requires bumping the file format version. Thanks to drchase@ for benchmarks showing this isn't as expensive as I feared it would be. Change-Id: I99afa0ee0b6ef5f30ed8ca840805ff9fd46b1857 Reviewed-on: https://go-review.googlesource.com/c/go/+/417097 Reviewed-by: David Chase Run-TryBot: Matthew Dempsky TryBot-Result: Gopher Robot --- src/internal/pkgbits/encoder.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'src/internal/pkgbits/encoder.go') diff --git a/src/internal/pkgbits/encoder.go b/src/internal/pkgbits/encoder.go index 1326a135cf..c50c838caa 100644 --- a/src/internal/pkgbits/encoder.go +++ b/src/internal/pkgbits/encoder.go @@ -14,6 +14,13 @@ import ( "runtime" ) +// currentVersion is the current version number. +// +// - v0: initial prototype +// +// - v1: adds the flags uint32 word +const currentVersion uint32 = 1 + // A PkgEncoder provides methods for encoding a package's Unified IR // export data. type PkgEncoder struct { @@ -25,15 +32,21 @@ type PkgEncoder struct { // elems[RelocString][stringsIdx[s]] == s (if present). stringsIdx map[string]Index + // syncFrames is the number of frames to write at each sync + // marker. A negative value means sync markers are omitted. syncFrames int } +// SyncMarkers reports whether pw uses sync markers. +func (pw *PkgEncoder) SyncMarkers() bool { return pw.syncFrames >= 0 } + // NewPkgEncoder returns an initialized PkgEncoder. // // syncFrames is the number of caller frames that should be serialized // at Sync points. Serializing additional frames results in larger // export data files, but can help diagnosing desync errors in -// higher-level Unified IR reader/writer code. +// higher-level Unified IR reader/writer code. If syncFrames is +// negative, then sync markers are omitted entirely. func NewPkgEncoder(syncFrames int) PkgEncoder { return PkgEncoder{ stringsIdx: make(map[string]Index), @@ -51,7 +64,13 @@ func (pw *PkgEncoder) DumpTo(out0 io.Writer) (fingerprint [8]byte) { assert(binary.Write(out, binary.LittleEndian, x) == nil) } - writeUint32(0) // version + writeUint32(currentVersion) + + var flags uint32 + if pw.SyncMarkers() { + flags |= flagSyncMarkers + } + writeUint32(flags) // Write elemEndsEnds. var sum uint32 @@ -204,7 +223,7 @@ func (w *Encoder) rawReloc(r RelocKind, idx Index) int { } func (w *Encoder) Sync(m SyncMarker) { - if !EnableSync { + if !w.p.SyncMarkers() { return } -- cgit v1.3-5-g9baa From fc72b7705d67f432fb6a570f84df9e8840eec226 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Sat, 23 Jul 2022 23:54:15 -0700 Subject: [dev.unified] cmd/compile: add method expressions to dictionaries This CL changes method expressions that use derived-type receiver parameters to use dictionary lookups. Change-Id: Iacd09b6d77a2d3000438ec8bc9b5af2a0b068aa7 Reviewed-on: https://go-review.googlesource.com/c/go/+/419455 Reviewed-by: David Chase Run-TryBot: Matthew Dempsky TryBot-Result: Gopher Robot --- src/cmd/compile/internal/noder/reader.go | 24 ++++++---- src/cmd/compile/internal/noder/writer.go | 76 +++++++++++++++++++++++++++++--- src/internal/pkgbits/encoder.go | 8 +++- 3 files changed, 93 insertions(+), 15 deletions(-) (limited to 'src/internal/pkgbits/encoder.go') diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index fbbce7e80f..6b7ac5494f 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -154,6 +154,8 @@ type readerDict struct { funcsObj []ir.Node itabs []itabInfo2 + + methodExprs []ir.Node } type itabInfo2 struct { @@ -776,6 +778,14 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex dict.itabs[i] = itabInfo2{typ: typ, lsym: lsym} } + dict.methodExprs = make([]ir.Node, r.Len()) + for i := range dict.methodExprs { + recv := pr.typIdx(typeInfo{idx: pkgbits.Index(r.Len()), derived: true}, &dict, true) + _, sym := r.selector() + + dict.methodExprs[i] = typecheck.Expr(ir.NewSelectorExpr(src.NoXPos, ir.OXDOT, ir.TypeNode(recv), sym)) + } + return &dict } @@ -1696,15 +1706,13 @@ func (r *reader) expr() (res ir.Node) { case exprSelector: var x ir.Node if r.Bool() { // MethodExpr - x = r.exprType(false) - - // Method expression with derived receiver type. - if x.Op() == ir.ODYNAMICTYPE { - // TODO(mdempsky): Handle with runtime dictionary lookup. - n := ir.TypeNode(x.Type()) - n.SetTypecheck(1) - x = n + if r.Bool() { + return r.dict.methodExprs[r.Len()] } + + n := ir.TypeNode(r.typ()) + n.SetTypecheck(1) + x = n } else { // FieldVal, MethodVal x = r.expr() } diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index ac08022c34..93e81bdc82 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -177,6 +177,10 @@ type writerDict struct { // itabs lists itabs that are needed for dynamic type assertions // (including type switches). itabs []itabInfo + + // methodsExprs lists method expressions with derived-type receiver + // parameters. + methodExprs []methodExprInfo } // A derivedInfo represents a reference to an encoded generic Go type. @@ -208,13 +212,26 @@ type objInfo struct { // An itabInfo represents a reference to an encoded itab entry (i.e., // a non-empty interface type along with a concrete type that // implements that interface). -// -// itabInfo is only used for type itabInfo struct { typIdx pkgbits.Index // always a derived type index iface typeInfo // always a non-empty interface type } +// A methodExprInfo represents a reference to an encoded method +// expression, whose receiver parameter is a derived type. +type methodExprInfo struct { + recvIdx pkgbits.Index // always a derived type index + methodInfo selectorInfo +} + +// A selectorInfo represents a reference to an encoded field or method +// name (i.e., objects that can only be accessed using selector +// expressions). +type selectorInfo struct { + pkgIdx pkgbits.Index + nameIdx pkgbits.Index +} + // anyDerived reports whether any of info's explicit type arguments // are derived types. func (info objInfo) anyDerived() bool { @@ -296,8 +313,12 @@ func (pw *pkgWriter) posBaseIdx(b *syntax.PosBase) pkgbits.Index { // pkg writes a use of the given Package into the element bitstream. func (w *writer) pkg(pkg *types2.Package) { + w.pkgRef(w.p.pkgIdx(pkg)) +} + +func (w *writer) pkgRef(idx pkgbits.Index) { w.Sync(pkgbits.SyncPkg) - w.Reloc(pkgbits.RelocPkg, w.p.pkgIdx(pkg)) + w.Reloc(pkgbits.RelocPkg, idx) } // pkgIdx returns the index for the given package, adding it to the @@ -793,6 +814,12 @@ func (w *writer) objDict(obj types2.Object, dict *writerDict) { w.typInfo(itab.iface) } + w.Len(len(dict.methodExprs)) + for _, methodExpr := range dict.methodExprs { + w.Len(int(methodExpr.recvIdx)) + w.selectorInfo(methodExpr.methodInfo) + } + assert(len(dict.derived) == nderived) assert(len(dict.funcs) == nfuncs) } @@ -862,9 +889,19 @@ func (w *writer) localIdent(obj types2.Object) { // selector writes the name of a field or method (i.e., objects that // can only be accessed using selector expressions). func (w *writer) selector(obj types2.Object) { + w.selectorInfo(w.p.selectorIdx(obj)) +} + +func (w *writer) selectorInfo(info selectorInfo) { w.Sync(pkgbits.SyncSelector) - w.pkg(obj.Pkg()) - w.String(obj.Name()) + w.pkgRef(info.pkgIdx) + w.StringRef(info.nameIdx) +} + +func (pw *pkgWriter) selectorIdx(obj types2.Object) selectorInfo { + pkgIdx := pw.pkgIdx(obj.Pkg()) + nameIdx := pw.StringIdx(obj.Name()) + return selectorInfo{pkgIdx: pkgIdx, nameIdx: nameIdx} } // @@@ Compiler extensions @@ -1490,7 +1527,19 @@ func (w *writer) expr(expr syntax.Expr) { w.Code(exprSelector) if w.Bool(sel.Kind() == types2.MethodExpr) { - w.exprType(nil, expr.X, false) + tv, ok := w.p.info.Types[expr.X] + assert(ok) + assert(tv.IsType()) + + typInfo := w.p.typIdx(tv.Type, w.dict) + if w.Bool(typInfo.derived) { + methodInfo := w.p.selectorIdx(sel.Obj()) + idx := w.dict.methodExprIdx(typInfo, methodInfo) + w.Len(idx) + break + } + + w.typInfo(typInfo) } else { w.expr(expr.X) } @@ -1821,6 +1870,21 @@ func (w *writer) exprType(iface types2.Type, typ syntax.Expr, nilOK bool) { w.typInfo(info) } +func (dict *writerDict) methodExprIdx(recvInfo typeInfo, methodInfo selectorInfo) int { + assert(recvInfo.derived) + newInfo := methodExprInfo{recvIdx: recvInfo.idx, methodInfo: methodInfo} + + for idx, oldInfo := range dict.methodExprs { + if oldInfo == newInfo { + return idx + } + } + + idx := len(dict.methodExprs) + dict.methodExprs = append(dict.methodExprs, newInfo) + return idx +} + // isInterface reports whether typ is known to be an interface type. // If typ is a type parameter, then isInterface reports an internal // compiler error instead. diff --git a/src/internal/pkgbits/encoder.go b/src/internal/pkgbits/encoder.go index c50c838caa..c0f2252909 100644 --- a/src/internal/pkgbits/encoder.go +++ b/src/internal/pkgbits/encoder.go @@ -316,8 +316,14 @@ func (w *Encoder) Code(c Code) { // section (if not already present), and then writing a relocation // into the element bitstream. func (w *Encoder) String(s string) { + w.StringRef(w.p.StringIdx(s)) +} + +// StringRef writes a reference to the given index, which must be a +// previously encoded string value. +func (w *Encoder) StringRef(idx Index) { w.Sync(SyncString) - w.Reloc(RelocString, w.p.StringIdx(s)) + w.Reloc(RelocString, idx) } // Strings encodes and writes a variable-length slice of strings into -- cgit v1.3-5-g9baa From ac0844ec274bf6cfd64bc8fa1e2cc8b24c789dee Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 27 Jul 2022 00:33:56 -0700 Subject: [dev.unified] cmd/compile: move "has init" to private metadata Currently, there's a "has init" bool in the public metadata section, which is only needed by cmd/compile; but because it's in the public metadata section, it's known to the go/types importers too. This CL moves it instead to the new compiler-only private metadata section added in the last CL for the inline bodies index. The existing bool in the public metadata section is left in place, and just always set to false, to avoid breaking the x/tools importer. The next time we bump the export version number, we can remove the bool properly. But no urgency just yet. Change-Id: I380f358652374b5a221f85020a53dc65912ddb29 Reviewed-on: https://go-review.googlesource.com/c/go/+/419676 Run-TryBot: Matthew Dempsky Reviewed-by: David Chase TryBot-Result: Gopher Robot --- src/cmd/compile/internal/importer/ureader.go | 2 +- src/cmd/compile/internal/noder/unified.go | 23 +++++++++++++---------- src/go/internal/gcimporter/ureader.go | 2 +- src/internal/pkgbits/encoder.go | 4 ++++ 4 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src/internal/pkgbits/encoder.go') diff --git a/src/cmd/compile/internal/importer/ureader.go b/src/cmd/compile/internal/importer/ureader.go index ccd4c7c502..d00b765859 100644 --- a/src/cmd/compile/internal/importer/ureader.go +++ b/src/cmd/compile/internal/importer/ureader.go @@ -39,7 +39,7 @@ func ReadPackage(ctxt *types2.Context, imports map[string]*types2.Package, input r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) pkg := r.pkg() - r.Bool() // has init + r.Bool() // TODO(mdempsky): Remove; was "has init" for i, n := 0, r.Len(); i < n; i++ { // As if r.obj(), but avoiding the Scope.Lookup call, diff --git a/src/cmd/compile/internal/noder/unified.go b/src/cmd/compile/internal/noder/unified.go index d9b15ab385..eebbb03742 100644 --- a/src/cmd/compile/internal/noder/unified.go +++ b/src/cmd/compile/internal/noder/unified.go @@ -161,7 +161,7 @@ func writePkgStub(noders []*noder) string { { w := publicRootWriter w.pkg(pkg) - w.Bool(false) // has init; XXX + w.Bool(false) // TODO(mdempsky): Remove; was "has init" scope := pkg.Scope() names := scope.Names() @@ -237,12 +237,7 @@ func readPackage(pr *pkgReader, importpkg *types.Pkg, localStub bool) { pkg := r.pkg() base.Assertf(pkg == importpkg, "have package %q (%p), want package %q (%p)", pkg.Path, pkg, importpkg.Path, importpkg) - if r.Bool() { - sym := pkg.Lookup(".inittask") - task := ir.NewNameAt(src.NoXPos, sym) - task.Class = ir.PEXTERN - sym.Def = task - } + r.Bool() // TODO(mdempsky): Remove; was "has init" for i, n := 0, r.Len(); i < n; i++ { r.Sync(pkgbits.SyncObject) @@ -262,6 +257,13 @@ func readPackage(pr *pkgReader, importpkg *types.Pkg, localStub bool) { if !localStub { r := pr.newReader(pkgbits.RelocMeta, pkgbits.PrivateRootIdx, pkgbits.SyncPrivate) + if r.Bool() { + sym := importpkg.Lookup(".inittask") + task := ir.NewNameAt(src.NoXPos, sym) + task.Class = ir.PEXTERN + sym.Def = task + } + for i, n := 0, r.Len(); i < n; i++ { path := r.String() name := r.String() @@ -302,7 +304,7 @@ func writeUnifiedExport(out io.Writer) { r.Sync(pkgbits.SyncPkg) selfPkgIdx = l.relocIdx(pr, pkgbits.RelocPkg, r.Reloc(pkgbits.RelocPkg)) - r.Bool() // has init + r.Bool() // TODO(mdempsky): Remove; was "has init" for i, n := 0, r.Len(); i < n; i++ { r.Sync(pkgbits.SyncObject) @@ -333,8 +335,7 @@ func writeUnifiedExport(out io.Writer) { w.Sync(pkgbits.SyncPkg) w.Reloc(pkgbits.RelocPkg, selfPkgIdx) - - w.Bool(typecheck.Lookup(".inittask").Def != nil) + w.Bool(false) // TODO(mdempsky): Remove; was "has init" w.Len(len(idxs)) for _, idx := range idxs { @@ -361,6 +362,8 @@ func writeUnifiedExport(out io.Writer) { w := privateRootWriter + w.Bool(typecheck.Lookup(".inittask").Def != nil) + w.Len(len(bodies)) for _, body := range bodies { w.String(body.sym.Pkg.Path) diff --git a/src/go/internal/gcimporter/ureader.go b/src/go/internal/gcimporter/ureader.go index 63718a59e1..2047ad8ae9 100644 --- a/src/go/internal/gcimporter/ureader.go +++ b/src/go/internal/gcimporter/ureader.go @@ -60,7 +60,7 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) pkg := r.pkg() - r.Bool() // has init + r.Bool() // TODO(mdempsky): Remove; was "has init" for i, n := 0, r.Len(); i < n; i++ { // As if r.obj(), but avoiding the Scope.Lookup call, diff --git a/src/internal/pkgbits/encoder.go b/src/internal/pkgbits/encoder.go index c0f2252909..f1bc8367ef 100644 --- a/src/internal/pkgbits/encoder.go +++ b/src/internal/pkgbits/encoder.go @@ -19,6 +19,10 @@ import ( // - v0: initial prototype // // - v1: adds the flags uint32 word +// +// TODO(mdempsky): For the next version bump: +// - remove the unused dict.derived.needed bool +// - remove the legacy "has init" bool from the public root const currentVersion uint32 = 1 // A PkgEncoder provides methods for encoding a package's Unified IR -- cgit v1.3-5-g9baa From c9f2150cfb3c1db87f6434f727c25403d985a6e4 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Sun, 31 Jul 2022 18:48:16 -0700 Subject: [dev.unified] cmd/compile: start using runtime dictionaries This CL switches unified IR to start using runtime dictionaries, rather than pure stenciling. In particular, for each instantiated function `F[T]`, it now: 1. Generates a global variable `F[T]-dict` of type `[N]uintptr`, with all of the `*runtime._type` values needed by `F[T]`. 2. Generates a function `F[T]-shaped`, with an extra `.dict *[N]uintptr` parameter and indexing into that parameter for derived types. (N.B., this function is not yet actually using shape types.) 3. Changes `F[T]` to instead be a wrapper function that calls `F[T]-shaped` passing `&F[T]-dict` as the `.dict` parameter. This is done in one pass to make sure the overall wiring is all working (especially, function literals and inlining). Subsequent CLs will write more information into `F[T]-dict` and update `F[T]-shaped` to use it instead of relying on `T`-derived information itself. Once that's done, `F[T]-shaped` can be changed to `F[shapify(T)]` (e.g., `F[go.shape.int]`) and deduplicated. Change-Id: I0e802a4d9934794e01a6bfc367820af893335155 Reviewed-on: https://go-review.googlesource.com/c/go/+/420416 Run-TryBot: Matthew Dempsky TryBot-Result: Gopher Robot Auto-Submit: Matthew Dempsky Reviewed-by: Cuong Manh Le Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: David Chase --- src/cmd/compile/internal/noder/reader.go | 318 ++++++++++++++++++++--- src/cmd/compile/internal/noder/unified.go | 4 +- src/cmd/compile/internal/noder/writer.go | 22 +- src/cmd/compile/internal/ssa/debug_lines_test.go | 4 +- src/internal/pkgbits/encoder.go | 1 - 5 files changed, 308 insertions(+), 41 deletions(-) (limited to 'src/internal/pkgbits/encoder.go') diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index 9458332fc8..d02d05bc5d 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -17,6 +17,7 @@ import ( "cmd/compile/internal/dwarfgen" "cmd/compile/internal/inline" "cmd/compile/internal/ir" + "cmd/compile/internal/objw" "cmd/compile/internal/reflectdata" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" @@ -61,14 +62,16 @@ 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 + pr *pkgReader + idx pkgbits.Index + dict *readerDict + shapedFn *ir.Func } func (pri pkgReaderIndex) asReader(k pkgbits.RelocKind, marker pkgbits.SyncMarker) *reader { r := pri.pr.newReader(k, pri.idx, marker) r.dict = pri.dict + r.shapedFn = pri.shapedFn return r } @@ -98,6 +101,12 @@ type reader struct { funarghack bool + // shapedFn is the shape-typed version of curfn, if any. + shapedFn *ir.Func + + // dictParam is the .dict param, if any. + dictParam *ir.Name + // scopeVars is a stack tracking the number of variables declared in // the current function at the moment each open scope was opened. scopeVars []int @@ -119,7 +128,13 @@ type reader struct { // Label to return to. retlabel *types.Sym - inlvars, retvars ir.Nodes + // inlvars is the list of variables that the inlinee's arguments are + // assigned to, one for each receiver and normal parameter, in order. + inlvars ir.Nodes + + // retvars is the list of variables that the inlinee's results are + // assigned to, one for each result parameter, in order. + retvars ir.Nodes } type readerDict struct { @@ -737,12 +752,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex // For stenciling, we can just skip over the type parameters. for range dict.targs[dict.implicits:] { // Skip past bounds without actually evaluating them. - r.Sync(pkgbits.SyncType) - if r.Bool() { - r.Len() - } else { - r.Reloc(pkgbits.RelocType) - } + r.typInfo() } dict.derived = make([]derivedInfo, r.Len()) @@ -806,9 +816,7 @@ func (r *reader) method(rext *reader) *types.Field { _, recv := r.param() typ := r.signature(pkg, recv) - fnsym := sym - fnsym = ir.MethodSym(recv.Type, fnsym) - name := ir.NewNameAt(pos, fnsym) + name := ir.NewNameAt(pos, ir.MethodSym(recv.Type, sym)) setType(name, typ) name.Func = ir.NewFunc(r.pos()) @@ -981,7 +989,7 @@ var importBodyReader = map[*types.Sym]pkgReaderIndex{} func bodyReaderFor(fn *ir.Func) (pri pkgReaderIndex, ok bool) { if fn.Nname.Defn != nil { pri, ok = bodyReader[fn] - assert(ok) // must always be available + base.AssertfAt(ok, base.Pos, "must have bodyReader for %v", fn) // must always be available } else { pri, ok = importBodyReader[fn.Sym()] } @@ -999,7 +1007,38 @@ func (r *reader) addBody(fn *ir.Func) { // generic functions; see comment in funcExt. assert(fn.Nname.Defn != nil) - pri := pkgReaderIndex{r.p, r.Reloc(pkgbits.RelocBody), r.dict} + 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} bodyReader[fn] = pri if r.curfn == nil { @@ -1020,6 +1059,9 @@ func (pri pkgReaderIndex) funcBody(fn *ir.Func) { 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.WithFunc(fn, func() { r.funcargs(fn) @@ -1028,6 +1070,11 @@ func (r *reader) funcBody(fn *ir.Func) { return } + if r.shapedFn != nil { + r.callShaped(fn.Pos()) + return + } + body := r.stmts() if body == nil { body = []ir.Node{typecheck.Stmt(ir.NewBlockStmt(src.NoXPos, nil))} @@ -1039,6 +1086,139 @@ func (r *reader) funcBody(fn *ir.Func) { r.marker.WriteTo(fn) } +// callShaped emits a tail call to r.shapedFn, passing along the +// arguments to the current function. +func (r *reader) callShaped(pos src.XPos) { + 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() { + var arg ir.Node + if param.Nname != nil { + name := param.Nname.(*ir.Name) + if !ir.IsBlank(name) { + if r.inlCall != nil { + // During inlining, we want the respective inlvar where we + // assigned the callee's arguments. + arg = r.inlvars[len(args)-1] + } 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) + arg = name + } + } + } + + // For anonymous and blank parameters, we don't have an *ir.Name + // to use as the argument. However, since we know the shaped + // function won't use the value either, we can just pass the + // zero value. (Also unfortunately, we don't have an easy + // zero-value IR node; so we use a default-initialized temporary + // variable.) + if arg == nil { + tmp := typecheck.TempAt(pos, r.curfn, param.Type) + r.curfn.Body.Append( + typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp)), + typecheck.Stmt(ir.NewAssignStmt(pos, tmp, nil)), + ) + arg = tmp + } + + args.Append(arg) + } + } + + // 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) + + var stmt ir.Node + if sig.NumResults() != 0 { + stmt = typecheck.Stmt(ir.NewReturnStmt(pos, []ir.Node{call})) + } else { + stmt = call + } + 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 + } + + 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() + } + + sym := baseSym.Pkg.Lookup(baseSym.Name + "-dict") + + if sym.Def == nil { + dict := ir.NewNameAt(r.curfn.Pos(), sym) + dict.Class = ir.PEXTERN + + lsym := dict.Linksym() + ot := 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) + } + } + + // TODO(mdempsky): Write out more dictionary information. + + objw.Global(lsym, int32(ot), obj.DUPOK|obj.RODATA) + + dict.SetType(r.dict.varType()) + dict.SetTypecheck(1) + + sym.Def = dict + } + + return typecheck.Expr(ir.NewAddrExpr(r.curfn.Pos(), sym.Def.(*ir.Name))) +} + +// numWords returns the number of words that dict's runtime dictionary +// variable requires. +func (dict *readerDict) numWords() int64 { + var num int + num += len(dict.derivedTypes) + // TODO(mdempsky): Add space for more dictionary information. + return int64(num) +} + +// 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) funcargs(fn *ir.Func) { sig := fn.Nname.Type() @@ -1096,16 +1276,20 @@ func (r *reader) funcarg(param *types.Field, sym *types.Sym, ctxt ir.Class) { func (r *reader) addLocal(name *ir.Name, ctxt ir.Class) { assert(ctxt == ir.PAUTO || ctxt == ir.PPARAM || ctxt == ir.PPARAMOUT) - 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 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") + } } + r.locals = append(r.locals, name) } name.SetUsed(true) - r.locals = append(r.locals, name) // TODO(mdempsky): Move earlier. if ir.IsBlank(name) { @@ -2000,6 +2184,11 @@ func (r *reader) funcLit() ir.Node { for len(fn.ClosureVars) < cap(fn.ClosureVars) { 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) @@ -2024,39 +2213,60 @@ func (r *reader) exprs() []ir.Node { return nodes } -// rtype returns an expression of type *runtime._type. +// 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 { + 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)))) +} + +// 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 { r.Sync(pkgbits.SyncRType) - // TODO(mdempsky): For derived types, use dictionary instead. - return reflectdata.TypePtrAt(pos, r.typ()) + 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) + } + return typecheck.Expr(ir.NewConvExpr(pos, ir.OCONVNOP, types.NewPtr(types.Types[types.TUINT8]), r.dictWord(pos, int64(info.idx)))) } // 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 := r.typ() - dst := r.typ() + srcInfo := r.typInfo() + dstInfo := r.typInfo() + dst := r.p.typIdx(dstInfo, r.dict, true) if !dst.IsInterface() { return } + src := r.p.typIdx(srcInfo, r.dict, true) + // See reflectdata.ConvIfaceTypeWord. switch { case dst.IsEmptyInterface(): if !src.IsInterface() { - typeWord = reflectdata.TypePtrAt(pos, src) // direct eface construction + typeWord = r.rtypeInfo(pos, srcInfo) // direct eface construction } case !src.IsInterface(): typeWord = reflectdata.ITabAddrAt(pos, src, dst) // direct iface construction default: - typeWord = reflectdata.TypePtrAt(pos, dst) // convI2I + typeWord = r.rtypeInfo(pos, dstInfo) // convI2I } // See reflectdata.ConvIfaceSrcRType. if !src.IsInterface() { - srcRType = reflectdata.TypePtrAt(pos, src) + srcRType = r.rtypeInfo(pos, srcInfo) } return @@ -2096,7 +2306,7 @@ func (r *reader) exprType() ir.Node { return n } - rtype = lsymPtr(reflectdata.TypeLinksym(typ)) + rtype = r.rtypeInfo(pos, info) } dt := ir.NewDynamicType(pos, rtype) @@ -2275,6 +2485,9 @@ func InlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExp for i, cv := range r.inlFunc.ClosureVars { 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.funcargs(fn) @@ -2337,8 +2550,12 @@ func InlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExp nparams := len(r.curfn.Dcl) ir.WithFunc(r.curfn, func() { - r.curfn.Body = r.stmts() - r.curfn.Endlineno = r.pos() + if r.shapedFn != nil { + r.callShaped(call.Pos()) + } else { + 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 @@ -2800,3 +3017,40 @@ 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. +const dictParamName = ".dict" + +// 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. +func shapeSig(fn *ir.Func, dict *readerDict) *types.Type { + sig := fn.Nname.Type() + recv := sig.Recv() + nrecvs := 0 + if recv != nil { + nrecvs++ + } + + params := make([]*types.Field, 1+nrecvs+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 + } + + results := make([]*types.Field, sig.Results().Fields().Len()) + for i, result := range sig.Results().Fields().Slice() { + results[i] = types.NewField(result.Pos, result.Sym, result.Type) + } + + return types.NewSignature(types.LocalPkg, nil, nil, params, results) +} diff --git a/src/cmd/compile/internal/noder/unified.go b/src/cmd/compile/internal/noder/unified.go index eebbb03742..1ded367383 100644 --- a/src/cmd/compile/internal/noder/unified.go +++ b/src/cmd/compile/internal/noder/unified.go @@ -247,7 +247,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} + objReader[types.NewPkg(path, "").Lookup(name)] = pkgReaderIndex{pr, idx, nil, nil} } } @@ -271,7 +271,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} + importBodyReader[sym] = pkgReaderIndex{pr, idx, nil, nil} } } diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 0005c2e7fa..5f8767bf83 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -190,7 +190,7 @@ type writerDict struct { // A derivedInfo represents a reference to an encoded generic Go type. type derivedInfo struct { idx pkgbits.Index - needed bool // TODO(mdempsky): Remove; will break x/tools importer + needed bool } // A typeInfo represents a reference to an encoded Go type. @@ -1952,15 +1952,26 @@ func (w *writer) exprs(exprs []syntax.Expr) { // expression of type *runtime._type representing typ. func (w *writer) rtype(typ types2.Type) { w.Sync(pkgbits.SyncRType) - w.typ(typ) + w.typNeeded(typ) +} + +// 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) + + if info.derived { + w.dict.derived[info.idx].needed = true + } } // convRTTI writes information so that the reader can construct // expressions for converting from src to dst. func (w *writer) convRTTI(src, dst types2.Type) { w.Sync(pkgbits.SyncConvRTTI) - w.typ(src) - w.typ(dst) + w.typNeeded(src) + w.typNeeded(dst) } func (w *writer) exprType(iface types2.Type, typ syntax.Expr) { @@ -1992,6 +2003,9 @@ func (w *writer) exprType(iface types2.Type, typ syntax.Expr) { } w.typInfo(info) + if info.derived { + w.dict.derived[info.idx].needed = true + } } func (dict *writerDict) methodExprIdx(recvInfo typeInfo, methodInfo selectorInfo) int { diff --git a/src/cmd/compile/internal/ssa/debug_lines_test.go b/src/cmd/compile/internal/ssa/debug_lines_test.go index a76358967d..1b564055d3 100644 --- a/src/cmd/compile/internal/ssa/debug_lines_test.go +++ b/src/cmd/compile/internal/ssa/debug_lines_test.go @@ -76,7 +76,7 @@ func TestDebugLinesPushback(t *testing.T) { fn := "(*List[go.shape.int_0]).PushBack" if buildcfg.Experiment.Unified { // Unified mangles differently - fn = "(*List[int]).PushBack" + fn = "(*List[int]).PushBack-shaped" } testDebugLines(t, "-N -l", "pushback.go", fn, []int{17, 18, 19, 20, 21, 22, 24}, true) } @@ -95,7 +95,7 @@ func TestDebugLinesConvert(t *testing.T) { fn := "G[go.shape.int_0]" if buildcfg.Experiment.Unified { // Unified mangles differently - fn = "G[int]" + fn = "G[int]-shaped" } testDebugLines(t, "-N -l", "convertline.go", fn, []int{9, 10, 11}, true) } diff --git a/src/internal/pkgbits/encoder.go b/src/internal/pkgbits/encoder.go index f1bc8367ef..ec47e352cb 100644 --- a/src/internal/pkgbits/encoder.go +++ b/src/internal/pkgbits/encoder.go @@ -21,7 +21,6 @@ import ( // - v1: adds the flags uint32 word // // TODO(mdempsky): For the next version bump: -// - remove the unused dict.derived.needed bool // - remove the legacy "has init" bool from the public root const currentVersion uint32 = 1 -- cgit v1.3-5-g9baa