Skip to content
Snippets Groups Projects
reader.go 107 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright 2021 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package noder
    
    import (
    	"fmt"
    	"go/constant"
    
    	"internal/buildcfg"
    
    	"strings"
    
    	"cmd/compile/internal/base"
    	"cmd/compile/internal/deadcode"
    	"cmd/compile/internal/dwarfgen"
    
    	"cmd/compile/internal/inline"
    
    	"cmd/compile/internal/ir"
    
    	"cmd/compile/internal/reflectdata"
    
    	"cmd/compile/internal/staticinit"
    
    	"cmd/compile/internal/typecheck"
    	"cmd/compile/internal/types"
    	"cmd/internal/obj"
    
    // This file implements cmd/compile backend's reader for the Unified
    // IR export data.
    
    // A pkgReader reads Unified IR export data.
    
    type pkgReader struct {
    
    	// Indices for encoded things; lazily populated as needed.
    	//
    	// Note: Objects (i.e., ir.Names) are lazily instantiated by
    	// populating their types.Sym.Def; see objReader below.
    
    
    	posBases []*src.PosBase
    	pkgs     []*types.Pkg
    	typs     []*types.Type
    
    
    	// offset for rewriting the given (absolute!) index into the output,
    	// but bitwise inverted so we can detect if we're missing the entry
    	// or not.
    
    	newindex []pkgbits.Index
    
    func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader {
    
    		posBases: make([]*src.PosBase, pr.NumElems(pkgbits.RelocPosBase)),
    		pkgs:     make([]*types.Pkg, pr.NumElems(pkgbits.RelocPkg)),
    		typs:     make([]*types.Type, pr.NumElems(pkgbits.RelocType)),
    
    		newindex: make([]pkgbits.Index, pr.TotalElems()),
    
    // A pkgReaderIndex compactly identifies an index (and its
    // corresponding dictionary) within a package's export data.
    
    type pkgReaderIndex struct {
    
    	pr        *pkgReader
    	idx       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)
    
    func (pr *pkgReader) newReader(k pkgbits.RelocKind, idx pkgbits.Index, marker pkgbits.SyncMarker) *reader {
    
    		Decoder: pr.NewDecoder(k, idx, marker),
    
    // A reader provides APIs for reading an individual element.
    
    	// TODO(mdempsky): The state below is all specific to reading
    	// function bodies. It probably makes sense to split it out
    	// separately so that it doesn't take up space in every reader
    	// instance.
    
    
    	curfn       *ir.Func
    	locals      []*ir.Name
    	closureVars []*ir.Name
    
    	// methodSym is the name of method's name, if reading a method.
    	// It's nil if reading a normal function or closure body.
    	methodSym *types.Sym
    
    
    	// dictParam is the .dict param, if any.
    	dictParam *ir.Name
    
    
    	// synthetic is a callback function to construct a synthetic
    	// function body. It's used for creating the bodies of function
    	// literals used to curry arguments to shaped functions.
    	synthetic func(pos src.XPos, r *reader)
    
    
    	// scopeVars is a stack tracking the number of variables declared in
    	// the current function at the moment each open scope was opened.
    	scopeVars         []int
    	marker            dwarfgen.ScopeMarker
    	lastCloseScopePos src.XPos
    
    	// === details for handling inline body expansion ===
    
    	// If we're reading in a function body because of inlining, this is
    	// the call that we're inlining for.
    	inlCaller    *ir.Func
    	inlCall      *ir.CallExpr
    	inlFunc      *ir.Func
    	inlTreeIndex int
    	inlPosBases  map[*src.PosBase]*src.PosBase
    
    
    	// suppressInlPos tracks whether position base rewriting for
    	// inlining should be suppressed. See funcLit.
    	suppressInlPos int
    
    
    	delayResults bool
    
    	// Label to return to.
    	retlabel *types.Sym
    
    
    	// 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
    
    // 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.
    
    	shaped bool // whether this is a shaped dictionary
    
    	// baseSym is the symbol for the object this dictionary belongs to.
    	// If the object is an instantiated function or defined type, then
    	// baseSym is the mangled symbol, including any type arguments.
    	baseSym *types.Sym
    
    	// For non-shaped dictionaries, shapedObj is a reference to the
    	// corresponding shaped object (always a function or defined type).
    	shapedObj *ir.Name
    
    
    	// targs holds the implicit and explicit type arguments in use for
    	// reading the current object. For example:
    	//
    	//	func F[T any]() {
    	//		type X[U any] struct { t T; u U }
    	//		var _ X[string]
    	//	}
    	//
    	//	var _ = F[int]
    	//
    	// While instantiating F[int], we need to in turn instantiate
    	// X[string]. [int] and [string] are explicit type arguments for F
    	// and X, respectively; but [int] is also the implicit type
    	// arguments for X.
    	//
    	// (As an analogy to function literals, explicits are the function
    	// literal's formal parameters, while implicits are variables
    	// captured by the function literal.)
    	targs []*types.Type
    
    	// implicits counts how many of types within targs are implicit type
    	// arguments; the rest are explicit.
    	implicits int
    
    
    	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) {
    
    func setValue(name *ir.Name, val constant.Value) {
    
    	name.SetVal(val)
    	name.Defn = nil
    }
    
    // @@@ Positions
    
    
    // pos reads a position from the bitstream.
    
    func (r *reader) pos() src.XPos {
    	return base.Ctxt.PosTable.XPos(r.pos0())
    }
    
    func (r *reader) pos0() src.Pos {
    
    	r.Sync(pkgbits.SyncPos)
    	if !r.Bool() {
    
    		return src.NoPos
    	}
    
    	posBase := r.posBase()
    
    	line := r.Uint()
    	col := r.Uint()
    
    	return src.MakePos(posBase, line, col)
    }
    
    
    // posBase reads a position base from the bitstream.
    
    func (r *reader) posBase() *src.PosBase {
    
    	return r.inlPosBase(r.p.posBaseIdx(r.Reloc(pkgbits.RelocPosBase)))
    
    // posBaseIdx returns the specified position base, reading it first if
    // needed.
    
    func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) *src.PosBase {
    
    	if b := pr.posBases[idx]; b != nil {
    		return b
    	}
    
    
    	r := pr.newReader(pkgbits.RelocPosBase, idx, pkgbits.SyncPosBase)
    
    	filename := absFilename
    
    	// For build artifact stability, the export data format only
    	// contains the "absolute" filename as returned by objabi.AbsFile.
    	// However, some tests (e.g., test/run.go's asmcheck tests) expect
    	// to see the full, original filename printed out. Re-expanding
    	// "$GOROOT" to buildcfg.GOROOT is a close-enough approximation to
    	// satisfy this.
    	//
    	// TODO(mdempsky): De-duplicate this logic with similar logic in
    	// cmd/link/internal/ld's expandGoroot. However, this will probably
    	// require being more consistent about when we use native vs UNIX
    	// file paths.
    	const dollarGOROOT = "$GOROOT"
    
    	if buildcfg.GOROOT != "" && strings.HasPrefix(filename, dollarGOROOT) {
    
    		filename = buildcfg.GOROOT + filename[len(dollarGOROOT):]
    	}
    
    		b = src.NewFileBase(filename, absFilename)
    
    		line := r.Uint()
    		col := r.Uint()
    
    		b = src.NewLinePragmaBase(pos, filename, absFilename, line, col)
    
    // inlPosBase returns the inlining-adjusted src.PosBase corresponding
    // to oldBase, which must be a non-inlined position. When not
    // inlining, this is just oldBase.
    
    func (r *reader) inlPosBase(oldBase *src.PosBase) *src.PosBase {
    
    	if index := oldBase.InliningIndex(); index >= 0 {
    		base.Fatalf("oldBase %v already has inlining index %v", oldBase, index)
    	}
    
    	if r.inlCall == nil || r.suppressInlPos != 0 {
    
    		return oldBase
    	}
    
    	if newBase, ok := r.inlPosBases[oldBase]; ok {
    		return newBase
    	}
    
    	newBase := src.NewInliningBase(oldBase, r.inlTreeIndex)
    	r.inlPosBases[oldBase] = newBase
    	return newBase
    }
    
    
    // inlPos returns the inlining-adjusted src.XPos corresponding to
    // xpos, which must be a non-inlined position. When not inlining, this
    // is just xpos.
    func (r *reader) inlPos(xpos src.XPos) src.XPos {
    
    	pos := base.Ctxt.PosTable.Pos(xpos)
    	pos.SetBase(r.inlPosBase(pos.Base()))
    	return base.Ctxt.PosTable.XPos(pos)
    }
    
    // @@@ Packages
    
    
    // pkg reads a package reference from the bitstream.
    
    func (r *reader) pkg() *types.Pkg {
    
    	r.Sync(pkgbits.SyncPkg)
    	return r.p.pkgIdx(r.Reloc(pkgbits.RelocPkg))
    
    // pkgIdx returns the specified package from the export data, reading
    // it first if needed.
    
    func (pr *pkgReader) pkgIdx(idx pkgbits.Index) *types.Pkg {
    
    	if pkg := pr.pkgs[idx]; pkg != nil {
    		return pkg
    	}
    
    
    	pkg := pr.newReader(pkgbits.RelocPkg, idx, pkgbits.SyncPkgDef).doPkg()
    
    // doPkg reads a package definition from the bitstream.
    
    func (r *reader) doPkg() *types.Pkg {
    
    	case "builtin":
    		return types.BuiltinPkg
    	case "unsafe":
    		return types.UnsafePkg
    
    
    	pkg := types.NewPkg(path, "")
    
    	if pkg.Name == "" {
    		pkg.Name = name
    	} else {
    
    		base.Assertf(pkg.Name == name, "package %q has name %q, but want %q", pkg.Path, pkg.Name, name)
    
    	}
    
    	return pkg
    }
    
    // @@@ Types
    
    func (r *reader) typ() *types.Type {
    
    	return r.typWrapped(true)
    }
    
    // typWrapped is like typ, but allows suppressing generation of
    // unnecessary wrappers as a compile-time optimization.
    func (r *reader) typWrapped(wrapped bool) *types.Type {
    	return r.p.typIdx(r.typInfo(), r.dict, wrapped)
    
    	r.Sync(pkgbits.SyncType)
    	if r.Bool() {
    
    		return typeInfo{idx: pkgbits.Index(r.Len()), derived: true}
    
    	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 {
    
    	if info.derived {
    		where = &dict.derivedTypes[idx]
    		idx = dict.derived[idx].idx
    
    	} else {
    		where = &pr.typs[idx]
    	}
    
    	if typ := *where; typ != nil {
    
    	r := pr.newReader(pkgbits.RelocType, idx, pkgbits.SyncTypeIdx)
    
    	typ := r.doTyp()
    	assert(typ != nil)
    
    
    	// For recursive type declarations involving interfaces and aliases,
    	// above r.doTyp() call may have already set pr.typs[idx], so just
    	// double check and return the type.
    	//
    	// Example:
    	//
    	//     type F = func(I)
    	//
    	//     type I interface {
    	//         m(F)
    	//     }
    	//
    	// The writer writes data types in following index order:
    	//
    	//     0: func(I)
    	//     1: I
    	//     2: interface{m(func(I))}
    	//
    	// The reader resolves it in following index order:
    	//
    	//     0 -> 1 -> 2 -> 0 -> 1
    	//
    	// and can divide in logically 2 steps:
    	//
    	//  - 0 -> 1     : first time the reader reach type I,
    	//                 it creates new named type with symbol I.
    	//
    	//  - 2 -> 0 -> 1: the reader ends up reaching symbol I again,
    	//                 now the symbol I was setup in above step, so
    	//                 the reader just return the named type.
    	//
    	// Now, the functions called return, the pr.typs looks like below:
    	//
    	//  - 0 -> 1 -> 2 -> 0 : [<T> I <T>]
    	//  - 0 -> 1 -> 2      : [func(I) I <T>]
    	//  - 0 -> 1           : [func(I) I interface { "".m(func("".I)) }]
    	//
    	// The idx 1, corresponding with type I was resolved successfully
    	// after r.doTyp() call.
    
    	if prev := *where; prev != nil {
    		return prev
    
    	if wrapped {
    		// Only cache if we're adding wrappers, so that other callers that
    		// find a cached type know it was wrapped.
    		*where = typ
    
    		r.needWrapper(typ)
    	}
    
    	if !typ.IsUntyped() {
    		types.CheckSize(typ)
    	}
    
    	return typ
    }
    
    func (r *reader) doTyp() *types.Type {
    
    	switch tag := pkgbits.CodeType(r.Code(pkgbits.SyncType)); tag {
    
    	default:
    		panic(fmt.Sprintf("unexpected type: %v", tag))
    
    
    	case pkgbits.TypeBasic:
    		return *basics[r.Len()]
    
    		obj := r.obj()
    		assert(obj.Op() == ir.OTYPE)
    		return obj.Type()
    
    
    	case pkgbits.TypeTypeParam:
    		return r.dict.targs[r.Len()]
    
    	case pkgbits.TypeArray:
    		len := int64(r.Uint64())
    
    		return types.NewArray(r.typ(), len)
    
    	case pkgbits.TypeChan:
    		dir := dirs[r.Len()]
    
    		return types.NewChan(r.typ(), dir)
    
    		return types.NewMap(r.typ(), r.typ())
    
    		return types.NewPtr(r.typ())
    
    	case pkgbits.TypeSignature:
    
    		return r.signature(types.LocalPkg, nil)
    
    		return types.NewSlice(r.typ())
    
    	case pkgbits.TypeInterface:
    
    		return r.interfaceType()
    
    	case pkgbits.TypeUnion:
    		return r.unionType()
    
    func (r *reader) unionType() *types.Type {
    	terms := make([]*types.Type, r.Len())
    	tildes := make([]bool, len(terms))
    	for i := range terms {
    		tildes[i] = r.Bool()
    		terms[i] = r.typ()
    	}
    	return types.NewUnion(terms, tildes)
    }
    
    
    func (r *reader) interfaceType() *types.Type {
    	tpkg := types.LocalPkg // TODO(mdempsky): Remove after iexport is gone.
    
    
    	nmethods, nembeddeds := r.Len(), r.Len()
    
    	implicit := nmethods == 0 && nembeddeds == 1 && r.Bool()
    	assert(!implicit) // implicit interfaces only appear in constraints
    
    
    	fields := make([]*types.Field, nmethods+nembeddeds)
    	methods, embeddeds := fields[:nmethods], fields[nmethods:]
    
    	for i := range methods {
    		pos := r.pos()
    		pkg, sym := r.selector()
    		tpkg = pkg
    
    		mtyp := r.signature(pkg, types.FakeRecv())
    
    		methods[i] = types.NewField(pos, sym, mtyp)
    	}
    	for i := range embeddeds {
    		embeddeds[i] = types.NewField(src.NoXPos, nil, r.typ())
    	}
    
    	if len(fields) == 0 {
    		return types.Types[types.TINTER] // empty interface
    	}
    
    	return types.NewInterface(tpkg, fields, false)
    
    }
    
    func (r *reader) structType() *types.Type {
    	tpkg := types.LocalPkg // TODO(mdempsky): Remove after iexport is gone.
    
    	fields := make([]*types.Field, r.Len())
    
    	for i := range fields {
    		pos := r.pos()
    		pkg, sym := r.selector()
    		tpkg = pkg
    		ftyp := r.typ()
    
    		tag := r.String()
    		embedded := r.Bool()
    
    
    		f := types.NewField(pos, sym, ftyp)
    		f.Note = tag
    		if embedded {
    			f.Embedded = 1
    		}
    		fields[i] = f
    	}
    
    	return types.NewStruct(tpkg, fields)
    
    }
    
    func (r *reader) signature(tpkg *types.Pkg, recv *types.Field) *types.Type {
    
    	r.Sync(pkgbits.SyncSignature)
    
    
    	params := r.params(&tpkg)
    	results := r.params(&tpkg)
    
    		params[len(params)-1].SetIsDDD(true)
    	}
    
    	return types.NewSignature(tpkg, recv, nil, params, results)
    }
    
    func (r *reader) params(tpkg **types.Pkg) []*types.Field {
    
    	r.Sync(pkgbits.SyncParams)
    	fields := make([]*types.Field, r.Len())
    
    	for i := range fields {
    		*tpkg, fields[i] = r.param()
    	}
    	return fields
    }
    
    func (r *reader) param() (*types.Pkg, *types.Field) {
    
    
    	pos := r.pos()
    	pkg, sym := r.localIdent()
    	typ := r.typ()
    
    	return pkg, types.NewField(pos, sym, typ)
    }
    
    // @@@ Objects
    
    
    // objReader maps qualified identifiers (represented as *types.Sym) to
    // a pkgReader and corresponding index that can be used for reading
    // that object's definition.
    
    var objReader = map[*types.Sym]pkgReaderIndex{}
    
    
    // obj reads an instantiated object reference from the bitstream.
    
    func (r *reader) obj() ir.Node {
    
    	return r.p.objInstIdx(r.objInfo(), r.dict, false)
    }
    
    // objInfo reads an instantiated object reference from the bitstream
    // and returns the encoded reference to it, without instantiating it.
    func (r *reader) objInfo() objInfo {
    
    	r.Sync(pkgbits.SyncObject)
    
    	assert(!r.Bool()) // TODO(mdempsky): Remove; was derived func inst.
    
    	idx := r.Reloc(pkgbits.RelocObj)
    
    	explicits := make([]typeInfo, r.Len())
    
    	for i := range explicits {
    
    	return objInfo{idx, explicits}
    }
    
    // objInstIdx returns the encoded, instantiated object. If shaped is
    // true, then the shaped variant of the object is returned instead.
    func (pr *pkgReader) objInstIdx(info objInfo, dict *readerDict, shaped bool) ir.Node {
    	explicits := pr.typListIdx(info.explicits, dict)
    
    
    	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)
    
    	tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj))
    
    	if tag == pkgbits.ObjStub {
    
    		assert(!sym.IsBlank())
    		switch sym.Pkg {
    
    		case types.BuiltinPkg, types.UnsafePkg:
    
    			return sym.Def.(ir.Node)
    		}
    		if pri, ok := objReader[sym]; ok {
    
    			return pri.pr.objIdx(pri.idx, nil, explicits, shaped)
    
    		}
    		base.Fatalf("unresolved stub: %v", sym)
    	}
    
    	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)
    
    	do := func(op ir.Op, hasTParams bool) *ir.Name {
    		pos := r.pos()
    
    		if hasTParams {
    			r.typeParamNames()
    		}
    
    		name := ir.NewDeclNameAt(pos, op, sym)
    		name.Class = ir.PEXTERN // may be overridden later
    		if !sym.IsBlank() {
    			if sym.Def != nil {
    				base.FatalfAt(name.Pos(), "already have a definition for %v", name)
    			}
    			assert(sym.Def == nil)
    			sym.Def = name
    		}
    		return name
    	}
    
    	switch tag {
    	default:
    		panic("unexpected object")
    
    
    		name := do(ir.OTYPE, false)
    
    		name := do(ir.OLITERAL, false)
    
    		val := FixValue(typ, r.Value())
    
    		setType(name, typ)
    		setValue(name, val)
    
    			sym = Renameinit()
    
    		}
    		name := do(ir.ONAME, true)
    
    		setType(name, r.signature(sym.Pkg, nil))
    
    
    		name.Func = ir.NewFunc(r.pos())
    		name.Func.Nname = name
    
    
    		if r.hasTypeParams() {
    			name.Func.SetDupok(true)
    
    			if r.dict.shaped {
    				setType(name, shapeSig(name.Func, r.dict))
    			} else {
    				todoDicts = append(todoDicts, func() {
    					r.dict.shapedObj = pr.objIdx(idx, implicits, explicits, true).(*ir.Name)
    				})
    			}
    
    		name := do(ir.OTYPE, true)
    		typ := types.NewNamed(name)
    
    		if r.hasTypeParams() && r.dict.shaped {
    			typ.SetHasShape(true)
    		}
    
    
    		// Important: We need to do this before SetUnderlying.
    
    
    		// We need to defer CheckSize until we've called SetUnderlying to
    		// handle recursive types.
    		types.DeferCheckSize()
    
    		typ.SetUnderlying(r.typWrapped(false))
    
    		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 {
    
    		}
    		if len(methods) != 0 {
    			typ.Methods().Set(methods)
    		}
    
    
    		name := do(ir.ONAME, false)
    
    func (dict *readerDict) mangle(sym *types.Sym) *types.Sym {
    	if !dict.hasTypeParams() {
    
    	// If sym is a locally defined generic type, we need the suffix to
    	// stay at the end after mangling so that types/fmt.go can strip it
    	// out again when writing the type's runtime descriptor (#54456).
    	base, suffix := types.SplitVargenSuffix(sym.Name)
    
    
    	for i, targ := range dict.targs {
    
    	return sym.Pkg.Lookup(buf.String())
    }
    
    
    // shapify returns the shape type for targ.
    
    //
    // If basic is true, then the type argument is used to instantiate a
    // type parameter whose constraint is a basic interface.
    func shapify(targ *types.Type, basic bool) *types.Type {
    
    	base.Assertf(targ.Kind() != types.TFORW, "%v is missing its underlying type", targ)
    
    
    	// When a pointer type is used to instantiate a type parameter
    	// constrained by a basic interface, we know the pointer's element
    	// type can't matter to the generated code. In this case, we can use
    	// an arbitrary pointer type as the shape type. (To match the
    	// non-unified frontend, we use `*byte`.)
    	//
    	// Otherwise, we simply use the type's underlying type as its shape.
    	//
    	// TODO(mdempsky): It should be possible to do much more aggressive
    	// shaping still; e.g., collapsing all pointer-shaped types into a
    	// common type, collapsing scalars of the same size/alignment into a
    	// common type, recursively shaping the element types of composite
    	// types, and discarding struct field names and tags. However, we'll
    	// need to start tracking how type parameters are actually used to
    	// implement some of these optimizations.
    
    	if basic && targ.IsPtr() && !targ.Elem().NotInHeap() {
    		under = types.NewPtr(types.Types[types.TUINT8])
    	}
    
    
    	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, shaped bool) *readerDict {
    
    	r := pr.newReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1)
    
    	nimplicits := r.Len()
    	nexplicits := r.Len()
    
    	if nimplicits > len(implicits) || nexplicits != len(explicits) {
    		base.Fatalf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits))
    
    	dict.targs = append(implicits[:nimplicits:nimplicits], explicits...)
    	dict.implicits = nimplicits
    
    	// Within the compiler, we can just skip over the type parameters.
    	for range dict.targs[dict.implicits:] {
    		// Skip past bounds without actually evaluating them.
    		r.typInfo()
    	}
    
    	dict.derived = make([]derivedInfo, r.Len())
    	dict.derivedTypes = make([]*types.Type, len(dict.derived))
    	for i := range dict.derived {
    		dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()}
    	}
    
    	// Runtime dictionary information; private to the compiler.
    
    
    	// If any type argument is already shaped, then we're constructing a
    	// shaped object, even if not explicitly requested (i.e., calling
    	// objIdx with shaped==true). This can happen with instantiating
    	// types that are referenced within a function body.
    	for _, targ := range dict.targs {
    		if targ.HasShape() {
    			dict.shaped = true
    			break
    		}
    	}
    
    	// And if we're constructing a shaped object, then shapify all type
    	// arguments.
    
    	for i, targ := range dict.targs {
    		basic := r.Bool()
    		if dict.shaped {
    			dict.targs[i] = shapify(targ, basic)
    
    		}
    	}
    
    	dict.baseSym = dict.mangle(sym)
    
    	dict.typeParamMethodExprs = make([]readerMethodExprInfo, r.Len())
    	for i := range dict.typeParamMethodExprs {
    		typeParamIdx := r.Len()
    		_, method := r.selector()
    
    		dict.typeParamMethodExprs[i] = readerMethodExprInfo{typeParamIdx, method}
    	}
    
    	dict.subdicts = make([]objInfo, r.Len())
    	for i := range dict.subdicts {
    		dict.subdicts[i] = r.objInfo()
    	}
    
    	dict.rtypes = make([]typeInfo, r.Len())
    	for i := range dict.rtypes {
    		dict.rtypes[i] = r.typInfo()
    	}
    
    	dict.itabs = make([]itabInfo, r.Len())
    	for i := range dict.itabs {
    		dict.itabs[i] = itabInfo{typ: r.typInfo(), iface: r.typInfo()}
    	}
    
    
    }
    
    func (r *reader) typeParamNames() {
    
    	r.Sync(pkgbits.SyncTypeParamNames)
    
    	for range r.dict.targs[r.dict.implicits:] {
    
    func (r *reader) method(rext *reader) *types.Field {
    
    	r.Sync(pkgbits.SyncMethod)
    
    	pos := r.pos()
    	pkg, sym := r.selector()
    	r.typeParamNames()
    	_, recv := r.param()
    	typ := r.signature(pkg, recv)
    
    
    	name := ir.NewNameAt(pos, ir.MethodSym(recv.Type, sym))
    
    
    	name.Func = ir.NewFunc(r.pos())
    	name.Func.Nname = name
    
    
    	if r.hasTypeParams() {
    		name.Func.SetDupok(true)
    
    		if r.dict.shaped {
    			typ = shapeSig(name.Func, r.dict)
    			setType(name, typ)
    		}
    
    
    	meth := types.NewField(name.Func.Pos(), sym, typ)
    	meth.Nname = name
    
    	meth.SetNointerface(name.Func.Pragma&ir.Nointerface != 0)
    
    
    	return meth
    }
    
    func (r *reader) qualifiedIdent() (pkg *types.Pkg, sym *types.Sym) {
    
    	if name := r.String(); name != "" {
    
    		sym = pkg.Lookup(name)
    	}
    	return
    }
    
    func (r *reader) localIdent() (pkg *types.Pkg, sym *types.Sym) {
    
    	r.Sync(pkgbits.SyncLocalIdent)
    
    	if name := r.String(); name != "" {
    
    		sym = pkg.Lookup(name)
    	}
    	return
    }
    
    func (r *reader) selector() (origPkg *types.Pkg, sym *types.Sym) {
    
    	r.Sync(pkgbits.SyncSelector)
    
    	pkg := origPkg
    	if types.IsExported(name) {
    		pkg = types.LocalPkg
    	}