blob: 785176b3b57b8454f9a4c2bfa76ae5bd870a125d [file] [log] [blame]
// 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"
"go/token"
"go/version"
"internal/buildcfg"
"internal/pkgbits"
"os"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/syntax"
"cmd/compile/internal/types"
"cmd/compile/internal/types2"
)
// This file implements the Unified IR package writer and defines the
// Unified IR export data format.
//
// Low-level coding details (e.g., byte-encoding of individual
// primitive values, or handling element bitstreams and
// cross-references) are handled by internal/pkgbits, so here we only
// concern ourselves with higher-level worries like mapping Go
// language constructs into elements.
// There are two central types in the writing process: the "writer"
// type handles writing out individual elements, while the "pkgWriter"
// type keeps track of which elements have already been created.
//
// For each sort of "thing" (e.g., position, package, object, type)
// that can be written into the export data, there are generally
// several methods that work together:
//
// - writer.thing handles writing out a *use* of a thing, which often
// means writing a relocation to that thing's encoded index.
//
// - pkgWriter.thingIdx handles reserving an index for a thing, and
// writing out any elements needed for the thing.
//
// - writer.doThing handles writing out the *definition* of a thing,
// which in general is a mix of low-level coding primitives (e.g.,
// ints and strings) or uses of other things.
//
// A design goal of Unified IR is to have a single, canonical writer
// implementation, but multiple reader implementations each tailored
// to their respective needs. For example, within cmd/compile's own
// backend, inlining is implemented largely by just re-running the
// function body reading code.
// TODO(mdempsky): Add an importer for Unified IR to the x/tools repo,
// and better document the file format boundary between public and
// private data.
// A pkgWriter constructs Unified IR export data from the results of
// running the types2 type checker on a Go compilation unit.
type pkgWriter struct {
pkgbits.PkgEncoder
m posMap
curpkg *types2.Package
info *types2.Info
// Indices for previously written syntax and types2 things.
posBasesIdx map[*syntax.PosBase]pkgbits.Index
pkgsIdx map[*types2.Package]pkgbits.Index
typsIdx map[types2.Type]pkgbits.Index
objsIdx map[types2.Object]pkgbits.Index
// Maps from types2.Objects back to their syntax.Decl.
funDecls map[*types2.Func]*syntax.FuncDecl
typDecls map[*types2.TypeName]typeDeclGen
// linknames maps package-scope objects to their linker symbol name,
// if specified by a //go:linkname directive.
linknames map[types2.Object]string
// cgoPragmas accumulates any //go:cgo_* pragmas that need to be
// passed through to cmd/link.
cgoPragmas [][]string
}
// newPkgWriter returns an initialized pkgWriter for the specified
// package.
func newPkgWriter(m posMap, pkg *types2.Package, info *types2.Info) *pkgWriter {
return &pkgWriter{
PkgEncoder: pkgbits.NewPkgEncoder(base.Debug.SyncFrames),
m: m,
curpkg: pkg,
info: info,
pkgsIdx: make(map[*types2.Package]pkgbits.Index),
objsIdx: make(map[types2.Object]pkgbits.Index),
typsIdx: make(map[types2.Type]pkgbits.Index),
posBasesIdx: make(map[*syntax.PosBase]pkgbits.Index),
funDecls: make(map[*types2.Func]*syntax.FuncDecl),
typDecls: make(map[*types2.TypeName]typeDeclGen),
linknames: make(map[types2.Object]string),
}
}
// errorf reports a user error about thing p.
func (pw *pkgWriter) errorf(p poser, msg string, args ...interface{}) {
base.ErrorfAt(pw.m.pos(p), 0, msg, args...)
}
// fatalf reports an internal compiler error about thing p.
func (pw *pkgWriter) fatalf(p poser, msg string, args ...interface{}) {
base.FatalfAt(pw.m.pos(p), msg, args...)
}
// unexpected reports a fatal error about a thing of unexpected
// dynamic type.
func (pw *pkgWriter) unexpected(what string, p poser) {
pw.fatalf(p, "unexpected %s: %v (%T)", what, p, p)
}
func (pw *pkgWriter) typeAndValue(x syntax.Expr) syntax.TypeAndValue {
tv, ok := pw.maybeTypeAndValue(x)
if !ok {
pw.fatalf(x, "missing Types entry: %v", syntax.String(x))
}
return tv
}
func (pw *pkgWriter) maybeTypeAndValue(x syntax.Expr) (syntax.TypeAndValue, bool) {
tv := x.GetTypeInfo()
// If x is a generic function whose type arguments are inferred
// from assignment context, then we need to find its inferred type
// in Info.Instances instead.
if name, ok := x.(*syntax.Name); ok {
if inst, ok := pw.info.Instances[name]; ok {
tv.Type = inst.Type
}
}
return tv, tv.Type != nil
}
// typeOf returns the Type of the given value expression.
func (pw *pkgWriter) typeOf(expr syntax.Expr) types2.Type {
tv := pw.typeAndValue(expr)
if !tv.IsValue() {
pw.fatalf(expr, "expected value: %v", syntax.String(expr))
}
return tv.Type
}
// A writer provides APIs for writing out an individual element.
type writer struct {
p *pkgWriter
pkgbits.Encoder
// sig holds the signature for the current function body, if any.
sig *types2.Signature
// TODO(mdempsky): We should be able to prune localsIdx whenever a
// scope closes, and then maybe we can just use the same map for
// storing the TypeParams too (as their TypeName instead).
// localsIdx tracks any local variables declared within this
// function body. It's unused for writing out non-body things.
localsIdx map[*types2.Var]int
// closureVars tracks any free variables that are referenced by this
// function body. It's unused for writing out non-body things.
closureVars []posVar
closureVarsIdx map[*types2.Var]int // index of previously seen free variables
dict *writerDict
// derived tracks whether the type being written out references any
// type parameters. It's unused for writing non-type things.
derived bool
}
// A writerDict tracks types and objects that are used by a declaration.
type writerDict struct {
// implicits is a slice of type parameters from the enclosing
// declarations.
implicits []*types2.TypeParam
// derived is a slice of type indices for computing derived types
// (i.e., types that depend on the declaration's type parameters).
derived []derivedInfo
// 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 == 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 // TODO(mdempsky): Remove.
}
// A typeInfo represents a reference to an encoded Go type.
//
// If derived is true, then the typeInfo represents a generic Go type
// that contains type parameters. In this case, idx is an index into
// the readerDict.derived{,Types} arrays.
//
// Otherwise, the typeInfo represents a non-generic Go type, and idx
// is an index into the reader.typs array instead.
type typeInfo struct {
idx pkgbits.Index
derived bool
}
// An objInfo represents a reference to an encoded, instantiated (if
// applicable) Go object.
type objInfo struct {
idx pkgbits.Index // index for the generic function declaration
explicits []typeInfo // info for the type arguments
}
// 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 {
for _, explicit := range info.explicits {
if explicit.derived {
return true
}
}
return false
}
// equals reports whether info and other represent the same Go object
// (i.e., same base object and identical type arguments, if any).
func (info objInfo) equals(other objInfo) bool {
if info.idx != other.idx {
return false
}
assert(len(info.explicits) == len(other.explicits))
for i, targ := range info.explicits {
if targ != other.explicits[i] {
return false
}
}
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),
p: pw,
}
}
// @@@ Positions
// pos writes the position of p into the element bitstream.
func (w *writer) pos(p poser) {
w.Sync(pkgbits.SyncPos)
pos := p.Pos()
// TODO(mdempsky): Track down the remaining cases here and fix them.
if !w.Bool(pos.IsKnown()) {
return
}
// TODO(mdempsky): Delta encoding.
w.posBase(pos.Base())
w.Uint(pos.Line())
w.Uint(pos.Col())
}
// posBase writes a reference to the given PosBase into the element
// bitstream.
func (w *writer) posBase(b *syntax.PosBase) {
w.Reloc(pkgbits.RelocPosBase, w.p.posBaseIdx(b))
}
// posBaseIdx returns the index for the given PosBase.
func (pw *pkgWriter) posBaseIdx(b *syntax.PosBase) pkgbits.Index {
if idx, ok := pw.posBasesIdx[b]; ok {
return idx
}
w := pw.newWriter(pkgbits.RelocPosBase, pkgbits.SyncPosBase)
w.p.posBasesIdx[b] = w.Idx
w.String(trimFilename(b))
if !w.Bool(b.IsFileBase()) {
w.pos(b)
w.Uint(b.Line())
w.Uint(b.Col())
}
return w.Flush()
}
// @@@ Packages
// 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, idx)
}
// pkgIdx returns the index for the given package, adding it to the
// package export data if needed.
func (pw *pkgWriter) pkgIdx(pkg *types2.Package) pkgbits.Index {
if idx, ok := pw.pkgsIdx[pkg]; ok {
return idx
}
w := pw.newWriter(pkgbits.RelocPkg, pkgbits.SyncPkgDef)
pw.pkgsIdx[pkg] = w.Idx
// The universe and package unsafe need to be handled specially by
// importers anyway, so we serialize them using just their package
// path. This ensures that readers don't confuse them for
// user-defined packages.
switch pkg {
case nil: // universe
w.String("builtin") // same package path used by godoc
case types2.Unsafe:
w.String("unsafe")
default:
// TODO(mdempsky): Write out pkg.Path() for curpkg too.
var path string
if pkg != w.p.curpkg {
path = pkg.Path()
}
base.Assertf(path != "builtin" && path != "unsafe", "unexpected path for user-defined package: %q", path)
w.String(path)
w.String(pkg.Name())
w.Len(len(pkg.Imports()))
for _, imp := range pkg.Imports() {
w.pkg(imp)
}
}
return w.Flush()
}
// @@@ Types
var (
anyTypeName = types2.Universe.Lookup("any").(*types2.TypeName)
comparableTypeName = types2.Universe.Lookup("comparable").(*types2.TypeName)
runeTypeName = types2.Universe.Lookup("rune").(*types2.TypeName)
)
// typ writes a use of the given type into the bitstream.
func (w *writer) typ(typ types2.Type) {
w.typInfo(w.p.typIdx(typ, w.dict))
}
// typInfo writes a use of the given type (specified as a typeInfo
// instead) into the bitstream.
func (w *writer) typInfo(info typeInfo) {
w.Sync(pkgbits.SyncType)
if w.Bool(info.derived) {
w.Len(int(info.idx))
w.derived = true
} else {
w.Reloc(pkgbits.RelocType, info.idx)
}
}
// typIdx returns the index where the export data description of type
// can be read back in. If no such index exists yet, it's created.
//
// typIdx also reports whether typ is a derived type; that is, whether
// its identity depends on type parameters.
func (pw *pkgWriter) typIdx(typ types2.Type, dict *writerDict) typeInfo {
if idx, ok := pw.typsIdx[typ]; ok {
return typeInfo{idx: idx, derived: false}
}
if dict != nil {
if idx, ok := dict.derivedIdx[typ]; ok {
return typeInfo{idx: idx, derived: true}
}
}
w := pw.newWriter(pkgbits.RelocType, pkgbits.SyncTypeIdx)
w.dict = dict
switch typ := typ.(type) {
default:
base.Fatalf("unexpected type: %v (%T)", typ, typ)
case *types2.Basic:
switch kind := typ.Kind(); {
case kind == types2.Invalid:
base.Fatalf("unexpected types2.Invalid")
case types2.Typ[kind] == typ:
w.Code(pkgbits.TypeBasic)
w.Len(int(kind))
default:
// Handle "byte" and "rune" as references to their TypeNames.
obj := types2.Universe.Lookup(typ.Name()).(*types2.TypeName)
assert(obj.Type() == typ)
w.Code(pkgbits.TypeNamed)
w.namedType(obj, nil)
}
case *types2.Named:
w.Code(pkgbits.TypeNamed)
w.namedType(splitNamed(typ))
case *types2.Alias:
w.Code(pkgbits.TypeNamed)
w.namedType(typ.Obj(), nil)
case *types2.TypeParam:
w.derived = true
w.Code(pkgbits.TypeTypeParam)
w.Len(w.dict.typeParamIndex(typ))
case *types2.Array:
w.Code(pkgbits.TypeArray)
w.Uint64(uint64(typ.Len()))
w.typ(typ.Elem())
case *types2.Chan:
w.Code(pkgbits.TypeChan)
w.Len(int(typ.Dir()))
w.typ(typ.Elem())
case *types2.Map:
w.Code(pkgbits.TypeMap)
w.typ(typ.Key())
w.typ(typ.Elem())
case *types2.Pointer:
w.Code(pkgbits.TypePointer)
w.typ(typ.Elem())
case *types2.Signature:
base.Assertf(typ.TypeParams() == nil, "unexpected type params: %v", typ)
w.Code(pkgbits.TypeSignature)
w.signature(typ)
case *types2.Slice:
w.Code(pkgbits.TypeSlice)
w.typ(typ.Elem())
case *types2.Struct:
w.Code(pkgbits.TypeStruct)
w.structType(typ)
case *types2.Interface:
// Handle "any" as reference to its TypeName.
if typ == anyTypeName.Type() {
w.Code(pkgbits.TypeNamed)
w.obj(anyTypeName, nil)
break
}
w.Code(pkgbits.TypeInterface)
w.interfaceType(typ)
case *types2.Union:
w.Code(pkgbits.TypeUnion)
w.unionType(typ)
}
if w.derived {
idx := pkgbits.Index(len(dict.derived))
dict.derived = append(dict.derived, derivedInfo{idx: w.Flush()})
dict.derivedIdx[typ] = idx
return typeInfo{idx: idx, derived: true}
}
pw.typsIdx[typ] = w.Idx
return typeInfo{idx: w.Flush(), derived: false}
}
// namedType writes a use of the given named type into the bitstream.
func (w *writer) namedType(obj *types2.TypeName, targs *types2.TypeList) {
// Named types that are declared within a generic function (and
// thus have implicit type parameters) are always derived types.
if w.p.hasImplicitTypeParams(obj) {
w.derived = true
}
w.obj(obj, targs)
}
func (w *writer) structType(typ *types2.Struct) {
w.Len(typ.NumFields())
for i := 0; i < typ.NumFields(); i++ {
f := typ.Field(i)
w.pos(f)
w.selector(f)
w.typ(f.Type())
w.String(typ.Tag(i))
w.Bool(f.Embedded())
}
}
func (w *writer) unionType(typ *types2.Union) {
w.Len(typ.Len())
for i := 0; i < typ.Len(); i++ {
t := typ.Term(i)
w.Bool(t.Tilde())
w.typ(t.Type())
}
}
func (w *writer) interfaceType(typ *types2.Interface) {
// If typ has no embedded types but it's not a basic interface, then
// the natural description we write out below will fail to
// reconstruct it.
if typ.NumEmbeddeds() == 0 && !typ.IsMethodSet() {
// Currently, this can only happen for the underlying Interface of
// "comparable", which is needed to handle type declarations like
// "type C comparable".
assert(typ == comparableTypeName.Type().(*types2.Named).Underlying())
// Export as "interface{ comparable }".
w.Len(0) // NumExplicitMethods
w.Len(1) // NumEmbeddeds
w.Bool(false) // IsImplicit
w.typ(comparableTypeName.Type()) // EmbeddedType(0)
return
}
w.Len(typ.NumExplicitMethods())
w.Len(typ.NumEmbeddeds())
if typ.NumExplicitMethods() == 0 && typ.NumEmbeddeds() == 1 {
w.Bool(typ.IsImplicit())
} else {
// Implicit interfaces always have 0 explicit methods and 1
// embedded type, so we skip writing out the implicit flag
// otherwise as a space optimization.
assert(!typ.IsImplicit())
}
for i := 0; i < typ.NumExplicitMethods(); i++ {
m := typ.ExplicitMethod(i)
sig := m.Type().(*types2.Signature)
assert(sig.TypeParams() == nil)
w.pos(m)
w.selector(m)
w.signature(sig)
}
for i := 0; i < typ.NumEmbeddeds(); i++ {
w.typ(typ.EmbeddedType(i))
}
}
func (w *writer) signature(sig *types2.Signature) {
w.Sync(pkgbits.SyncSignature)
w.params(sig.Params())
w.params(sig.Results())
w.Bool(sig.Variadic())
}
func (w *writer) params(typ *types2.Tuple) {
w.Sync(pkgbits.SyncParams)
w.Len(typ.Len())
for i := 0; i < typ.Len(); i++ {
w.param(typ.At(i))
}
}
func (w *writer) param(param *types2.Var) {
w.Sync(pkgbits.SyncParam)
w.pos(param)
w.localIdent(param)
w.typ(param.Type())
}
// @@@ Objects
// obj writes a use of the given object into the bitstream.
//
// If obj is a generic object, then explicits are the explicit type
// arguments used to instantiate it (i.e., used to substitute the
// object's own declared type parameters).
func (w *writer) obj(obj types2.Object, explicits *types2.TypeList) {
w.objInfo(w.p.objInstIdx(obj, explicits, w.dict))
}
// objInfo writes a use of the given encoded object into the
// bitstream.
func (w *writer) objInfo(info objInfo) {
w.Sync(pkgbits.SyncObject)
w.Bool(false) // TODO(mdempsky): Remove; was derived func inst.
w.Reloc(pkgbits.RelocObj, info.idx)
w.Len(len(info.explicits))
for _, info := range info.explicits {
w.typInfo(info)
}
}
// objInstIdx returns the indices for an object and a corresponding
// list of type arguments used to instantiate it, adding them to the
// export data as needed.
func (pw *pkgWriter) objInstIdx(obj types2.Object, explicits *types2.TypeList, dict *writerDict) objInfo {
explicitInfos := make([]typeInfo, explicits.Len())
for i := range explicitInfos {
explicitInfos[i] = pw.typIdx(explicits.At(i), dict)
}
return objInfo{idx: pw.objIdx(obj), explicits: explicitInfos}
}
// objIdx returns the index for the given Object, adding it to the
// export data as needed.
func (pw *pkgWriter) objIdx(obj types2.Object) pkgbits.Index {
// TODO(mdempsky): Validate that obj is a global object (or a local
// defined type, which we hoist to global scope anyway).
if idx, ok := pw.objsIdx[obj]; ok {
return idx
}
dict := &writerDict{
derivedIdx: make(map[types2.Type]pkgbits.Index),
}
if isDefinedType(obj) && obj.Pkg() == pw.curpkg {
decl, ok := pw.typDecls[obj.(*types2.TypeName)]
assert(ok)
dict.implicits = decl.implicits
}
// We encode objects into 4 elements across different sections, all
// sharing the same index:
//
// - RelocName has just the object's qualified name (i.e.,
// Object.Pkg and Object.Name) and the CodeObj indicating what
// specific type of Object it is (Var, Func, etc).
//
// - RelocObj has the remaining public details about the object,
// relevant to go/types importers.
//
// - RelocObjExt has additional private details about the object,
// which are only relevant to cmd/compile itself. This is
// separated from RelocObj so that go/types importers are
// unaffected by internal compiler changes.
//
// - RelocObjDict has public details about the object's type
// parameters and derived type's used by the object. This is
// separated to facilitate the eventual introduction of
// shape-based stenciling.
//
// TODO(mdempsky): Re-evaluate whether RelocName still makes sense
// to keep separate from RelocObj.
w := pw.newWriter(pkgbits.RelocObj, pkgbits.SyncObject1)
wext := pw.newWriter(pkgbits.RelocObjExt, pkgbits.SyncObject1)
wname := pw.newWriter(pkgbits.RelocName, pkgbits.SyncObject1)
wdict := pw.newWriter(pkgbits.RelocObjDict, pkgbits.SyncObject1)
pw.objsIdx[obj] = w.Idx // break cycles
assert(wext.Idx == w.Idx)
assert(wname.Idx == w.Idx)
assert(wdict.Idx == w.Idx)
w.dict = dict
wext.dict = dict
code := w.doObj(wext, obj)
w.Flush()
wext.Flush()
wname.qualifiedIdent(obj)
wname.Code(code)
wname.Flush()
wdict.objDict(obj, w.dict)
wdict.Flush()
return w.Idx
}
// doObj writes the RelocObj definition for obj to w, and the
// RelocObjExt definition to wext.
func (w *writer) doObj(wext *writer, obj types2.Object) pkgbits.CodeObj {
if obj.Pkg() != w.p.curpkg {
return pkgbits.ObjStub
}
switch obj := obj.(type) {
default:
w.p.unexpected("object", obj)
panic("unreachable")
case *types2.Const:
w.pos(obj)
w.typ(obj.Type())
w.Value(obj.Val())
return pkgbits.ObjConst
case *types2.Func:
decl, ok := w.p.funDecls[obj]
assert(ok)
sig := obj.Type().(*types2.Signature)
w.pos(obj)
w.typeParamNames(sig.TypeParams())
w.signature(sig)
w.pos(decl)
wext.funcExt(obj)
return pkgbits.ObjFunc
case *types2.TypeName:
if obj.IsAlias() {
w.pos(obj)
t := obj.Type()
if alias, ok := t.(*types2.Alias); ok { // materialized alias
t = alias.Rhs()
}
w.typ(t)
return pkgbits.ObjAlias
}
named := obj.Type().(*types2.Named)
assert(named.TypeArgs() == nil)
w.pos(obj)
w.typeParamNames(named.TypeParams())
wext.typeExt(obj)
w.typ(named.Underlying())
w.Len(named.NumMethods())
for i := 0; i < named.NumMethods(); i++ {
w.method(wext, named.Method(i))
}
return pkgbits.ObjType
case *types2.Var:
w.pos(obj)
w.typ(obj.Type())
wext.varExt(obj)
return pkgbits.ObjVar
}
}
// objDict writes the dictionary needed for reading the given object.
func (w *writer) objDict(obj types2.Object, dict *writerDict) {
// TODO(mdempsky): Split objDict into multiple entries? reader.go
// doesn't care about the type parameter bounds, and reader2.go
// doesn't care about referenced functions.
w.dict = dict // TODO(mdempsky): This is a bit sketchy.
w.Len(len(dict.implicits))
tparams := objTypeParams(obj)
ntparams := tparams.Len()
w.Len(ntparams)
for i := 0; i < ntparams; i++ {
w.typ(tparams.At(i).Constraint())
}
nderived := len(dict.derived)
w.Len(nderived)
for _, typ := range dict.derived {
w.Reloc(pkgbits.RelocType, typ.idx)
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.)
// For each type parameter, write out whether the constraint is a
// basic interface. This is used to determine how aggressively we
// can shape corresponding type arguments.
//
// This is somewhat redundant with writing out the full type
// parameter constraints above, but the compiler currently skips
// over those. Also, we don't care about the *declared* constraints,
// but how the type parameters are actually *used*. E.g., if a type
// parameter is constrained to `int | uint` but then never used in
// arithmetic/conversions/etc, we could shape those together.
for _, implicit := range dict.implicits {
w.Bool(implicit.Underlying().(*types2.Interface).IsMethodSet())
}
for i := 0; i < ntparams; i++ {
tparam := tparams.At(i)
w.Bool(tparam.Underlying().(*types2.Interface).IsMethodSet())
}
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)
}
func (w *writer) typeParamNames(tparams *types2.TypeParamList) {
w.Sync(pkgbits.SyncTypeParamNames)
ntparams := tparams.Len()
for i := 0; i < ntparams; i++ {
tparam := tparams.At(i).Obj()
w.pos(tparam)
w.localIdent(tparam)
}
}
func (w *writer) method(wext *writer, meth *types2.Func) {
decl, ok := w.p.funDecls[meth]
assert(ok)
sig := meth.Type().(*types2.Signature)
w.Sync(pkgbits.SyncMethod)
w.pos(meth)
w.selector(meth)
w.typeParamNames(sig.RecvTypeParams())
w.param(sig.Recv())
w.signature(sig)
w.pos(decl) // XXX: Hack to workaround linker limitations.
wext.funcExt(meth)
}
// qualifiedIdent writes out the name of an object declared at package
// scope. (For now, it's also used to refer to local defined types.)
func (w *writer) qualifiedIdent(obj types2.Object) {
w.Sync(pkgbits.SyncSym)
name := obj.Name()
if isDefinedType(obj) && obj.Pkg() == w.p.curpkg {
decl, ok := w.p.typDecls[obj.(*types2.TypeName)]
assert(ok)
if decl.gen != 0 {
// For local defined types, we embed a scope-disambiguation
// number directly into their name. types.SplitVargenSuffix then
// knows to look for this.
//
// TODO(mdempsky): Find a better solution; this is terrible.
name = fmt.Sprintf("%s·%v", name, decl.gen)
}
}
w.pkg(obj.Pkg())
w.String(name)
}
// TODO(mdempsky): We should be able to omit pkg from both localIdent
// and selector, because they should always be known from context.
// However, past frustrations with this optimization in iexport make
// me a little nervous to try it again.
// localIdent writes the name of a locally declared object (i.e.,
// objects that can only be accessed by non-qualified name, within the
// context of a particular function).
func (w *writer) localIdent(obj types2.Object) {
assert(!isGlobal(obj))
w.Sync(pkgbits.SyncLocalIdent)
w.pkg(obj.Pkg())
w.String(obj.Name())
}
// 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.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
func (w *writer) funcExt(obj *types2.Func) {
decl, ok := w.p.funDecls[obj]
assert(ok)
// TODO(mdempsky): Extend these pragma validation flags to account
// for generics. E.g., linkname probably doesn't make sense at
// least.
pragma := asPragmaFlag(decl.Pragma)
if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
}
wi := asWasmImport(decl.Pragma)
if decl.Body != nil {
if pragma&ir.Noescape != 0 {
w.p.errorf(decl, "can only use //go:noescape with external func implementations")
}
if wi != nil {
w.p.errorf(decl, "can only use //go:wasmimport with external func implementations")
}
if (pragma&ir.UintptrKeepAlive != 0 && pragma&ir.UintptrEscapes == 0) && pragma&ir.Nosplit == 0 {
// Stack growth can't handle uintptr arguments that may
// be pointers (as we don't know which are pointers
// when creating the stack map). Thus uintptrkeepalive
// functions (and all transitive callees) must be
// nosplit.
//
// N.B. uintptrescapes implies uintptrkeepalive but it
// is OK since the arguments must escape to the heap.
//
// TODO(prattmic): Add recursive nosplit check of callees.
// TODO(prattmic): Functions with no body (i.e.,
// assembly) must also be nosplit, but we can't check
// that here.
w.p.errorf(decl, "go:uintptrkeepalive requires go:nosplit")
}
} else {
if base.Flag.Complete || decl.Name.Value == "init" {
// Linknamed functions are allowed to have no body. Hopefully
// the linkname target has a body. See issue 23311.
// Wasmimport functions are also allowed to have no body.
if _, ok := w.p.linknames[obj]; !ok && wi == nil {
w.p.errorf(decl, "missing function body")
}
}
}
sig, block := obj.Type().(*types2.Signature), decl.Body
body, closureVars := w.p.bodyIdx(sig, block, w.dict)
if len(closureVars) > 0 {
fmt.Fprintln(os.Stderr, "CLOSURE", closureVars)
}
assert(len(closureVars) == 0)
w.Sync(pkgbits.SyncFuncExt)
w.pragmaFlag(pragma)
w.linkname(obj)
if buildcfg.GOARCH == "wasm" {
if wi != nil {
w.String(wi.Module)
w.String(wi.Name)
} else {
w.String("")
w.String("")
}
}
w.Bool(false) // stub extension
w.Reloc(pkgbits.RelocBody, body)
w.Sync(pkgbits.SyncEOF)
}
func (w *writer) typeExt(obj *types2.TypeName) {
decl, ok := w.p.typDecls[obj]
assert(ok)
w.Sync(pkgbits.SyncTypeExt)
w.pragmaFlag(asPragmaFlag(decl.Pragma))
// No LSym.SymIdx info yet.
w.Int64(-1)
w.Int64(-1)
}
func (w *writer) varExt(obj *types2.Var) {
w.Sync(pkgbits.SyncVarExt)
w.linkname(obj)
}
func (w *writer) linkname(obj types2.Object) {
w.Sync(pkgbits.SyncLinkname)
w.Int64(-1)
w.String(w.p.linknames[obj])
}
func (w *writer) pragmaFlag(p ir.PragmaFlag) {
w.Sync(pkgbits.SyncPragma)
w.Int(int(p))
}
// @@@ Function bodies
// bodyIdx returns the index for the given function body (specified by
// block), adding it to the export data
func (pw *pkgWriter) bodyIdx(sig *types2.Signature, block *syntax.BlockStmt, dict *writerDict) (idx pkgbits.Index, closureVars []posVar) {
w := pw.newWriter(pkgbits.RelocBody, pkgbits.SyncFuncBody)
w.sig = sig
w.dict = dict
w.declareParams(sig)
if w.Bool(block != nil) {
w.stmts(block.List)
w.pos(block.Rbrace)
}
return w.Flush(), w.closureVars
}
func (w *writer) declareParams(sig *types2.Signature) {
addLocals := func(params *types2.Tuple) {
for i := 0; i < params.Len(); i++ {
w.addLocal(params.At(i))
}
}
if recv := sig.Recv(); recv != nil {
w.addLocal(recv)
}
addLocals(sig.Params())
addLocals(sig.Results())
}
// addLocal records the declaration of a new local variable.
func (w *writer) addLocal(obj *types2.Var) {
idx := len(w.localsIdx)
w.Sync(pkgbits.SyncAddLocal)
if w.p.SyncMarkers() {
w.Int(idx)
}
w.varDictIndex(obj)
if w.localsIdx == nil {
w.localsIdx = make(map[*types2.Var]int)
}
w.localsIdx[obj] = idx
}
// useLocal writes a reference to the given local or free variable
// into the bitstream.
func (w *writer) useLocal(pos syntax.Pos, obj *types2.Var) {
w.Sync(pkgbits.SyncUseObjLocal)
if idx, ok := w.localsIdx[obj]; w.Bool(ok) {
w.Len(idx)
return
}
idx, ok := w.closureVarsIdx[obj]
if !ok {
if w.closureVarsIdx == nil {
w.closureVarsIdx = make(map[*types2.Var]int)
}
idx = len(w.closureVars)
w.closureVars = append(w.closureVars, posVar{pos, obj})
w.closureVarsIdx[obj] = idx
}
w.Len(idx)
}
func (w *writer) openScope(pos syntax.Pos) {
w.Sync(pkgbits.SyncOpenScope)
w.pos(pos)
}
func (w *writer) closeScope(pos syntax.Pos) {
w.Sync(pkgbits.SyncCloseScope)
w.pos(pos)
w.closeAnotherScope()
}
func (w *writer) closeAnotherScope() {
w.Sync(pkgbits.SyncCloseAnotherScope)
}
// @@@ Statements
// stmt writes the given statement into the function body bitstream.
func (w *writer) stmt(stmt syntax.Stmt) {
var stmts []syntax.Stmt
if stmt != nil {
stmts = []syntax.Stmt{stmt}
}
w.stmts(stmts)
}
func (w *writer) stmts(stmts []syntax.Stmt) {
dead := false
w.Sync(pkgbits.SyncStmts)
var lastLabel = -1
for i, stmt := range stmts {
if _, ok := stmt.(*syntax.LabeledStmt); ok {
lastLabel = i
}
}
for i, stmt := range stmts {
if dead && i > lastLabel {
// Any statements after a terminating and last label statement are safe to omit.
// Otherwise, code after label statement may refer to dead stmts between terminating
// and label statement, see issue #65593.
if _, ok := stmt.(*syntax.LabeledStmt); !ok {
continue
}
}
w.stmt1(stmt)
dead = w.p.terminates(stmt)
}
w.Code(stmtEnd)
w.Sync(pkgbits.SyncStmtsEnd)
}
func (w *writer) stmt1(stmt syntax.Stmt) {
switch stmt := stmt.(type) {
default:
w.p.unexpected("statement", stmt)
case nil, *syntax.EmptyStmt:
return
case *syntax.AssignStmt:
switch {
case stmt.Rhs == nil:
w.Code(stmtIncDec)
w.op(binOps[stmt.Op])
w.expr(stmt.Lhs)
w.pos(stmt)
case stmt.Op != 0 && stmt.Op != syntax.Def:
w.Code(stmtAssignOp)
w.op(binOps[stmt.Op])
w.expr(stmt.Lhs)
w.pos(stmt)
var typ types2.Type
if stmt.Op != syntax.Shl && stmt.Op != syntax.Shr {
typ = w.p.typeOf(stmt.Lhs)
}
w.implicitConvExpr(typ, stmt.Rhs)
default:
w.assignStmt(stmt, stmt.Lhs, stmt.Rhs)
}
case *syntax.BlockStmt:
w.Code(stmtBlock)
w.blockStmt(stmt)
case *syntax.BranchStmt:
w.Code(stmtBranch)
w.pos(stmt)
w.op(branchOps[stmt.Tok])
w.optLabel(stmt.Label)
case *syntax.CallStmt:
w.Code(stmtCall)
w.pos(stmt)
w.op(callOps[stmt.Tok])
w.expr(stmt.Call)
if stmt.Tok == syntax.Defer {
w.optExpr(stmt.DeferAt)
}
case *syntax.DeclStmt:
for _, decl := range stmt.DeclList {
w.declStmt(decl)
}
case *syntax.ExprStmt:
w.Code(stmtExpr)
w.expr(stmt.X)
case *syntax.ForStmt:
w.Code(stmtFor)
w.forStmt(stmt)
case *syntax.IfStmt:
w.Code(stmtIf)
w.ifStmt(stmt)
case *syntax.LabeledStmt:
w.Code(stmtLabel)
w.pos(stmt)
w.label(stmt.Label)
w.stmt1(stmt.Stmt)
case *syntax.ReturnStmt:
w.Code(stmtReturn)
w.pos(stmt)
resultTypes := w.sig.Results()
dstType := func(i int) types2.Type {
return resultTypes.At(i).Type()
}
w.multiExpr(stmt, dstType, syntax.UnpackListExpr(stmt.Results))
case *syntax.SelectStmt:
w.Code(stmtSelect)
w.selectStmt(stmt)
case *syntax.SendStmt:
chanType := types2.CoreType(w.p.typeOf(stmt.Chan)).(*types2.Chan)
w.Code(stmtSend)
w.pos(stmt)
w.expr(stmt.Chan)
w.implicitConvExpr(chanType.Elem(), stmt.Value)
case *syntax.SwitchStmt:
w.Code(stmtSwitch)
w.switchStmt(stmt)
}
}
func (w *writer) assignList(expr syntax.Expr) {
exprs := syntax.UnpackListExpr(expr)
w.Len(len(exprs))
for _, expr := range exprs {
w.assign(expr)
}
}
func (w *writer) assign(expr syntax.Expr) {
expr = syntax.Unparen(expr)
if name, ok := expr.(*syntax.Name); ok {
if name.Value == "_" {
w.Code(assignBlank)
return
}
if obj, ok := w.p.info.Defs[name]; ok {
obj := obj.(*types2.Var)
w.Code(assignDef)
w.pos(obj)
w.localIdent(obj)
w.typ(obj.Type())
// TODO(mdempsky): Minimize locals index size by deferring
// this until the variables actually come into scope.
w.addLocal(obj)
return
}
}
w.Code(assignExpr)
w.expr(expr)
}
func (w *writer) declStmt(decl syntax.Decl) {
switch decl := decl.(type) {
default:
w.p.unexpected("declaration", decl)
case *syntax.ConstDecl, *syntax.TypeDecl:
case *syntax.VarDecl:
w.assignStmt(decl, namesAsExpr(decl.NameList), decl.Values)
}
}
// assignStmt writes out an assignment for "lhs = rhs".
func (w *writer) assignStmt(pos poser, lhs0, rhs0 syntax.Expr) {
lhs := syntax.UnpackListExpr(lhs0)
rhs := syntax.UnpackListExpr(rhs0)
w.Code(stmtAssign)
w.pos(pos)
// As if w.assignList(lhs0).
w.Len(len(lhs))
for _, expr := range lhs {
w.assign(expr)
}
dstType := func(i int) types2.Type {
dst := lhs[i]
// Finding dstType is somewhat involved, because for VarDecl
// statements, the Names are only added to the info.{Defs,Uses}
// maps, not to info.Types.
if name, ok := syntax.Unparen(dst).(*syntax.Name); ok {
if name.Value == "_" {
return nil // ok: no implicit conversion
} else if def, ok := w.p.info.Defs[name].(*types2.Var); ok {
return def.Type()
} else if use, ok := w.p.info.Uses[name].(*types2.Var); ok {
return use.Type()
} else {
w.p.fatalf(dst, "cannot find type of destination object: %v", dst)
}
}
return w.p.typeOf(dst)
}
w.multiExpr(pos, dstType, rhs)
}
func (w *writer) blockStmt(stmt *syntax.BlockStmt) {
w.Sync(pkgbits.SyncBlockStmt)
w.openScope(stmt.Pos())
w.stmts(stmt.List)
w.closeScope(stmt.Rbrace)
}
func (w *writer) forStmt(stmt *syntax.ForStmt) {
w.Sync(pkgbits.SyncForStmt)
w.openScope(stmt.Pos())
if rang, ok := stmt.Init.(*syntax.RangeClause); w.Bool(ok) {
w.pos(rang)
w.assignList(rang.Lhs)
w.expr(rang.X)
xtyp := w.p.typeOf(rang.X)
if _, isMap := types2.CoreType(xtyp).(*types2.Map); isMap {
w.rtype(xtyp)
}
{
lhs := syntax.UnpackListExpr(rang.Lhs)
assign := func(i int, src types2.Type) {
if i >= len(lhs) {
return
}
dst := syntax.Unparen(lhs[i])
if name, ok := dst.(*syntax.Name); ok && name.Value == "_" {
return
}
var dstType types2.Type
if rang.Def {
// For `:=` assignments, the LHS names only appear in Defs,
// not Types (as used by typeOf).
dstType = w.p.info.Defs[dst.(*syntax.Name)].(*types2.Var).Type()
} else {
dstType = w.p.typeOf(dst)
}
w.convRTTI(src, dstType)
}
keyType, valueType := types2.RangeKeyVal(w.p.typeOf(rang.X))
assign(0, keyType)
assign(1, valueType)
}
} else {
if stmt.Cond != nil && w.p.staticBool(&stmt.Cond) < 0 { // always false
stmt.Post = nil
stmt.Body.List = nil
}
w.pos(stmt)
w.stmt(stmt.Init)
w.optExpr(stmt.Cond)
w.stmt(stmt.Post)
}
w.blockStmt(stmt.Body)
w.Bool(w.distinctVars(stmt))
w.closeAnotherScope()
}
func (w *writer) distinctVars(stmt *syntax.ForStmt) bool {
lv := base.Debug.LoopVar
fileVersion := w.p.info.FileVersions[stmt.Pos().Base()]
is122 := fileVersion == "" || version.Compare(fileVersion, "go1.22") >= 0
// Turning off loopvar for 1.22 is only possible with loopvarhash=qn
//
// Debug.LoopVar values to be preserved for 1.21 compatibility are 1 and 2,
// which are also set (=1) by GOEXPERIMENT=loopvar. The knobs for turning on
// the new, unshared, loopvar behavior apply to versions less than 1.21 because
// (1) 1.21 also did that and (2) this is believed to be the likely use case;
// anyone checking to see if it affects their code will just run the GOEXPERIMENT
// but will not also update all their go.mod files to 1.21.
//
// -gcflags=-d=loopvar=3 enables logging for 1.22 but does not turn loopvar on for <= 1.21.
return is122 || lv > 0 && lv != 3
}
func (w *writer) ifStmt(stmt *syntax.IfStmt) {
cond := w.p.staticBool(&stmt.Cond)
w.Sync(pkgbits.SyncIfStmt)
w.openScope(stmt.Pos())
w.pos(stmt)
w.stmt(stmt.Init)
w.expr(stmt.Cond)
w.Int(cond)
if cond >= 0 {
w.blockStmt(stmt.Then)
} else {
w.pos(stmt.Then.Rbrace)
}
if cond <= 0 {
w.stmt(stmt.Else)
}
w.closeAnotherScope()
}
func (w *writer) selectStmt(stmt *syntax.SelectStmt) {
w.Sync(pkgbits.SyncSelectStmt)
w.pos(stmt)
w.Len(len(stmt.Body))
for i, clause := range stmt.Body {
if i > 0 {
w.closeScope(clause.Pos())
}
w.openScope(clause.Pos())
w.pos(clause)
w.stmt(clause.Comm)
w.stmts(clause.Body)
}
if len(stmt.Body) > 0 {
w.closeScope(stmt.Rbrace)
}
}
func (w *writer) switchStmt(stmt *syntax.SwitchStmt) {
w.Sync(pkgbits.SyncSwitchStmt)
w.openScope(stmt.Pos())
w.pos(stmt)
w.stmt(stmt.Init)
var iface, tagType types2.Type
if guard, ok := stmt.Tag.(*syntax.TypeSwitchGuard); w.Bool(ok) {
iface = w.p.typeOf(guard.X)
w.pos(guard)
if tag := guard.Lhs; w.Bool(tag != nil) {
w.pos(tag)
// Like w.localIdent, but we don't have a types2.Object.
w.Sync(pkgbits.SyncLocalIdent)
w.pkg(w.p.curpkg)
w.String(tag.Value)
}
w.expr(guard.X)
} else {
tag := stmt.Tag
var tagValue constant.Value
if tag != nil {
tv := w.p.typeAndValue(tag)
tagType = tv.Type
tagValue = tv.Value
} else {
tagType = types2.Typ[types2.Bool]
tagValue = constant.MakeBool(true)
}
if tagValue != nil {
// If the switch tag has a constant value, look for a case
// clause that we always branch to.
func() {
var target *syntax.CaseClause
Outer:
for _, clause := range stmt.Body {
if clause.Cases == nil {
target = clause
}
for _, cas := range syntax.UnpackListExpr(clause.Cases) {
tv := w.p.typeAndValue(cas)
if tv.Value == nil {
return // non-constant case; give up
}
if constant.Compare(tagValue, token.EQL, tv.Value) {
target = clause
break Outer
}
}
}
// We've found the target clause, if any.
if target != nil {
if hasFallthrough(target.Body) {
return // fallthrough is tricky; give up
}
// Rewrite as single "default" case.
target.Cases = nil
stmt.Body = []*syntax.CaseClause{target}
} else {
stmt.Body = nil
}
// Clear switch tag (i.e., replace with implicit "true").
tag = nil
stmt.Tag = nil
tagType = types2.Typ[types2.Bool]
}()
}
// Walk is going to emit comparisons between the tag value and
// each case expression, and we want these comparisons to always
// have the same type. If there are any case values that can't be
// converted to the tag value's type, then convert everything to
// `any` instead.
Outer:
for _, clause := range stmt.Body {
for _, cas := range syntax.UnpackListExpr(clause.Cases) {
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
tagType = types2.NewInterfaceType(nil, nil)
break Outer
}
}
}
if w.Bool(tag != nil) {
w.implicitConvExpr(tagType, tag)
}
}
w.Len(len(stmt.Body))
for i, clause := range stmt.Body {
if i > 0 {
w.closeScope(clause.Pos())
}
w.openScope(clause.Pos())
w.pos(clause)
cases := syntax.UnpackListExpr(clause.Cases)
if iface != nil {
w.Len(len(cases))
for _, cas := range cases {
if w.Bool(isNil(w.p, cas)) {
continue
}
w.exprType(iface, cas)
}
} else {
// As if w.exprList(clause.Cases),
// but with implicit conversions to tagType.
w.Sync(pkgbits.SyncExprList)
w.Sync(pkgbits.SyncExprs)
w.Len(len(cases))
for _, cas := range cases {
w.implicitConvExpr(tagType, cas)
}
}
if obj, ok := w.p.info.Implicits[clause]; ok {
// TODO(mdempsky): These pos details are quirkish, but also
// necessary so the variable's position is correct for DWARF
// scope assignment later. It would probably be better for us to
// instead just set the variable's DWARF scoping info earlier so
// we can give it the correct position information.
pos := clause.Pos()
if typs := syntax.UnpackListExpr(clause.Cases); len(typs) != 0 {
pos = typeExprEndPos(typs[len(typs)-1])
}
w.pos(pos)
obj := obj.(*types2.Var)
w.typ(obj.Type())
w.addLocal(obj)
}
w.stmts(clause.Body)
}
if len(stmt.Body) > 0 {
w.closeScope(stmt.Rbrace)
}
w.closeScope(stmt.Rbrace)
}
func (w *writer) label(label *syntax.Name) {
w.Sync(pkgbits.SyncLabel)
// TODO(mdempsky): Replace label strings with dense indices.
w.String(label.Value)
}
func (w *writer) optLabel(label *syntax.Name) {
w.Sync(pkgbits.SyncOptLabel)
if w.Bool(label != nil) {
w.label(label)
}
}
// @@@ Expressions
// expr writes the given expression into the function body bitstream.
func (w *writer) expr(expr syntax.Expr) {
base.Assertf(expr != nil, "missing expression")
expr = syntax.Unparen(expr) // skip parens; unneeded after typecheck
obj, inst := lookupObj(w.p, expr)
targs := inst.TypeArgs
if tv, ok := w.p.maybeTypeAndValue(expr); ok {
if tv.IsRuntimeHelper() {
if pkg := obj.Pkg(); pkg != nil && pkg.Name() == "runtime" {
objName := obj.Name()
w.Code(exprRuntimeBuiltin)
w.String(objName)
return
}
}
if tv.IsType() {
w.p.fatalf(expr, "unexpected type expression %v", syntax.String(expr))
}
if tv.Value != nil {
w.Code(exprConst)
w.pos(expr)
typ := idealType(tv)
assert(typ != nil)
w.typ(typ)
w.Value(tv.Value)
return
}
if _, isNil := obj.(*types2.Nil); isNil {
w.Code(exprZero)
w.pos(expr)
w.typ(tv.Type)
return
}
// With shape types (and particular pointer shaping), we may have
// an expression of type "go.shape.*uint8", but need to reshape it
// to another shape-identical type to allow use in field
// selection, indexing, etc.
if typ := tv.Type; !tv.IsBuiltin() && !isTuple(typ) && !isUntyped(typ) {
w.Code(exprReshape)
w.typ(typ)
// fallthrough
}
}
if obj != nil {
if targs.Len() != 0 {
obj := obj.(*types2.Func)
w.Code(exprFuncInst)
w.pos(expr)
w.funcInst(obj, targs)
return
}
if isGlobal(obj) {
w.Code(exprGlobal)
w.obj(obj, nil)
return
}
obj := obj.(*types2.Var)
assert(!obj.IsField())
w.Code(exprLocal)
w.useLocal(expr.Pos(), obj)
return
}
switch expr := expr.(type) {
default:
w.p.unexpected("expression", expr)
case *syntax.CompositeLit:
w.Code(exprCompLit)
w.compLit(expr)
case *syntax.FuncLit:
w.Code(exprFuncLit)
w.funcLit(expr)
case *syntax.SelectorExpr:
sel, ok := w.p.info.Selections[expr]
assert(ok)
switch sel.Kind() {
default:
w.p.fatalf(expr, "unexpected selection kind: %v", sel.Kind())
case types2.FieldVal:
w.Code(exprFieldVal)
w.expr(expr.X)
w.pos(expr)
w.selector(sel.Obj())
case types2.MethodVal:
w.Code(exprMethodVal)
typ := w.recvExpr(expr, sel)
w.pos(expr)
w.methodExpr(expr, typ, sel)
case types2.MethodExpr:
w.Code(exprMethodExpr)
tv := w.p.typeAndValue(expr.X)
assert(tv.IsType())
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.methodExpr(expr, typ, sel)
}
case *syntax.IndexExpr:
_ = w.p.typeOf(expr.Index) // ensure this is an index expression, not an instantiation
xtyp := w.p.typeOf(expr.X)
var keyType types2.Type
if mapType, ok := types2.CoreType(xtyp).(*types2.Map); ok {
keyType = mapType.Key()
}
w.Code(exprIndex)
w.expr(expr.X)
w.pos(expr)
w.implicitConvExpr(keyType, expr.Index)
if keyType != nil {
w.rtype(xtyp)
}
case *syntax.SliceExpr:
w.Code(exprSlice)
w.expr(expr.X)
w.pos(expr)
for _, n := range &expr.Index {
w.optExpr(n)
}
case *syntax.AssertExpr:
iface := w.p.typeOf(expr.X)
w.Code(exprAssert)
w.expr(expr.X)
w.pos(expr)
w.exprType(iface, expr.Type)
w.rtype(iface)
case *syntax.Operation:
if expr.Y == nil {
w.Code(exprUnaryOp)
w.op(unOps[expr.Op])
w.pos(expr)
w.expr(expr.X)
break
}
var commonType types2.Type
switch expr.Op {
case syntax.Shl, syntax.Shr:
// ok: operands are allowed to have different types
default:
xtyp := w.p.typeOf(expr.X)
ytyp := w.p.typeOf(expr.Y)
switch {
case types2.AssignableTo(xtyp, ytyp):
commonType = ytyp
case types2.AssignableTo(ytyp, xtyp):
commonType = xtyp
default:
w.p.fatalf(expr, "failed to find common type between %v and %v", xtyp, ytyp)
}
}
w.Code(exprBinaryOp)
w.op(binOps[expr.Op])
w.implicitConvExpr(commonType, expr.X)
w.pos(expr)
w.implicitConvExpr(commonType, expr.Y)
case *syntax.CallExpr:
tv := w.p.typeAndValue(expr.Fun)
if tv.IsType() {
assert(len(expr.ArgList) == 1)
assert(!expr.HasDots)
w.convertExpr(tv.Type, expr.ArgList[0], false)
break
}
var rtype types2.Type
if tv.IsBuiltin() {
switch obj, _ := lookupObj(w.p, syntax.Unparen(expr.Fun)); obj.Name() {
case "make":
assert(len(expr.ArgList) >= 1)
assert(!expr.HasDots)
w.Code(exprMake)
w.pos(expr)
w.exprType(nil, expr.ArgList[0])
w.exprs(expr.ArgList[1:])
typ := w.p.typeOf(expr)
switch coreType := types2.CoreType(typ).(type) {
default:
w.p.fatalf(expr, "unexpected core type: %v", coreType)
case *types2.Chan:
w.rtype(typ)
case *types2.Map:
w.rtype(typ)
case *types2.Slice:
w.rtype(sliceElem(typ))
}
return
case "new":
assert(len(expr.ArgList) == 1)
assert(!expr.HasDots)
w.Code(exprNew)
w.pos(expr)
w.exprType(nil, expr.ArgList[0])
return
case "Sizeof":
assert(len(expr.ArgList) == 1)
assert(!expr.HasDots)
w.Code(exprSizeof)
w.pos(expr)
w.typ(w.p.typeOf(expr.ArgList[0]))
return
case "Alignof":
assert(len(expr.ArgList) == 1)
assert(!expr.HasDots)
w.Code(exprAlignof)
w.pos(expr)
w.typ(w.p.typeOf(expr.ArgList[0]))
return
case "Offsetof":
assert(len(expr.ArgList) == 1)
assert(!expr.HasDots)
selector := syntax.Unparen(expr.ArgList[0]).(*syntax.SelectorExpr)
index := w.p.info.Selections[selector].Index()
w.Code(exprOffsetof)
w.pos(expr)
w.typ(deref2(w.p.typeOf(selector.X)))
w.Len(len(index) - 1)
for _, idx := range index {
w.Len(idx)
}
return
case "append":
rtype = sliceElem(w.p.typeOf(expr))
case "copy":
typ := w.p.typeOf(expr.ArgList[0])
if tuple, ok := typ.(*types2.Tuple); ok { // "copy(g())"
typ = tuple.At(0).Type()
}
rtype = sliceElem(typ)
case "delete":
typ := w.p.typeOf(expr.ArgList[0])
if tuple, ok := typ.(*types2.Tuple); ok { // "delete(g())"
typ = tuple.At(0).Type()
}
rtype = typ
case "Slice":
rtype = sliceElem(w.p.typeOf(expr))
}
}
writeFunExpr := func() {
fun := syntax.Unparen(expr.Fun)
if selector, ok := fun.(*syntax.SelectorExpr); ok {
if sel, ok := w.p.info.Selections[selector]; ok && sel.Kind() == types2.MethodVal {
w.Bool(true) // method call
typ := w.recvExpr(selector, sel)
w.methodExpr(selector, typ, sel)
return
}
}
w.Bool(false) // not a method call (i.e., normal function call)
if obj, inst := lookupObj(w.p, 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)
paramTypes := sigType.Params()
w.Code(exprCall)
writeFunExpr()
w.pos(expr)
paramType := func(i int) types2.Type {
if sigType.Variadic() && !expr.HasDots && i >= paramTypes.Len()-1 {
return paramTypes.At(paramTypes.Len() - 1).Type().(*types2.Slice).Elem()
}
return paramTypes.At(i).Type()
}
w.multiExpr(expr, paramType, expr.ArgList)
w.Bool(expr.HasDots)
if rtype != nil {
w.rtype(rtype)
}
}
}
func sliceElem(typ types2.Type) types2.Type {
return types2.CoreType(typ).(*types2.Slice).Elem()
}
func (w *writer) optExpr(expr syntax.Expr) {
if w.Bool(expr != nil) {
w.expr(expr)
}
}
// recvExpr writes out expr.X, but handles any implicit addressing,
// 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]
w.Code(exprRecv)
w.expr(expr.X)
w.pos(expr)
w.Len(len(implicits))
typ := w.p.typeOf(expr.X)
for _, ix := range implicits {
typ = deref2(typ).Underlying().(*types2.Struct).Field(ix).Type()
w.Len(ix)
}
recv := sel.Obj().(*types2.Func).Type().(*types2.Signature).Recv().Type()
if w.Bool(isPtrTo(typ, recv)) { // needs deref
typ = recv
} else if w.Bool(isPtrTo(recv, typ)) { // needs addr
typ = recv
}
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.typ(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 := types2.Unalias(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 := types2.Unalias(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
// an f(g()) call, or the RHS operand in a comma-ok assignment).
func (w *writer) multiExpr(pos poser, dstType func(int) types2.Type, exprs []syntax.Expr) {
w.Sync(pkgbits.SyncMultiExpr)
if len(exprs) == 1 {
expr := exprs[0]
if tuple, ok := w.p.typeOf(expr).(*types2.Tuple); ok {
assert(tuple.Len() > 1)
w.Bool(true) // N:1 assignment
w.pos(pos)
w.expr(expr)
w.Len(tuple.Len())
for i := 0; i < tuple.Len(); i++ {
src := tuple.At(i).Type()
// TODO(mdempsky): Investigate not writing src here. I think
// the reader should be able to infer it from expr anyway.
w.typ(src)
if dst := dstType(i); w.Bool(dst != nil && !types2.Identical(src, dst)) {
if src == nil || dst == nil {
w.p.fatalf(pos, "src is %v, dst is %v", src, dst)
}
if !types2.AssignableTo(src, dst) {
w.p.fatalf(pos, "%v is not assignable to %v", src, dst)
}
w.typ(dst)
w.convRTTI(src, dst)
}
}
return
}
}
w.Bool(false) // N:N assignment
w.Len(len(exprs))
for i, expr := range exprs {
w.implicitConvExpr(dstType(i), expr)
}
}
// implicitConvExpr is like expr, but if dst is non-nil and different
// from expr's type, then an implicit conversion operation is inserted
// at expr's position.
func (w *writer) implicitConvExpr(dst types2.Type, expr syntax.Expr) {
w.convertExpr(dst, expr, true)
}
func (w *writer) convertExpr(dst types2.Type, expr syntax.Expr, implicit bool) {
src := w.p.typeOf(expr)
// Omit implicit no-op conversions.
identical := dst == nil || types2.Identical(src, dst)
if implicit && identical {
w.expr(expr)
return
}
if implicit && !types2.AssignableTo(src, dst) {
w.p.fatalf(expr, "%v is not assignable to %v", src, dst)
}
w.Code(exprConvert)
w.Bool(implicit)
w.typ(dst)
w.pos(expr)
w.convRTTI(src, dst)
w.Bool(isTypeParam(dst))
w.Bool(identical)
w.expr(expr)
}
func (w *writer) compLit(lit *syntax.CompositeLit) {
typ := w.p.typeOf(lit)
w.Sync(pkgbits.SyncCompLit)
w.pos(lit)
w.typ(typ)
if ptr, ok := types2.CoreType(typ).(*types2.Pointer); ok {
typ = ptr.Elem()
}
var keyType, elemType types2.Type
var structType *types2.Struct
switch typ0 := typ; typ := types2.CoreType(typ).(type) {
default:
w.p.fatalf(lit, "unexpected composite literal type: %v", typ)
case *types2.Array:
elemType = typ.Elem()
case *types2.Map:
w.rtype(typ0)
keyType, elemType = typ.Key(), typ.Elem()
case *types2.Slice:
elemType = typ.Elem()
case *types2.Struct:
structType = typ
}
w.Len(len(lit.ElemList))
for i, elem := range lit.ElemList {
elemType := elemType
if structType != nil {
if kv, ok := elem.(*syntax.KeyValueExpr); ok {
// use position of expr.Key rather than of elem (which has position of ':')
w.pos(kv.Key)
i = fieldIndex(w.p.info, structType, kv.Key.(*syntax.Name))
elem = kv.Value
} else {
w.pos(elem)
}
elemType = structType.Field(i).Type()
w.Len(i)
} else {
if kv, ok := elem.(*syntax.KeyValueExpr); w.Bool(ok) {
// use position of expr.Key rather than of elem (which has position of ':')
w.pos(kv.Key)
w.implicitConvExpr(keyType, kv.Key)
elem = kv.Value
}
}
w.implicitConvExpr(elemType, elem)
}
}
func (w *writer) funcLit(expr *syntax.FuncLit) {
sig := w.p.typeOf(expr).(*types2.Signature)
body, closureVars := w.p.bodyIdx(sig, expr.Body, w.dict)
w.Sync(pkgbits.SyncFuncLit)
w.pos(expr)
w.signature(sig)
w.Len(len(closureVars))
for _, cv := range closureVars {
w.pos(cv.pos)
w.useLocal(cv.pos, cv.var_)
}
w.Reloc(pkgbits.RelocBody, body)
}
type posVar struct {
pos syntax.Pos
var_ *types2.Var
}
func (p posVar) String() string {
return p.pos.String() + ":" + p.var_.String()
}
func (w *writer) exprList(expr syntax.Expr) {
w.Sync(pkgbits.SyncExprList)
w.exprs(syntax.UnpackListExpr(expr))
}
func (w *writer) exprs(exprs []syntax.Expr) {
w.Sync(pkgbits.SyncExprs)
w.Len(len(exprs))
for _, expr := range exprs {
w.expr(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)
info := w.p.typIdx(typ, w.dict)
w.rtypeInfo(info)
}
func (w *writer) rtypeInfo(info typeInfo) {
w.Sync(pkgbits.SyncRType)
if w.Bool(info.derived) {
w.Len(w.dict.rtypeIdx(info))
} else {
w.typInfo(info)
}
}
// varDictIndex writes out information for populating DictIndex for
// the ir.Name that will represent obj.
func (w *writer) varDictIndex(obj *types2.Var) {
info := w.p.typIdx(obj.Type(), w.dict)
if w.Bool(info.derived) {
w.Len(w.dict.rtypeIdx(info))
}
}
// isUntyped reports whether typ is an untyped type.
func isUntyped(typ types2.Type) bool {
// Note: types2.Unalias is unnecessary here, since untyped types can't be aliased.
basic, ok := typ.(*types2.Basic)
return ok && basic.Info()&types2.IsUntyped != 0
}
// isTuple reports whether typ is a tuple type.
func isTuple(typ types2.Type) bool {
// Note: types2.Unalias is unnecessary here, since tuple types can't be aliased.
_, ok := typ.(*types2.Tuple)
return ok
}
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)
w.rtypeInfo(typInfo)
w.rtypeInfo(ifaceInfo)
if w.Bool(typInfo.derived || ifaceInfo.derived) {
w.Len(w.dict.itabIdx(typInfo, ifaceInfo))
}
}
// 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.itab(src, dst)
}
func (w *writer) exprType(iface types2.Type, typ syntax.Expr) {
base.Assertf(iface == nil || isInterface(iface), "%v must be nil or an interface type", iface)
tv := w.p.typeAndValue(typ)
assert(tv.IsType())
w.Sync(pkgbits.SyncExprType)
w.pos(typ)
if w.Bool(iface != nil && !iface.Underlying().(*types2.Interface).Empty()) {
w.itab(tv.Type, iface)
} else {
w.rtype(tv.Type)
info := w.p.typIdx(tv.Type, w.dict)
w.Bool(info.derived)
}
}
// 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.
func isInterface(typ types2.Type) bool {
if _, ok := types2.Unalias(typ).(*types2.TypeParam); ok {
// typ is a type parameter and may be instantiated as either a
// concrete or interface type, so the writer can't depend on
// knowing this.
base.Fatalf("%v is a type parameter", typ)
}
_, ok := typ.Underlying().(*types2.Interface)
return ok
}
// op writes an Op into the bitstream.
func (w *writer) op(op ir.Op) {
// TODO(mdempsky): Remove in favor of explicit codes? Would make
// export data more stable against internal refactorings, but low
// priority at the moment.
assert(op != 0)
w.Sync(pkgbits.SyncOp)
w.Len(int(op))
}
// @@@ Package initialization
// Caution: This code is still clumsy, because toolstash -cmp is
// particularly sensitive to it.
type typeDeclGen struct {
*syntax.TypeDecl
gen int
// Implicit type parameters in scope at this type declaration.
implicits []*types2.TypeParam
}
type fileImports struct {
importedEmbed, importedUnsafe bool
}
// declCollector is a visitor type that collects compiler-needed
// information about declarations that types2 doesn't track.
//
// Notably, it maps declared types and functions back to their
// declaration statement, keeps track of implicit type parameters, and
// assigns unique type "generation" numbers to local defined types.
type declCollector struct {
pw *pkgWriter
typegen *int
file *fileImports
withinFunc bool
implicits []*types2.TypeParam
}
func (c *declCollector) withTParams(obj types2.Object) *declCollector {
tparams := objTypeParams(obj)
n := tparams.Len()
if n == 0 {
return c
}
copy := *c
copy.implicits = copy.implicits[:len(copy.implicits):len(copy.implicits)]
for i := 0; i < n; i++ {
copy.implicits = append(copy.implicits, tparams.At(i))
}
return &copy
}
func (c *declCollector) Visit(n syntax.Node) syntax.Visitor {
pw := c.pw
switch n := n.(type) {
case *syntax.File:
pw.checkPragmas(n.Pragma, ir.GoBuildPragma, false)
case *syntax.ImportDecl:
pw.checkPragmas(n.Pragma, 0, false)
switch pw.info.PkgNameOf(n).Imported().Path() {
case "embed":
c.file.importedEmbed = true
case "unsafe":
c.file.importedUnsafe = true
}
case *syntax.ConstDecl:
pw.checkPragmas(n.Pragma, 0, false)
case *syntax.FuncDecl:
pw.checkPragmas(n.Pragma, funcPragmas, false)
obj := pw.info.Defs[n.Name].(*types2.Func)
pw.funDecls[obj] = n
return c.withTParams(obj)
case *syntax.TypeDecl:
obj := pw.info.Defs[n.Name].(*types2.TypeName)
d := typeDeclGen{TypeDecl: n, implicits: c.implicits}
if n.Alias {
pw.checkPragmas(n.Pragma, 0, false)
} else {
pw.checkPragmas(n.Pragma, 0, false)
// Assign a unique ID to function-scoped defined types.
if c.withinFunc {
*c.typegen++
d.gen = *c.typegen
}
}
pw.typDecls[obj] = d
// TODO(mdempsky): Omit? Not strictly necessary; only matters for
// type declarations within function literals within parameterized
// type declarations, but types2 the function literals will be
// constant folded away.
return c.withTParams(obj)
case *syntax.VarDecl:
pw.checkPragmas(n.Pragma, 0, true)
if p, ok := n.Pragma.(*pragmas); ok && len(p.Embeds) > 0 {
if err := checkEmbed(n, c.file.importedEmbed, c.withinFunc); err != nil {
pw.errorf(p.Embeds[0].Pos, "%s", err)
}
}
case *syntax.BlockStmt:
if !c.withinFunc {
copy := *c
copy.withinFunc = true
return &copy
}
}
return c
}
func (pw *pkgWriter) collectDecls(noders []*noder) {
var typegen int
for _, p := range noders {
var file fileImports
syntax.Walk(p.file, &declCollector{
pw: pw,
typegen: &typegen,
file: &file,
})
pw.cgoPragmas = append(pw.cgoPragmas, p.pragcgobuf...)
for _, l := range p.linknames {
if !file.importedUnsafe {
pw.errorf(l.pos, "//go:linkname only allowed in Go files that import \"unsafe\"")
continue
}
switch obj := pw.curpkg.Scope().Lookup(l.local).(type) {
case *types2.Func, *types2.Var:
if _, ok := pw.linknames[obj]; !ok {
pw.linknames[obj] = l.remote
} else {
pw.errorf(l.pos, "duplicate //go:linkname for %s", l.local)
}
default:
if types.AllowsGoVersion(1, 18) {
pw.errorf(l.pos, "//go:linkname must refer to declared function or variable")
}
}
}
}
}
func (pw *pkgWriter) checkPragmas(p syntax.Pragma, allowed ir.PragmaFlag, embedOK bool) {
if p == nil {
return
}
pragma := p.(*pragmas)
for _, pos := range pragma.Pos {
if pos.Flag&^allowed != 0 {
pw.errorf(pos.Pos, "misplaced compiler directive")
}
}
if !embedOK {
for _, e := range pragma.Embeds {
pw.errorf(e.Pos, "misplaced go:embed directive")
}
}
}
func (w *writer) pkgInit(noders []*noder) {
w.Len(len(w.p.cgoPragmas))
for _, cgoPragma := range w.p.cgoPragmas {
w.Strings(cgoPragma)
}
w.pkgInitOrder()
w.Sync(pkgbits.SyncDecls)
for _, p := range noders {
for _, decl := range p.file.DeclList {
w.pkgDecl(decl)
}
}
w.Code(declEnd)
w.Sync(pkgbits.SyncEOF)
}
func (w *writer) pkgInitOrder() {
// TODO(mdempsky): Write as a function body instead?
w.Len(len(w.p.info.InitOrder))
for _, init := range w.p.info.InitOrder {
w.Len(len(init.Lhs))
for _, v := range init.Lhs {
w.obj(v, nil)
}
w.expr(init.Rhs)
}
}
func (w *writer) pkgDecl(decl syntax.Decl) {
switch decl := decl.(type) {
default:
w.p.unexpected("declaration", decl)
case *syntax.ImportDecl:
case *syntax.ConstDecl:
w.Code(declOther)
w.pkgObjs(decl.NameList...)
case *syntax.FuncDecl:
if decl.Name.Value == "_" {
break // skip blank functions
}
obj := w.p.info.Defs[decl.Name].(*types2.Func)
sig := obj.Type().(*types2.Signature)
if sig.RecvTypeParams() != nil || sig.TypeParams() != nil {
break // skip generic functions
}
if recv := sig.Recv(); recv != nil {
w.Code(declMethod)
w.typ(recvBase(recv))
w.selector(obj)
break
}
w.Code(declFunc)
w.pkgObjs(decl.Name)
case *syntax.TypeDecl:
if len(decl.TParamList) != 0 {
break // skip generic type decls
}
if decl.Name.Value == "_" {
break // skip blank type decls
}
name := w.p.info.Defs[decl.Name].(*types2.TypeName)
// Skip type declarations for interfaces that are only usable as
// type parameter bounds.
if iface, ok := name.Type().Underlying().(*types2.Interface); ok && !iface.IsMethodSet() {
break
}
w.Code(declOther)
w.pkgObjs(decl.Name)
case *syntax.VarDecl:
w.Code(declVar)
w.pkgObjs(decl.NameList...)
var embeds []pragmaEmbed
if p, ok := decl.Pragma.(*pragmas); ok {
embeds = p.Embeds
}
w.Len(len(embeds))
for _, embed := range embeds {
w.pos(embed.Pos)
w.Strings(embed.Patterns)
}
}
}
func (w *writer) pkgObjs(names ...*syntax.Name) {
w.Sync(pkgbits.SyncDeclNames)
w.Len(len(names))
for _, name := range names {
obj, ok := w.p.info.Defs[name]
assert(ok)
w.Sync(pkgbits.SyncDeclName)
w.obj(obj, nil)
}
}
// @@@ Helpers
// staticBool analyzes a boolean expression and reports whether it's
// always true (positive result), always false (negative result), or
// unknown (zero).
//
// It also simplifies the expression while preserving semantics, if
// possible.
func (pw *pkgWriter) staticBool(ep *syntax.Expr) int {
if val := pw.typeAndValue(*ep).Value; val != nil {
if constant.BoolVal(val) {
return +1
} else {
return -1
}
}
if e, ok := (*ep).(*syntax.Operation); ok {
switch e.Op {
case syntax.Not:
return pw.staticBool(&e.X)
case syntax.AndAnd:
x := pw.staticBool(&e.X)
if x < 0 {
*ep = e.X
return x
}
y := pw.staticBool(&e.Y)
if x > 0 || y < 0 {
if pw.typeAndValue(e.X).Value != nil {
*ep = e.Y
}
return y
}
case syntax.OrOr:
x := pw.staticBool(&e.X)
if x > 0 {
*ep = e.X
return x
}
y := pw.staticBool(&e.Y)
if x < 0 || y > 0 {
if pw.typeAndValue(e.X).Value != nil {
*ep = e.Y
}
return y
}
}
}
return 0
}
// hasImplicitTypeParams reports whether obj is a defined type with
// implicit type parameters (e.g., declared within a generic function
// or method).
func (pw *pkgWriter) hasImplicitTypeParams(obj *types2.TypeName) bool {
if obj.Pkg() == pw.curpkg {
decl, ok := pw.typDecls[obj]
assert(ok)
if len(decl.implicits) != 0 {
return true
}
}
return false
}
// isDefinedType reports whether obj is a defined type.
func isDefinedType(obj types2.Object) bool {
if obj, ok := obj.(*types2.TypeName); ok {
return !obj.IsAlias()
}
return false
}
// isGlobal reports whether obj was declared at package scope.
//
// Caveat: blank objects are not declared.
func isGlobal(obj types2.Object) bool {
return obj.Parent() == obj.Pkg().Scope()
}
// lookupObj returns the object that expr refers to, if any. If expr
// is an explicit instantiation of a generic object, then the instance
// object is returned as well.
func lookupObj(p *pkgWriter, expr syntax.Expr) (obj types2.Object, inst types2.Instance) {
if index, ok := expr.(*syntax.IndexExpr); ok {
args := syntax.UnpackListExpr(index.Index)
if len(args) == 1 {
tv := p.typeAndValue(args[0])
if tv.IsValue() {
return // normal index expression
}
}
expr = index.X
}
// Strip package qualifier, if present.
if sel, ok := expr.(*syntax.SelectorExpr); ok {
if !isPkgQual(p.info, sel) {
return // normal selector expression
}
expr = sel.Sel
}
if name, ok := expr.(*syntax.Name); ok {
obj = p.info.Uses[name]
inst = p.info.Instances[name]
}
return
}
// isPkgQual reports whether the given selector expression is a
// package-qualified identifier.
func isPkgQual(info *types2.Info, sel *syntax.SelectorExpr) bool {
if name, ok := sel.X.(*syntax.Name); ok {
_, isPkgName := info.Uses[name].(*types2.PkgName)
return isPkgName
}
return false
}
// isNil reports whether expr is a (possibly parenthesized) reference
// to the predeclared nil value.
func isNil(p *pkgWriter, expr syntax.Expr) bool {
tv := p.typeAndValue(expr)
return tv.IsNil()
}
// isBuiltin reports whether expr is a (possibly parenthesized)
// referenced to the specified built-in function.
func (pw *pkgWriter) isBuiltin(expr syntax.Expr, builtin string) bool {
if name, ok := syntax.Unparen(expr).(*syntax.Name); ok && name.Value == builtin {
return pw.typeAndValue(name).IsBuiltin()
}
return false
}
// recvBase returns the base type for the given receiver parameter.
func recvBase(recv *types2.Var) *types2.Named {
typ := types2.Unalias(recv.Type())
if ptr, ok := typ.(*types2.Pointer); ok {
typ = types2.Unalias(ptr.Elem())
}
return typ.(*types2.Named)
}
// namesAsExpr returns a list of names as a syntax.Expr.
func namesAsExpr(names []*syntax.Name) syntax.Expr {
if len(names) == 1 {
return names[0]
}
exprs := make([]syntax.Expr, len(names))
for i, name := range names {
exprs[i] = name
}
return &syntax.ListExpr{ElemList: exprs}
}
// fieldIndex returns the index of the struct field named by key.
func fieldIndex(info *types2.Info, str *types2.Struct, key *syntax.Name) int {
field := info.Uses[key].(*types2.Var)
for i := 0; i < str.NumFields(); i++ {
if str.Field(i) == field {
return i
}
}
panic(fmt.Sprintf("%s: %v is not a field of %v", key.Pos(), field, str))
}
// objTypeParams returns the type parameters on the given object.
func objTypeParams(obj types2.Object) *types2.TypeParamList {
switch obj := obj.(type) {
case *types2.Func:
sig := obj.Type().(*types2.Signature)
if sig.Recv() != nil {
return sig.RecvTypeParams()
}
return sig.TypeParams()
case *types2.TypeName:
if !obj.IsAlias() {
return obj.Type().(*types2.Named).TypeParams()
}
}
return nil
}
// splitNamed decomposes a use of a defined type into its original
// type definition and the type arguments used to instantiate it.
func splitNamed(typ *types2.Named) (*types2.TypeName, *types2.TypeList) {
base.Assertf(typ.TypeParams().Len() == typ.TypeArgs().Len(), "use of uninstantiated type: %v", typ)
orig := typ.Origin()
base.Assertf(orig.TypeArgs() == nil, "origin %v of %v has type arguments", orig, typ)
base.Assertf(typ.Obj() == orig.Obj(), "%v has object %v, but %v has object %v", typ, typ.Obj(), orig, orig.Obj())
return typ.Obj(), typ.TypeArgs()
}
func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag {
if p == nil {
return 0
}
return p.(*pragmas).Flag
}
func asWasmImport(p syntax.Pragma) *WasmImport {
if p == nil {
return nil
}
return p.(*pragmas).WasmImport
}
// isPtrTo reports whether from is the type *to.
func isPtrTo(from, to types2.Type) bool {
ptr, ok := types2.Unalias(from).(*types2.Pointer)
return ok && types2.Identical(ptr.Elem(), to)
}
// hasFallthrough reports whether stmts ends in a fallthrough
// statement.
func hasFallthrough(stmts []syntax.Stmt) bool {
last, ok := lastNonEmptyStmt(stmts).(*syntax.BranchStmt)
return ok && last.Tok == syntax.Fallthrough
}
// lastNonEmptyStmt returns the last non-empty statement in list, if
// any.
func lastNonEmptyStmt(stmts []syntax.Stmt) syntax.Stmt {
for i := len(stmts) - 1; i >= 0; i-- {
stmt := stmts[i]
if _, ok := stmt.(*syntax.EmptyStmt); !ok {
return stmt
}
}
return nil
}
// terminates reports whether stmt terminates normal control flow
// (i.e., does not merely advance to the following statement).
func (pw *pkgWriter) terminates(stmt syntax.Stmt) bool {
switch stmt := stmt.(type) {
case *syntax.BranchStmt:
if stmt.Tok == syntax.Goto {
return true
}
case *syntax.ReturnStmt:
return true
case *syntax.ExprStmt:
if call, ok := syntax.Unparen(stmt.X).(*syntax.CallExpr); ok {
if pw.isBuiltin(call.Fun, "panic") {
return true
}
}
// The handling of BlockStmt here is approximate, but it serves to
// allow dead-code elimination for:
//
// if true {
// return x
// }
// unreachable
case *syntax.IfStmt:
cond := pw.staticBool(&stmt.Cond)
return (cond < 0 || pw.terminates(stmt.Then)) && (cond > 0 || pw.terminates(stmt.Else))
case *syntax.BlockStmt:
return pw.terminates(lastNonEmptyStmt(stmt.List))
}
return false
}