// Copyright 2018 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. // Indexed package export. // // The indexed export data format is an evolution of the previous // binary export data format. Its chief contribution is introducing an // index table, which allows efficient random access of individual // declarations and inline function bodies. In turn, this allows // avoiding unnecessary work for compilation units that import large // packages. // // // The top-level data format is structured as: // // Header struct { // Tag byte // 'i' // Version uvarint // StringSize uvarint // DataSize uvarint // } // // Strings [StringSize]byte // Data [DataSize]byte // // MainIndex []struct{ // PkgPath stringOff // PkgName stringOff // PkgHeight uvarint // // Decls []struct{ // Name stringOff // Offset declOff // } // } // // Fingerprint [8]byte // // uvarint means a uint64 written out using uvarint encoding. // // []T means a uvarint followed by that many T objects. In other // words: // // Len uvarint // Elems [Len]T // // stringOff means a uvarint that indicates an offset within the // Strings section. At that offset is another uvarint, followed by // that many bytes, which form the string value. // // declOff means a uvarint that indicates an offset within the Data // section where the associated declaration can be found. // // // There are five kinds of declarations, distinguished by their first // byte: // // type Var struct { // Tag byte // 'V' // Pos Pos // Type typeOff // } // // type Func struct { // Tag byte // 'F' // Pos Pos // Signature Signature // } // // type Const struct { // Tag byte // 'C' // Pos Pos // Value Value // } // // type Type struct { // Tag byte // 'T' // Pos Pos // Underlying typeOff // // Methods []struct{ // omitted if Underlying is an interface type // Pos Pos // Name stringOff // Recv Param // Signature Signature // } // } // // type Alias struct { // Tag byte // 'A' // Pos Pos // Type typeOff // } // // // typeOff means a uvarint that either indicates a predeclared type, // or an offset into the Data section. If the uvarint is less than // predeclReserved, then it indicates the index into the predeclared // types list (see predeclared in bexport.go for order). Otherwise, // subtracting predeclReserved yields the offset of a type descriptor. // // Value means a type and type-specific value. See // (*exportWriter).value for details. // // // There are nine kinds of type descriptors, distinguished by an itag: // // type DefinedType struct { // Tag itag // definedType // Name stringOff // PkgPath stringOff // } // // type PointerType struct { // Tag itag // pointerType // Elem typeOff // } // // type SliceType struct { // Tag itag // sliceType // Elem typeOff // } // // type ArrayType struct { // Tag itag // arrayType // Len uint64 // Elem typeOff // } // // type ChanType struct { // Tag itag // chanType // Dir uint64 // 1 RecvOnly; 2 SendOnly; 3 SendRecv // Elem typeOff // } // // type MapType struct { // Tag itag // mapType // Key typeOff // Elem typeOff // } // // type FuncType struct { // Tag itag // signatureType // PkgPath stringOff // Signature Signature // } // // type StructType struct { // Tag itag // structType // PkgPath stringOff // Fields []struct { // Pos Pos // Name stringOff // Type typeOff // Embedded bool // Note stringOff // } // } // // type InterfaceType struct { // Tag itag // interfaceType // PkgPath stringOff // Embeddeds []struct { // Pos Pos // Type typeOff // } // Methods []struct { // Pos Pos // Name stringOff // Signature Signature // } // } // // // type Signature struct { // Params []Param // Results []Param // Variadic bool // omitted if Results is empty // } // // type Param struct { // Pos Pos // Name stringOff // Type typOff // } // // // Pos encodes a file:line:column triple, incorporating a simple delta // encoding scheme within a data object. See exportWriter.pos for // details. // // // Compiler-specific details. // // cmd/compile writes out a second index for inline bodies and also // appends additional compiler-specific details after declarations. // Third-party tools are not expected to depend on these details and // they're expected to change much more rapidly, so they're omitted // here. See exportWriter's varExt/funcExt/etc methods for details. package gc import ( "bufio" "bytes" "cmd/compile/internal/types" "cmd/internal/goobj" "cmd/internal/src" "crypto/md5" "encoding/binary" "fmt" "io" "math/big" "sort" "strings" ) // Current indexed export format version. Increase with each format change. // 1: added column details to Pos // 0: Go1.11 encoding const iexportVersion = 1 // predeclReserved is the number of type offsets reserved for types // implicitly declared in the universe block. const predeclReserved = 32 // An itag distinguishes the kind of type that was written into the // indexed export format. type itag uint64 const ( // Types definedType itag = iota pointerType sliceType arrayType chanType mapType signatureType structType interfaceType ) func iexport(out *bufio.Writer) { // Mark inline bodies that are reachable through exported types. // (Phase 0 of bexport.go.) { // TODO(mdempsky): Separate from bexport logic. p := &exporter{marked: make(map[*types.Type]bool)} for _, n := range exportlist { sym := n.Sym p.markType(asNode(sym.Def).Type) } } p := iexporter{ allPkgs: map[*types.Pkg]bool{}, stringIndex: map[string]uint64{}, declIndex: map[*Node]uint64{}, inlineIndex: map[*Node]uint64{}, typIndex: map[*types.Type]uint64{}, } for i, pt := range predeclared() { p.typIndex[pt] = uint64(i) } if len(p.typIndex) > predeclReserved { Fatalf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved) } // Initialize work queue with exported declarations. for _, n := range exportlist { p.pushDecl(n) } // Loop until no more work. We use a queue because while // writing out inline bodies, we may discover additional // declarations that are needed. for !p.declTodo.empty() { p.doDecl(p.declTodo.popLeft()) } // Append indices to data0 section. dataLen := uint64(p.data0.Len()) w := p.newWriter() w.writeIndex(p.declIndex, true) w.writeIndex(p.inlineIndex, false) w.flush() // Assemble header. var hdr intWriter hdr.WriteByte('i') hdr.uint64(iexportVersion) hdr.uint64(uint64(p.strings.Len())) hdr.uint64(dataLen) // Flush output. h := md5.New() wr := io.MultiWriter(out, h) io.Copy(wr, &hdr) io.Copy(wr, &p.strings) io.Copy(wr, &p.data0) // Add fingerprint (used by linker object file). // Attach this to the end, so tools (e.g. gcimporter) don't care. copy(Ctxt.Fingerprint[:], h.Sum(nil)[:]) out.Write(Ctxt.Fingerprint[:]) } // writeIndex writes out an object index. mainIndex indicates whether // we're writing out the main index, which is also read by // non-compiler tools and includes a complete package description // (i.e., name and height). func (w *exportWriter) writeIndex(index map[*Node]uint64, mainIndex bool) { // Build a map from packages to objects from that package. pkgObjs := map[*types.Pkg][]*Node{} // For the main index, make sure to include every package that // we reference, even if we're not exporting (or reexporting) // any symbols from it. if mainIndex { pkgObjs[localpkg] = nil for pkg := range w.p.allPkgs { pkgObjs[pkg] = nil } } for n := range index { pkgObjs[n.Sym.Pkg] = append(pkgObjs[n.Sym.Pkg], n) } var pkgs []*types.Pkg for pkg, objs := range pkgObjs { pkgs = append(pkgs, pkg) sort.Slice(objs, func(i, j int) bool { return objs[i].Sym.Name < objs[j].Sym.Name }) } sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path }) w.uint64(uint64(len(pkgs))) for _, pkg := range pkgs { w.string(pkg.Path) if mainIndex { w.string(pkg.Name) w.uint64(uint64(pkg.Height)) } objs := pkgObjs[pkg] w.uint64(uint64(len(objs))) for _, n := range objs { w.string(n.Sym.Name) w.uint64(index[n]) } } } type iexporter struct { // allPkgs tracks all packages that have been referenced by // the export data, so we can ensure to include them in the // main index. allPkgs map[*types.Pkg]bool declTodo nodeQueue strings intWriter stringIndex map[string]uint64 data0 intWriter declIndex map[*Node]uint64 inlineIndex map[*Node]uint64 typIndex map[*types.Type]uint64 } // stringOff returns the offset of s within the string section. // If not already present, it's added to the end. func (p *iexporter) stringOff(s string) uint64 { off, ok := p.stringIndex[s] if !ok { off = uint64(p.strings.Len()) p.stringIndex[s] = off p.strings.uint64(uint64(len(s))) p.strings.WriteString(s) } return off } // pushDecl adds n to the declaration work queue, if not already present. func (p *iexporter) pushDecl(n *Node) { if n.Sym == nil || asNode(n.Sym.Def) != n && n.Op != OTYPE { Fatalf("weird Sym: %v, %v", n, n.Sym) } // Don't export predeclared declarations. if n.Sym.Pkg == builtinpkg || n.Sym.Pkg == unsafepkg { return } if _, ok := p.declIndex[n]; ok { return } p.declIndex[n] = ^uint64(0) // mark n present in work queue p.declTodo.pushRight(n) } // exportWriter handles writing out individual data section chunks. type exportWriter struct { p *iexporter data intWriter currPkg *types.Pkg prevFile string prevLine int64 prevColumn int64 } func (p *iexporter) doDecl(n *Node) { w := p.newWriter() w.setPkg(n.Sym.Pkg, false) switch n.Op { case ONAME: switch n.Class() { case PEXTERN: // Variable. w.tag('V') w.pos(n.Pos) w.typ(n.Type) w.varExt(n) case PFUNC: if n.IsMethod() { Fatalf("unexpected method: %v", n) } // Function. w.tag('F') w.pos(n.Pos) w.signature(n.Type) w.funcExt(n) default: Fatalf("unexpected class: %v, %v", n, n.Class()) } case OLITERAL: // Constant. n = typecheck(n, ctxExpr) w.tag('C') w.pos(n.Pos) w.value(n.Type, n.Val()) case OTYPE: if IsAlias(n.Sym) { // Alias. w.tag('A') w.pos(n.Pos) w.typ(n.Type) break } // Defined type. w.tag('T') w.pos(n.Pos) underlying := n.Type.Orig if underlying == types.Errortype.Orig { // For "type T error", use error as the // underlying type instead of error's own // underlying anonymous interface. This // ensures consistency with how importers may // declare error (e.g., go/types uses nil Pkg // for predeclared objects). underlying = types.Errortype } w.typ(underlying) t := n.Type if t.IsInterface() { w.typeExt(t) break } ms := t.Methods() w.uint64(uint64(ms.Len())) for _, m := range ms.Slice() { w.pos(m.Pos) w.selector(m.Sym) w.param(m.Type.Recv()) w.signature(m.Type) } w.typeExt(t) for _, m := range ms.Slice() { w.methExt(m) } default: Fatalf("unexpected node: %v", n) } p.declIndex[n] = w.flush() } func (w *exportWriter) tag(tag byte) { w.data.WriteByte(tag) } func (p *iexporter) doInline(f *Node) { w := p.newWriter() w.setPkg(fnpkg(f), false) w.stmtList(asNodes(f.Func.Inl.Body)) p.inlineIndex[f] = w.flush() } func (w *exportWriter) pos(pos src.XPos) { p := Ctxt.PosTable.Pos(pos) file := p.Base().AbsFilename() line := int64(p.RelLine()) column := int64(p.RelCol()) // Encode position relative to the last position: column // delta, then line delta, then file name. We reserve the // bottom bit of the column and line deltas to encode whether // the remaining fields are present. // // Note: Because data objects may be read out of order (or not // at all), we can only apply delta encoding within a single // object. This is handled implicitly by tracking prevFile, // prevLine, and prevColumn as fields of exportWriter. deltaColumn := (column - w.prevColumn) << 1 deltaLine := (line - w.prevLine) << 1 if file != w.prevFile { deltaLine |= 1 } if deltaLine != 0 { deltaColumn |= 1 } w.int64(deltaColumn) if deltaColumn&1 != 0 { w.int64(deltaLine) if deltaLine&1 != 0 { w.string(file) } } w.prevFile = file w.prevLine = line w.prevColumn = column } func (w *exportWriter) pkg(pkg *types.Pkg) { // Ensure any referenced packages are declared in the main index. w.p.allPkgs[pkg] = true w.string(pkg.Path) } func (w *exportWriter) qualifiedIdent(n *Node) { // Ensure any referenced declarations are written out too. w.p.pushDecl(n) s := n.Sym w.string(s.Name) w.pkg(s.Pkg) } func (w *exportWriter) selector(s *types.Sym) { if w.currPkg == nil { Fatalf("missing currPkg") } // Method selectors are rewritten into method symbols (of the // form T.M) during typechecking, but we want to write out // just the bare method name. name := s.Name if i := strings.LastIndex(name, "."); i >= 0 { name = name[i+1:] } else { pkg := w.currPkg if types.IsExported(name) { pkg = localpkg } if s.Pkg != pkg { Fatalf("package mismatch in selector: %v in package %q, but want %q", s, s.Pkg.Path, pkg.Path) } } w.string(name) } func (w *exportWriter) typ(t *types.Type) { w.data.uint64(w.p.typOff(t)) } func (p *iexporter) newWriter() *exportWriter { return &exportWriter{p: p} } func (w *exportWriter) flush() uint64 { off := uint64(w.p.data0.Len()) io.Copy(&w.p.data0, &w.data) return off } func (p *iexporter) typOff(t *types.Type) uint64 { off, ok := p.typIndex[t] if !ok { w := p.newWriter() w.doTyp(t) off = predeclReserved + w.flush() p.typIndex[t] = off } return off } func (w *exportWriter) startType(k itag) { w.data.uint64(uint64(k)) } func (w *exportWriter) doTyp(t *types.Type) { if t.Sym != nil { if t.Sym.Pkg == builtinpkg || t.Sym.Pkg == unsafepkg { Fatalf("builtin type missing from typIndex: %v", t) } w.startType(definedType) w.qualifiedIdent(typenod(t)) return } switch t.Etype { case TPTR: w.startType(pointerType) w.typ(t.Elem()) case TSLICE: w.startType(sliceType) w.typ(t.Elem()) case TARRAY: w.startType(arrayType) w.uint64(uint64(t.NumElem())) w.typ(t.Elem()) case TCHAN: w.startType(chanType) w.uint64(uint64(t.ChanDir())) w.typ(t.Elem()) case TMAP: w.startType(mapType) w.typ(t.Key()) w.typ(t.Elem()) case TFUNC: w.startType(signatureType) w.setPkg(t.Pkg(), true) w.signature(t) case TSTRUCT: w.startType(structType) w.setPkg(t.Pkg(), true) w.uint64(uint64(t.NumFields())) for _, f := range t.FieldSlice() { w.pos(f.Pos) w.selector(f.Sym) w.typ(f.Type) w.bool(f.Embedded != 0) w.string(f.Note) } case TINTER: var embeddeds, methods []*types.Field for _, m := range t.Methods().Slice() { if m.Sym != nil { methods = append(methods, m) } else { embeddeds = append(embeddeds, m) } } w.startType(interfaceType) w.setPkg(t.Pkg(), true) w.uint64(uint64(len(embeddeds))) for _, f := range embeddeds { w.pos(f.Pos) w.typ(f.Type) } w.uint64(uint64(len(methods))) for _, f := range methods { w.pos(f.Pos) w.selector(f.Sym) w.signature(f.Type) } default: Fatalf("unexpected type: %v", t) } } func (w *exportWriter) setPkg(pkg *types.Pkg, write bool) { if pkg == nil { // TODO(mdempsky): Proactively set Pkg for types and // remove this fallback logic. pkg = localpkg } if write { w.pkg(pkg) } w.currPkg = pkg } func (w *exportWriter) signature(t *types.Type) { w.paramList(t.Params().FieldSlice()) w.paramList(t.Results().FieldSlice()) if n := t.Params().NumFields(); n > 0 { w.bool(t.Params().Field(n - 1).IsDDD()) } } func (w *exportWriter) paramList(fs []*types.Field) { w.uint64(uint64(len(fs))) for _, f := range fs { w.param(f) } } func (w *exportWriter) param(f *types.Field) { w.pos(f.Pos) w.localIdent(origSym(f.Sym), 0) w.typ(f.Type) } func constTypeOf(typ *types.Type) Ctype { switch typ { case types.UntypedInt, types.UntypedRune: return CTINT case types.UntypedFloat: return CTFLT case types.UntypedComplex: return CTCPLX } switch typ.Etype { case TCHAN, TFUNC, TMAP, TNIL, TINTER, TPTR, TSLICE, TUNSAFEPTR: return CTNIL case TBOOL: return CTBOOL case TSTRING: return CTSTR case TINT, TINT8, TINT16, TINT32, TINT64, TUINT, TUINT8, TUINT16, TUINT32, TUINT64, TUINTPTR: return CTINT case TFLOAT32, TFLOAT64: return CTFLT case TCOMPLEX64, TCOMPLEX128: return CTCPLX } Fatalf("unexpected constant type: %v", typ) return 0 } func (w *exportWriter) value(typ *types.Type, v Val) { if vt := idealType(v.Ctype()); typ.IsUntyped() && typ != vt { Fatalf("exporter: untyped type mismatch, have: %v, want: %v", typ, vt) } w.typ(typ) // Each type has only one admissible constant representation, // so we could type switch directly on v.U here. However, // switching on the type increases symmetry with import logic // and provides a useful consistency check. switch constTypeOf(typ) { case CTNIL: // Only one value; nothing to encode. _ = v.U.(*NilVal) case CTBOOL: w.bool(v.U.(bool)) case CTSTR: w.string(v.U.(string)) case CTINT: w.mpint(&v.U.(*Mpint).Val, typ) case CTFLT: w.mpfloat(&v.U.(*Mpflt).Val, typ) case CTCPLX: x := v.U.(*Mpcplx) w.mpfloat(&x.Real.Val, typ) w.mpfloat(&x.Imag.Val, typ) } } func intSize(typ *types.Type) (signed bool, maxBytes uint) { if typ.IsUntyped() { return true, Mpprec / 8 } switch typ.Etype { case TFLOAT32, TCOMPLEX64: return true, 3 case TFLOAT64, TCOMPLEX128: return true, 7 } signed = typ.IsSigned() maxBytes = uint(typ.Size()) // The go/types API doesn't expose sizes to importers, so they // don't know how big these types are. switch typ.Etype { case TINT, TUINT, TUINTPTR: maxBytes = 8 } return } // mpint exports a multi-precision integer. // // For unsigned types, small values are written out as a single // byte. Larger values are written out as a length-prefixed big-endian // byte string, where the length prefix is encoded as its complement. // For example, bytes 0, 1, and 2 directly represent the integer // values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, // 2-, and 3-byte big-endian string follow. // // Encoding for signed types use the same general approach as for // unsigned types, except small values use zig-zag encoding and the // bottom bit of length prefix byte for large values is reserved as a // sign bit. // // The exact boundary between small and large encodings varies // according to the maximum number of bytes needed to encode a value // of type typ. As a special case, 8-bit types are always encoded as a // single byte. // // TODO(mdempsky): Is this level of complexity really worthwhile? func (w *exportWriter) mpint(x *big.Int, typ *types.Type) { signed, maxBytes := intSize(typ) negative := x.Sign() < 0 if !signed && negative { Fatalf("negative unsigned integer; type %v, value %v", typ, x) } b := x.Bytes() if len(b) > 0 && b[0] == 0 { Fatalf("leading zeros") } if uint(len(b)) > maxBytes { Fatalf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x) } maxSmall := 256 - maxBytes if signed { maxSmall = 256 - 2*maxBytes } if maxBytes == 1 { maxSmall = 256 } // Check if x can use small value encoding. if len(b) <= 1 { var ux uint if len(b) == 1 { ux = uint(b[0]) } if signed { ux <<= 1 if negative { ux-- } } if ux < maxSmall { w.data.WriteByte(byte(ux)) return } } n := 256 - uint(len(b)) if signed { n = 256 - 2*uint(len(b)) if negative { n |= 1 } } if n < maxSmall || n >= 256 { Fatalf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n) } w.data.WriteByte(byte(n)) w.data.Write(b) } // mpfloat exports a multi-precision floating point number. // // The number's value is decomposed into mantissa × 2**exponent, where // mantissa is an integer. The value is written out as mantissa (as a // multi-precision integer) and then the exponent, except exponent is // omitted if mantissa is zero. func (w *exportWriter) mpfloat(f *big.Float, typ *types.Type) { if f.IsInf() { Fatalf("infinite constant") } // Break into f = mant × 2**exp, with 0.5 <= mant < 1. var mant big.Float exp := int64(f.MantExp(&mant)) // Scale so that mant is an integer. prec := mant.MinPrec() mant.SetMantExp(&mant, int(prec)) exp -= int64(prec) manti, acc := mant.Int(nil) if acc != big.Exact { Fatalf("mantissa scaling failed for %f (%s)", f, acc) } w.mpint(manti, typ) if manti.Sign() != 0 { w.int64(exp) } } func (w *exportWriter) bool(b bool) bool { var x uint64 if b { x = 1 } w.uint64(x) return b } func (w *exportWriter) int64(x int64) { w.data.int64(x) } func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } // Compiler-specific extensions. func (w *exportWriter) varExt(n *Node) { w.linkname(n.Sym) w.symIdx(n.Sym) } func (w *exportWriter) funcExt(n *Node) { w.linkname(n.Sym) w.symIdx(n.Sym) // Escape analysis. for _, fs := range &types.RecvsParams { for _, f := range fs(n.Type).FieldSlice() { w.string(f.Note) } } // Inline body. if n.Func.Inl != nil { w.uint64(1 + uint64(n.Func.Inl.Cost)) if n.Func.ExportInline() { w.p.doInline(n) } // Endlineno for inlined function. if n.Name.Defn != nil { w.pos(n.Name.Defn.Func.Endlineno) } else { // When the exported node was defined externally, // e.g. io exports atomic.(*Value).Load or bytes exports errors.New. // Keep it as we don't distinguish this case in iimport.go. w.pos(n.Func.Endlineno) } } else { w.uint64(0) } } func (w *exportWriter) methExt(m *types.Field) { w.bool(m.Nointerface()) w.funcExt(asNode(m.Type.Nname())) } func (w *exportWriter) linkname(s *types.Sym) { w.string(s.Linkname) } func (w *exportWriter) symIdx(s *types.Sym) { lsym := s.Linksym() if lsym.PkgIdx > goobj.PkgIdxSelf || (lsym.PkgIdx == goobj.PkgIdxInvalid && !lsym.Indexed()) || s.Linkname != "" { // Don't export index for non-package symbols, linkname'd symbols, // and symbols without an index. They can only be referenced by // name. w.int64(-1) } else { // For a defined symbol, export its index. // For re-exporting an imported symbol, pass its index through. w.int64(int64(lsym.SymIdx)) } } func (w *exportWriter) typeExt(t *types.Type) { // Export whether this type is marked notinheap. w.bool(t.NotInHeap()) // For type T, export the index of type descriptor symbols of T and *T. if i, ok := typeSymIdx[t]; ok { w.int64(i[0]) w.int64(i[1]) return } w.symIdx(typesym(t)) w.symIdx(typesym(t.PtrTo())) } // Inline bodies. func (w *exportWriter) stmtList(list Nodes) { for _, n := range list.Slice() { w.node(n) } w.op(OEND) } func (w *exportWriter) node(n *Node) { if opprec[n.Op] < 0 { w.stmt(n) } else { w.expr(n) } } // Caution: stmt will emit more than one node for statement nodes n that have a non-empty // n.Ninit and where n cannot have a natural init section (such as in "if", "for", etc.). func (w *exportWriter) stmt(n *Node) { if n.Ninit.Len() > 0 && !stmtwithinit(n.Op) { // can't use stmtList here since we don't want the final OEND for _, n := range n.Ninit.Slice() { w.stmt(n) } } switch op := n.Op; op { case ODCL: w.op(ODCL) w.pos(n.Left.Pos) w.localName(n.Left) w.typ(n.Left.Type) // case ODCLFIELD: // unimplemented - handled by default case case OAS: // Don't export "v = " initializing statements, hope they're always // preceded by the DCL which will be re-parsed and typecheck to reproduce // the "v = " again. if n.Right != nil { w.op(OAS) w.pos(n.Pos) w.expr(n.Left) w.expr(n.Right) } case OASOP: w.op(OASOP) w.pos(n.Pos) w.op(n.SubOp()) w.expr(n.Left) if w.bool(!n.Implicit()) { w.expr(n.Right) } case OAS2: w.op(OAS2) w.pos(n.Pos) w.exprList(n.List) w.exprList(n.Rlist) case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: w.op(OAS2) w.pos(n.Pos) w.exprList(n.List) w.exprList(asNodes([]*Node{n.Right})) case ORETURN: w.op(ORETURN) w.pos(n.Pos) w.exprList(n.List) // case ORETJMP: // unreachable - generated by compiler for trampolin routines case OGO, ODEFER: w.op(op) w.pos(n.Pos) w.expr(n.Left) case OIF: w.op(OIF) w.pos(n.Pos) w.stmtList(n.Ninit) w.expr(n.Left) w.stmtList(n.Nbody) w.stmtList(n.Rlist) case OFOR: w.op(OFOR) w.pos(n.Pos) w.stmtList(n.Ninit) w.exprsOrNil(n.Left, n.Right) w.stmtList(n.Nbody) case ORANGE: w.op(ORANGE) w.pos(n.Pos) w.stmtList(n.List) w.expr(n.Right) w.stmtList(n.Nbody) case OSELECT, OSWITCH: w.op(op) w.pos(n.Pos) w.stmtList(n.Ninit) w.exprsOrNil(n.Left, nil) w.caseList(n) // case OCASE: // handled by caseList case OFALL: w.op(OFALL) w.pos(n.Pos) case OBREAK, OCONTINUE: w.op(op) w.pos(n.Pos) w.exprsOrNil(n.Left, nil) case OEMPTY: // nothing to emit case OGOTO, OLABEL: w.op(op) w.pos(n.Pos) w.string(n.Sym.Name) default: Fatalf("exporter: CANNOT EXPORT: %v\nPlease notify gri@\n", n.Op) } } func (w *exportWriter) caseList(sw *Node) { namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil cases := sw.List.Slice() w.uint64(uint64(len(cases))) for _, cas := range cases { if cas.Op != OCASE { Fatalf("expected OCASE, got %v", cas) } w.pos(cas.Pos) w.stmtList(cas.List) if namedTypeSwitch { w.localName(cas.Rlist.First()) } w.stmtList(cas.Nbody) } } func (w *exportWriter) exprList(list Nodes) { for _, n := range list.Slice() { w.expr(n) } w.op(OEND) } func (w *exportWriter) expr(n *Node) { // from nodefmt (fmt.go) // // nodefmt reverts nodes back to their original - we don't need to do // it because we are not bound to produce valid Go syntax when exporting // // if (fmtmode != FExp || n.Op != OLITERAL) && n.Orig != nil { // n = n.Orig // } // from exprfmt (fmt.go) for n.Op == OPAREN || n.Implicit() && (n.Op == ODEREF || n.Op == OADDR || n.Op == ODOT || n.Op == ODOTPTR) { n = n.Left } switch op := n.Op; op { // expressions // (somewhat closely following the structure of exprfmt in fmt.go) case OLITERAL: if n.Val().Ctype() == CTNIL && n.Orig != nil && n.Orig != n { w.expr(n.Orig) break } w.op(OLITERAL) w.pos(n.Pos) w.value(n.Type, n.Val()) case ONAME: // Special case: explicit name of func (*T) method(...) is turned into pkg.(*T).method, // but for export, this should be rendered as (*pkg.T).meth. // These nodes have the special property that they are names with a left OTYPE and a right ONAME. if n.isMethodExpression() { w.op(OXDOT) w.pos(n.Pos) w.expr(n.Left) // n.Left.Op == OTYPE w.selector(n.Right.Sym) break } // Package scope name. if (n.Class() == PEXTERN || n.Class() == PFUNC) && !n.isBlank() { w.op(ONONAME) w.qualifiedIdent(n) break } // Function scope name. w.op(ONAME) w.localName(n) // case OPACK, ONONAME: // should have been resolved by typechecking - handled by default case case OTYPE: w.op(OTYPE) w.typ(n.Type) case OTYPESW: w.op(OTYPESW) w.pos(n.Pos) var s *types.Sym if n.Left != nil { if n.Left.Op != ONONAME { Fatalf("expected ONONAME, got %v", n.Left) } s = n.Left.Sym } w.localIdent(s, 0) // declared pseudo-variable, if any w.exprsOrNil(n.Right, nil) // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: // should have been resolved by typechecking - handled by default case // case OCLOSURE: // unimplemented - handled by default case // case OCOMPLIT: // should have been resolved by typechecking - handled by default case case OPTRLIT: w.op(OADDR) w.pos(n.Pos) w.expr(n.Left) case OSTRUCTLIT: w.op(OSTRUCTLIT) w.pos(n.Pos) w.typ(n.Type) w.elemList(n.List) // special handling of field names case OARRAYLIT, OSLICELIT, OMAPLIT: w.op(OCOMPLIT) w.pos(n.Pos) w.typ(n.Type) w.exprList(n.List) case OKEY: w.op(OKEY) w.pos(n.Pos) w.exprsOrNil(n.Left, n.Right) // case OSTRUCTKEY: // unreachable - handled in case OSTRUCTLIT by elemList case OCALLPART: // An OCALLPART is an OXDOT before type checking. w.op(OXDOT) w.pos(n.Pos) w.expr(n.Left) // Right node should be ONAME w.selector(n.Right.Sym) case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: w.op(OXDOT) w.pos(n.Pos) w.expr(n.Left) w.selector(n.Sym) case ODOTTYPE, ODOTTYPE2: w.op(ODOTTYPE) w.pos(n.Pos) w.expr(n.Left) w.typ(n.Type) case OINDEX, OINDEXMAP: w.op(OINDEX) w.pos(n.Pos) w.expr(n.Left) w.expr(n.Right) case OSLICE, OSLICESTR, OSLICEARR: w.op(OSLICE) w.pos(n.Pos) w.expr(n.Left) low, high, _ := n.SliceBounds() w.exprsOrNil(low, high) case OSLICE3, OSLICE3ARR: w.op(OSLICE3) w.pos(n.Pos) w.expr(n.Left) low, high, max := n.SliceBounds() w.exprsOrNil(low, high) w.expr(max) case OCOPY, OCOMPLEX: // treated like other builtin calls (see e.g., OREAL) w.op(op) w.pos(n.Pos) w.expr(n.Left) w.expr(n.Right) w.op(OEND) case OCONV, OCONVIFACE, OCONVNOP, OBYTES2STR, ORUNES2STR, OSTR2BYTES, OSTR2RUNES, ORUNESTR: w.op(OCONV) w.pos(n.Pos) w.expr(n.Left) w.typ(n.Type) case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: w.op(op) w.pos(n.Pos) if n.Left != nil { w.expr(n.Left) w.op(OEND) } else { w.exprList(n.List) // emits terminating OEND } // only append() calls may contain '...' arguments if op == OAPPEND { w.bool(n.IsDDD()) } else if n.IsDDD() { Fatalf("exporter: unexpected '...' with %v call", op) } case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: w.op(OCALL) w.pos(n.Pos) w.stmtList(n.Ninit) w.expr(n.Left) w.exprList(n.List) w.bool(n.IsDDD()) case OMAKEMAP, OMAKECHAN, OMAKESLICE: w.op(op) // must keep separate from OMAKE for importer w.pos(n.Pos) w.typ(n.Type) switch { default: // empty list w.op(OEND) case n.List.Len() != 0: // pre-typecheck w.exprList(n.List) // emits terminating OEND case n.Right != nil: w.expr(n.Left) w.expr(n.Right) w.op(OEND) case n.Left != nil && (n.Op == OMAKESLICE || !n.Left.Type.IsUntyped()): w.expr(n.Left) w.op(OEND) } // unary expressions case OPLUS, ONEG, OADDR, OBITNOT, ODEREF, ONOT, ORECV: w.op(op) w.pos(n.Pos) w.expr(n.Left) // binary expressions case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR: w.op(op) w.pos(n.Pos) w.expr(n.Left) w.expr(n.Right) case OADDSTR: w.op(OADDSTR) w.pos(n.Pos) w.exprList(n.List) case ODCLCONST: // if exporting, DCLCONST should just be removed as its usage // has already been replaced with literals default: Fatalf("cannot export %v (%d) node\n"+ "\t==> please file an issue and assign to gri@", n.Op, int(n.Op)) } } func (w *exportWriter) op(op Op) { w.uint64(uint64(op)) } func (w *exportWriter) exprsOrNil(a, b *Node) { ab := 0 if a != nil { ab |= 1 } if b != nil { ab |= 2 } w.uint64(uint64(ab)) if ab&1 != 0 { w.expr(a) } if ab&2 != 0 { w.node(b) } } func (w *exportWriter) elemList(list Nodes) { w.uint64(uint64(list.Len())) for _, n := range list.Slice() { w.selector(n.Sym) w.expr(n.Left) } } func (w *exportWriter) localName(n *Node) { // Escape analysis happens after inline bodies are saved, but // we're using the same ONAME nodes, so we might still see // PAUTOHEAP here. // // Check for Stackcopy to identify PAUTOHEAP that came from // PPARAM/PPARAMOUT, because we only want to include vargen in // non-param names. var v int32 if n.Class() == PAUTO || (n.Class() == PAUTOHEAP && n.Name.Param.Stackcopy == nil) { v = n.Name.Vargen } w.localIdent(n.Sym, v) } func (w *exportWriter) localIdent(s *types.Sym, v int32) { // Anonymous parameters. if s == nil { w.string("") return } name := s.Name if name == "_" { w.string("_") return } // TODO(mdempsky): Fix autotmp hack. if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") { Fatalf("unexpected dot in identifier: %v", name) } if v > 0 { if strings.Contains(name, "·") { Fatalf("exporter: unexpected · in symbol name") } name = fmt.Sprintf("%s·%d", name, v) } if !types.IsExported(name) && s.Pkg != w.currPkg { Fatalf("weird package in name: %v => %v, not %q", s, name, w.currPkg.Path) } w.string(name) } type intWriter struct { bytes.Buffer } func (w *intWriter) int64(x int64) { var buf [binary.MaxVarintLen64]byte n := binary.PutVarint(buf[:], x) w.Write(buf[:n]) } func (w *intWriter) uint64(x uint64) { var buf [binary.MaxVarintLen64]byte n := binary.PutUvarint(buf[:], x) w.Write(buf[:n]) }