From a9ea36afbbf39555b7a215651535f0eef89db37c Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 18 Mar 2016 17:21:32 -0700 Subject: [PATCH] cmd/compile: export inlined function bodies Completed implementation for exporting inlined functions using the new binary export format. This change passes (export GO_GCFLAGS=-newexport; make all.bash) but for gc's builtin_test.go which we need to adjust before enabling this code by default. For a high-level description of the export format see the comment at the top of bexport.go. Major changes: 1) The export format for the platform independent export data changed: When we export inlined function bodies, additional objects (other functions, types, etc.) that are referred to by the function bodies will need to be exported. While this doesn't affect the platform-independent portion directly, it adds more objects to the exportlist while we are exporting. Instead of trying to sort the objects into groups, just export objects as they appear in the export list. This is slightly less compact (one extra byte per object), but it is simpler and much more flexible. 2) The export format contains now three sections: 1) The plat- form independent objects, 2) the objects pulled in for export via inlined function bodies, and 3) the inlined function bodies. 3) Completed the exporting and importing code for inlined function bodies. The format is completely compiler-specific and easily changeable w/o affecting other tools. There is still quite a bit of room for denser encoding. This can happen at any time in the future. This change contains also the adjustments for go/internal/gcimporter, necessary because of the export format change 1) mentioned above. For #13241. Change-Id: I86bca0bd984b12ccf13d0d30892e6e25f6d04ed5 Reviewed-on: https://go-review.googlesource.com/21172 Run-TryBot: Robert Griesemer Reviewed-by: Matthew Dempsky TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/bexport.go | 1109 ++++++++++++++++-------- src/cmd/compile/internal/gc/bimport.go | 730 +++++++++++----- src/cmd/compile/internal/gc/dcl.go | 24 +- src/cmd/compile/internal/gc/export.go | 10 +- src/cmd/compile/internal/gc/fmt.go | 6 +- src/cmd/compile/internal/gc/inl.go | 2 +- src/cmd/compile/internal/gc/main.go | 3 + src/cmd/compile/internal/gc/parser.go | 2 +- src/cmd/compile/internal/gc/pgen.go | 6 + src/go/internal/gcimporter/bimport.go | 141 +-- 10 files changed, 1373 insertions(+), 660 deletions(-) diff --git a/src/cmd/compile/internal/gc/bexport.go b/src/cmd/compile/internal/gc/bexport.go index a63f615267..8968ce8924 100644 --- a/src/cmd/compile/internal/gc/bexport.go +++ b/src/cmd/compile/internal/gc/bexport.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. // Binary package export. -// Based loosely on x/tools/go/importer. // (see fmt.go, parser.go as "documentation" for how to use/setup data structures) // // Use "-newexport" flag to enable. @@ -12,20 +11,30 @@ Export data encoding: The export data is a serialized description of the graph of exported -objects: constants, types, variables, and functions. Only types can -be re-exported and so we need to know which package they are coming +"objects": constants, types, variables, and functions. In general, +types - but also objects referred to from inlined function bodies - +can be reexported and so we need to know which package they are coming from. Therefore, packages are also part of the export graph. -The roots of the graph are the list of constants, variables, functions, -and eventually types. Types are written last because most of them will -be written as part of other objects which will reduce the number of -types that need to be written separately. +The roots of the graph are two lists of objects. The 1st list (phase 1, +see Export) contains all objects that are exported at the package level. +These objects are the full representation of the package's API, and they +are the only information a platform-independent tool (e.g., go/types) +needs to know to type-check against a package. + +The 2nd list of objects contains all objects referred to from exported +inlined function bodies. These objects are needed by the compiler to +make sense of the function bodies; the exact list contents are compiler- +specific. + +Finally, the export data contains a list of representations for inlined +function bodies. The format of this representation is compiler specific. The graph is serialized in in-order fashion, starting with the roots. Each object in the graph is serialized by writing its fields sequentially. If the field is a pointer to another object, that object is serialized, recursively. Otherwise the field is written. Non-pointer fields are all -encoded as either an integer or string value. +encoded as integer or string values. Only packages and types may be referred to more than once. When getting to a package or type that was not serialized before, an integer _index_ @@ -43,6 +52,9 @@ Before exporting or importing, the type tables are populated with the predeclared types (int, string, error, unsafe.Pointer, etc.). This way they are automatically encoded with a known and fixed type index. +TODO(gri) We may consider using the same sharing for other items +that are written out, such as strings, or possibly symbols (*Sym). + Encoding format: The export data starts with a single byte indicating the encoding format @@ -51,8 +63,8 @@ The export data starts with a single byte indicating the encoding format package, and a string containing platform-specific information for that package. -After this header, the lists of objects follow. After the objects, platform- -specific data may be found which is not used strictly for type checking. +After this header, two lists of objects and the list of inlined function +bodies follows. The encoding of objects is straight-forward: Constants, variables, and functions start with their name, type, and possibly a value. Named types @@ -62,21 +74,12 @@ the previously imported type pointer so that we have exactly one version (i.e., one pointer) for each named type (and read but discard the current type encoding). Unnamed types simply encode their respective fields. -In the encoding, any list (of objects, struct fields, methods, parameter -names, but also the bytes of a string, etc.) starts with the list length. -This permits an importer to allocate the right amount of memory for the -list upfront, without the need to grow it later. +In the encoding, some lists start with the list length (incl. strings). +Some lists are terminated with an end marker (usually for lists where +we may not know the length a priori). All integer values use variable-length encoding for compact representation. -If debugFormat is set, each integer and string value is preceded by a marker -and position information in the encoding. This mechanism permits an importer -to recognize immediately when it is out of sync. The importer recognizes this -mode automatically (i.e., it can import export data produced with debugging -support even if debugFormat is not set at the time of import). This mode will -lead to massively larger export data (by a factor of 2 to 3) and should only -be enabled during development and debugging. - The exporter and importer are completely symmetric in implementation: For each encoding routine there is a matching and symmetric decoding routine. This symmetry makes it very easy to change or extend the format: If a new @@ -96,17 +99,45 @@ import ( "strings" ) -// debugging support -const ( - debugFormat = false // use debugging format for export data (emits a lot of additional data) -) +// If debugFormat is set, each integer and string value is preceded by a marker +// and position information in the encoding. This mechanism permits an importer +// to recognize immediately when it is out of sync. The importer recognizes this +// mode automatically (i.e., it can import export data produced with debugging +// support even if debugFormat is not set at the time of import). This mode will +// lead to massively larger export data (by a factor of 2 to 3) and should only +// be enabled during development and debugging. +// +// NOTE: This flag is the first flag to enable if importing dies because of +// (suspected) format errors, and whenever a change is made to the format. +// Having debugFormat enabled increases the export data size massively (by +// several factors) - avoid running with the flag enabled in general. +const debugFormat = false // default: false // TODO(gri) remove eventually -const forceNewExport = false // force new export format - do not submit with this flag set +const forceNewExport = false // force new export format - DO NOT SUBMIT with this flag set const exportVersion = "v0" -// Export writes the export data for localpkg to out and returns the number of bytes written. +// exportInlined enables the export of inlined function bodies and related +// dependencies. The compiler should work w/o any loss of functionality with +// the flag disabled, but the generated code will lose access to inlined +// function bodies across packages, leading to performance bugs. +// Leave for debugging. +const exportInlined = true // default: true + +type exporter struct { + out *obj.Biobuf + pkgIndex map[*Pkg]int + typIndex map[*Type]int + inlined []*Func + + // debugging support + written int // bytes written + indent int // for p.trace + trace bool +} + +// Export writes the exportlist for localpkg to out and returns the number of bytes written. func Export(out *obj.Biobuf, trace bool) int { p := exporter{ out: out, @@ -115,7 +146,7 @@ func Export(out *obj.Biobuf, trace bool) int { trace: trace, } - // write low-level encoding format + // first byte indicates low-level encoding format var format byte = 'c' // compact if debugFormat { format = 'd' @@ -155,6 +186,7 @@ func Export(out *obj.Biobuf, trace bool) int { p.pkg(localpkg) // write compiler-specific flags + // TODO(gri) move this into the compiler-specific export data section { var flags string if safemode != 0 { @@ -166,13 +198,31 @@ func Export(out *obj.Biobuf, trace bool) int { p.tracef("\n") } - // collect objects to export - var consts, vars, funcs []*Sym - var types []*Type + // export objects + + // First, export all exported (package-level) objects; i.e., all objects + // in the current exportlist. These objects represent all information + // required to import this package and type-check against it; i.e., this + // is the platform-independent export data. The format is generic in the + // sense that different compilers can use the same representation. + // + // During this first phase, more objects may be added to the exportlist + // (due to inlined function bodies and their dependencies). Export those + // objects in a second phase. That data is platform-specific as it depends + // on the inlining decisions of the compiler and the representation of the + // inlined function bodies. + + // remember initial exportlist length + var numglobals = len(exportlist) + + // Phase 1: Export objects in _current_ exportlist; exported objects at + // package level. + // Use range since we want to ignore objects added to exportlist during + // this phase. + objcount := 0 for _, n := range exportlist { sym := n.Sym - // TODO(gri) Closures appear marked as exported. - // Investigate and determine if we need this. + if sym.Flags&SymExported != 0 { continue } @@ -180,147 +230,102 @@ func Export(out *obj.Biobuf, trace bool) int { // TODO(gri) Closures have dots in their names; // e.g., TestFloatZeroValue.func1 in math/big tests. - // We may not need this eventually. See also comment - // on sym.Flags&SymExported test above. if strings.Contains(sym.Name, ".") { Fatalf("exporter: unexpected symbol: %v", sym) } - if sym.Flags&SymExport != 0 { - if sym.Def == nil { - Fatalf("exporter: unknown export symbol: %v", sym) - } - switch n := sym.Def; n.Op { - case OLITERAL: - // constant - n = typecheck(n, Erv) - if n == nil || n.Op != OLITERAL { - Fatalf("exporter: dumpexportconst: oconst nil: %v", sym) - } - consts = append(consts, sym) - - case ONAME: - // variable or function - n = typecheck(n, Erv|Ecall) - if n == nil || n.Type == nil { - Fatalf("exporter: variable/function exported but not defined: %v", sym) - } - if n.Type.Etype == TFUNC && n.Class == PFUNC { - funcs = append(funcs, sym) - } else { - vars = append(vars, sym) - } - - case OTYPE: - // named type - t := n.Type - if t.Etype == TFORW { - Fatalf("exporter: export of incomplete type %v", sym) - } - types = append(types, t) + // TODO(gri) Should we do this check? + // if sym.Flags&SymExport == 0 { + // continue + // } - default: - Fatalf("exporter: unexpected export symbol: %v %v", Oconv(n.Op, 0), sym) - } + if sym.Def == nil { + Fatalf("exporter: unknown export symbol: %v", sym) } - } - exportlist = nil // match export.go use of exportlist - // for reproducible output - sort.Sort(symByName(consts)) - sort.Sort(symByName(vars)) - sort.Sort(symByName(funcs)) - // sort types later when we have fewer types left + // TODO(gri) Optimization: Probably worthwhile collecting + // long runs of constants and export them "in bulk" (saving + // tags and types, and making import faster). - // write consts - if p.trace { - p.tracef("\n--- consts ---\n[ ") - } - p.int(len(consts)) - if p.trace { - p.tracef("]\n") - } - for _, sym := range consts { - p.string(sym.Name) - n := sym.Def - p.typ(unidealType(n.Type, n.Val())) - p.value(n.Val()) if p.trace { p.tracef("\n") } + p.obj(sym) + objcount++ } - // write vars + // indicate end of list if p.trace { - p.tracef("\n--- vars ---\n[ ") + p.tracef("\n") } - p.int(len(vars)) + p.tag(endTag) + + // for self-verification only (redundant) + p.int(objcount) + + // --- compiler-specific export data --- + if p.trace { - p.tracef("]\n") - } - for _, sym := range vars { - p.string(sym.Name) - p.typ(sym.Def.Type) - if p.trace { - p.tracef("\n") + p.tracef("\n--- compiler-specific export data ---\n[ ") + if p.indent != 0 { + Fatalf("exporter: incorrect indentation") } } - // write funcs - if p.trace { - p.tracef("\n--- funcs ---\n[ ") - } - p.int(len(funcs)) - if p.trace { - p.tracef("]\n") - } - for _, sym := range funcs { - p.string(sym.Name) - sig := sym.Def.Type - inlineable := p.isInlineable(sym.Def) - p.paramList(sig.Params(), inlineable) - p.paramList(sig.Results(), inlineable) - index := -1 - if inlineable { - index = len(p.inlined) - p.inlined = append(p.inlined, sym.Def.Func) - } - p.int(index) - if p.trace { - p.tracef("\n") + // Phase 2: Export objects added to exportlist during phase 1. + // Don't use range since exportlist may grow during this phase + // and we want to export all remaining objects. + objcount = 0 + for i := numglobals; exportInlined && i < len(exportlist); i++ { + n := exportlist[i] + sym := n.Sym + + // TODO(gri) The rest of this loop body is identical with + // the loop body above. Leave alone for now since there + // are different optimization opportunities, but factor + // eventually. + + if sym.Flags&SymExported != 0 { + continue } - } + sym.Flags |= SymExported + + // TODO(gri) Closures have dots in their names; + // e.g., TestFloatZeroValue.func1 in math/big tests. + if strings.Contains(sym.Name, ".") { + Fatalf("exporter: unexpected symbol: %v", sym) + } + + // TODO(gri) Should we do this check? + // if sym.Flags&SymExport == 0 { + // continue + // } - // determine which types are still left to write and sort them - i := 0 - for _, t := range types { - if _, ok := p.typIndex[t]; !ok { - types[i] = t - i++ + if sym.Def == nil { + Fatalf("exporter: unknown export symbol: %v", sym) } - } - types = types[:i] - sort.Sort(typByName(types)) - // write types - if p.trace { - p.tracef("\n--- types ---\n[ ") - } - p.int(len(types)) - if p.trace { - p.tracef("]\n") - } - for _, t := range types { - // Writing a type may further reduce the number of types - // that are left to be written, but at this point we don't - // care. - p.typ(t) + // TODO(gri) Optimization: Probably worthwhile collecting + // long runs of constants and export them "in bulk" (saving + // tags and types, and making import faster). + if p.trace { p.tracef("\n") } + p.obj(sym) + objcount++ } - // --- compiler-specific export data --- + // indicate end of list + if p.trace { + p.tracef("\n") + } + p.tag(endTag) + + // for self-verification only (redundant) + p.int(objcount) + + // --- inlined function bodies --- if p.trace { p.tracef("\n--- inlined function bodies ---\n[ ") @@ -336,9 +341,9 @@ func Export(out *obj.Biobuf, trace bool) int { } for _, f := range p.inlined { if p.trace { - p.tracef("{ %s }\n", Hconv(f.Inl, FmtSharp)) + p.tracef("\n----\nfunc { %s }\n", Hconv(f.Inl, FmtSharp)) } - p.nodeList(f.Inl) + p.stmtList(f.Inl) if p.trace { p.tracef("\n") } @@ -353,38 +358,6 @@ func Export(out *obj.Biobuf, trace bool) int { return p.written } -func unidealType(typ *Type, val Val) *Type { - // Untyped (ideal) constants get their own type. This decouples - // the constant type from the encoding of the constant value. - if typ == nil || typ.IsUntyped() { - typ = untype(val.Ctype()) - } - return typ -} - -type symByName []*Sym - -func (a symByName) Len() int { return len(a) } -func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type typByName []*Type - -func (a typByName) Len() int { return len(a) } -func (a typByName) Less(i, j int) bool { return a[i].Sym.Name < a[j].Sym.Name } -func (a typByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type exporter struct { - out *obj.Biobuf - pkgIndex map[*Pkg]int - typIndex map[*Type]int - inlined []*Func - - written int // bytes written - indent int // for p.trace - trace bool -} - func (p *exporter) pkg(pkg *Pkg) { if pkg == nil { Fatalf("exporter: unexpected nil pkg") @@ -408,6 +381,112 @@ func (p *exporter) pkg(pkg *Pkg) { p.string(pkg.Path) } +func unidealType(typ *Type, val Val) *Type { + // Untyped (ideal) constants get their own type. This decouples + // the constant type from the encoding of the constant value. + if typ == nil || typ.IsUntyped() { + typ = untype(val.Ctype()) + } + return typ +} + +func (p *exporter) obj(sym *Sym) { + // Exported objects may be from different packages because they + // may be re-exported as depencies when exporting inlined function + // bodies. Thus, exported object names must be fully qualified. + // + // TODO(gri) This can only happen if exportInlined is enabled + // (default), and during phase 2 of object export. Objects exported + // in phase 1 (compiler-indendepent objects) are by definition only + // the objects from the current package and not pulled in via inlined + // function bodies. In that case the package qualifier is not needed. + // Possible space optimization. + + n := sym.Def + switch n.Op { + case OLITERAL: + // constant + // TODO(gri) determine if we need the typecheck call here + n = typecheck(n, Erv) + if n == nil || n.Op != OLITERAL { + Fatalf("exporter: dumpexportconst: oconst nil: %v", sym) + } + + p.tag(constTag) + // TODO(gri) In inlined functions, constants are used directly + // so they should never occur as re-exported objects. We may + // not need the qualified name here. See also comment above. + // Possible space optimization. + p.qualifiedName(sym) + p.typ(unidealType(n.Type, n.Val())) + p.value(n.Val()) + + case OTYPE: + // named type + t := n.Type + if t.Etype == TFORW { + Fatalf("exporter: export of incomplete type %v", sym) + } + + p.tag(typeTag) + p.typ(t) + + case ONAME: + // variable or function + n = typecheck(n, Erv|Ecall) + if n == nil || n.Type == nil { + Fatalf("exporter: variable/function exported but not defined: %v", sym) + } + + if n.Type.Etype == TFUNC && n.Class == PFUNC { + // function + p.tag(funcTag) + p.qualifiedName(sym) + + sig := sym.Def.Type + inlineable := isInlineable(sym.Def) + + p.paramList(sig.Params(), inlineable) + p.paramList(sig.Results(), inlineable) + + index := -1 + if inlineable { + index = len(p.inlined) + p.inlined = append(p.inlined, sym.Def.Func) + // TODO(gri) re-examine reexportdeplist: + // Because we can trivially export types + // in-place, we don't need to collect types + // inside function bodies in the exportlist. + // With an adjusted reexportdeplist used only + // by the binary exporter, we can also avoid + // the global exportlist. + reexportdeplist(sym.Def.Func.Inl) + } + p.int(index) + } else { + // variable + p.tag(varTag) + p.qualifiedName(sym) + p.typ(sym.Def.Type) + } + + default: + Fatalf("exporter: unexpected export symbol: %v %v", Oconv(n.Op, 0), sym) + } +} + +func isInlineable(n *Node) bool { + if exportInlined && n != nil && n.Func != nil && len(n.Func.Inl.Slice()) != 0 { + // when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet. + // currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package + if Debug['l'] < 2 { + typecheckinl(n) + } + return true + } + return false +} + func (p *exporter) typ(t *Type) { if t == nil { Fatalf("exporter: nil type") @@ -442,13 +521,10 @@ func (p *exporter) typ(t *Type) { Fatalf("exporter: predeclared type missing from type map?") } // TODO(gri) The assertion below seems incorrect (crashes during all.bash). - // Investigate. - /* - // we expect the respective definition to point to us - if sym.Def.Type != t { - Fatalf("exporter: type definition doesn't point to us?") - } - */ + // we expect the respective definition to point to us + // if sym.Def.Type != t { + // Fatalf("exporter: type definition doesn't point to us?") + // } p.tag(namedTag) p.qualifiedName(sym) @@ -479,16 +555,25 @@ func (p *exporter) typ(t *Type) { if p.trace { p.tracef("\n") } - p.string(m.Sym.Name) + if strings.Contains(m.Sym.Name, ".") { + Fatalf("invalid symbol name: %s (%v)", m.Sym.Name, m.Sym) + } + + p.fieldSym(m.Sym, false) + sig := m.Type - inlineable := p.isInlineable(sig.Nname()) + mfn := sig.Nname() + inlineable := isInlineable(mfn) + p.paramList(sig.Recvs(), inlineable) p.paramList(sig.Params(), inlineable) p.paramList(sig.Results(), inlineable) + index := -1 if inlineable { index = len(p.inlined) - p.inlined = append(p.inlined, sig.Nname().Func) + p.inlined = append(p.inlined, mfn.Func) + reexportdeplist(mfn.Func.Inl) } p.int(index) } @@ -556,6 +641,9 @@ func (p *exporter) typ(t *Type) { } func (p *exporter) qualifiedName(sym *Sym) { + if strings.Contains(sym.Name, ".") { + Fatalf("exporter: invalid symbol name: %s", sym.Name) + } p.string(sym.Name) p.pkg(sym.Pkg) } @@ -576,7 +664,7 @@ func (p *exporter) fieldList(t *Type) { } func (p *exporter) field(f *Field) { - p.fieldName(f) + p.fieldName(f.Sym, f) p.typ(f.Type) p.note(f.Note) } @@ -605,28 +693,34 @@ func (p *exporter) methodList(t *Type) { } func (p *exporter) method(m *Field) { - p.fieldName(m) - // TODO(gri) For functions signatures, we use p.typ() to export - // so we could share the same type with multiple functions. Do - // the same here, or never try to do this for functions. + p.fieldName(m.Sym, m) p.paramList(m.Type.Params(), false) p.paramList(m.Type.Results(), false) } // fieldName is like qualifiedName but it doesn't record the package // for blank (_) or exported names. -func (p *exporter) fieldName(t *Field) { - sym := t.Sym - - var name string - if t.Embedded == 0 { - name = sym.Name - } else if bname := basetypeName(t.Type); bname != "" && !exportname(bname) { - // anonymous field with unexported base type name: use "?" as field name - // (bname != "" per spec, but we are conservative in case of errors) - name = "?" +func (p *exporter) fieldName(sym *Sym, t *Field) { + if t != nil && sym != t.Sym { + Fatalf("exporter: invalid fieldName parameters") + } + + name := sym.Name + if t != nil { + if t.Embedded == 0 { + name = sym.Name + } else if bname := basetypeName(t.Type); bname != "" && !exportname(bname) { + // anonymous field with unexported base type name: use "?" as field name + // (bname != "" per spec, but we are conservative in case of errors) + name = "?" + } else { + name = "" + } } + if strings.Contains(name, ".") { + Fatalf("exporter: invalid symbol name: %s", name) + } p.string(name) if name == "?" || name != "_" && name != "" && !exportname(name) { p.pkg(sym.Pkg) @@ -639,6 +733,9 @@ func basetypeName(t *Type) string { s = t.Elem().Sym // deref } if s != nil { + if strings.Contains(s.Name, ".") { + Fatalf("exporter: invalid symbol name: %s", s.Name) + } return s.Name } return "" @@ -652,6 +749,11 @@ func (p *exporter) paramList(params *Type, numbered bool) { // use negative length to indicate unnamed parameters // (look at the first parameter only since either all // names are present or all are absent) + // + // TODO(gri) If we don't have an exported function + // body, the parameter names are irrelevant for the + // compiler (though they may be of use for other tools). + // Possible space optimization. n := params.NumFields() if n > 0 && parName(params.Field(0), numbered) == "" { n = -n @@ -671,6 +773,14 @@ func (p *exporter) param(q *Field, n int, numbered bool) { p.typ(t) if n > 0 { p.string(parName(q, numbered)) + // Because of (re-)exported inlined functions + // the importpkg may not be the package to which this + // function (and thus its parameter) belongs. We need to + // supply the parameter package here. We need the package + // when the function is inlined so we can properly resolve + // the name. + // TODO(gri) should do this only once per function/method + p.pkg(q.Sym.Pkg) } // TODO(gri) This is compiler-specific (escape info). // Move into compiler-specific section eventually? @@ -681,27 +791,49 @@ func (p *exporter) param(q *Field, n int, numbered bool) { p.note(q.Note) } -func parName(q *Field, numbered bool) string { - if q.Sym == nil { +func parName(f *Field, numbered bool) string { + s := f.Sym + if s == nil { return "" } - name := q.Sym.Name - // undo gc-internal name mangling - we just need the source name - if len(name) > 0 && name[0] == '~' { - // name is ~b%d or ~r%d - switch name[1] { - case 'b': - return "_" - case 'r': - return "" - default: - Fatalf("exporter: unexpected parameter name: %s", name) + + // Take the name from the original, lest we substituted it with ~r%d or ~b%d. + // ~r%d is a (formerly) unnamed result. + if f.Nname != nil { + if f.Nname.Orig != nil { + s = f.Nname.Orig.Sym + if s != nil && s.Name[0] == '~' { + if s.Name[1] == 'r' { // originally an unnamed result + return "" // s = nil + } else if s.Name[1] == 'b' { // originally the blank identifier _ + return "_" + } + } + } else { + return "" // s = nil } } - // undo gc-internal name specialization unless required - if !numbered { + + if s == nil { + return "" + } + + // print symbol with Vargen number or not as desired + name := s.Name + if strings.Contains(name, ".") { + panic("invalid symbol name: " + name) + } + + // Functions that can be inlined use numbered parameters so we can distingish them + // from other names in their context after inlining (i.e., the parameter numbering + // is a form of parameter rewriting). See issue 4326 for an example and test case. + if numbered { + if !strings.Contains(name, "·") && f.Nname != nil && f.Nname.Name != nil && f.Nname.Name.Vargen > 0 { + name = fmt.Sprintf("%s·%d", name, f.Nname.Name.Vargen) // append Vargen + } + } else { if i := strings.Index(name, "·"); i > 0 { - name = name[:i] // cut off numbering + name = name[:i] // cut off Vargen } } return name @@ -788,19 +920,58 @@ func (p *exporter) float(x *Mpflt) { // ---------------------------------------------------------------------------- // Inlined function bodies -func (p *exporter) isInlineable(n *Node) bool { - if n != nil && n.Func != nil && len(n.Func.Inl.Slice()) != 0 { - // when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet. - // currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package - if Debug['l'] < 2 { - typecheckinl(n) +// Approach: More or less closely follow what fmt.go is doing for FExp mode +// but instead of emitting the information textually, emit the node tree in +// binary form. + +// stmtList may emit more (or fewer) than len(list) nodes. +func (p *exporter) stmtList(list Nodes) { + if p.trace { + if list.Len() == 0 { + p.tracef("{}") + } else { + p.tracef("{>") + defer p.tracef("<\n}") } - return true } - return false + + for _, n := range list.Slice() { + if p.trace { + p.tracef("\n") + } + // TODO inlining produces expressions with ninits. we can't export these yet. + // (from fmt.go:1461ff) + if opprec[n.Op] < 0 { + p.stmt(n) + } else { + p.expr(n) + } + } + + p.op(OEND) +} + +func (p *exporter) exprList(list Nodes) { + if p.trace { + if list.Len() == 0 { + p.tracef("{}") + } else { + p.tracef("{>") + defer p.tracef("<\n}") + } + } + + for _, n := range list.Slice() { + if p.trace { + p.tracef("\n") + } + p.expr(n) + } + + p.op(OEND) } -func (p *exporter) nodeList(list Nodes) { +func (p *exporter) elemList(list Nodes) { if p.trace { p.tracef("[ ") } @@ -813,189 +984,371 @@ func (p *exporter) nodeList(list Nodes) { defer p.tracef("<\n}") } } + for _, n := range list.Slice() { if p.trace { p.tracef("\n") } - p.node(n) + p.fieldSym(n.Left.Sym, false) + p.expr(n.Right) } } -func (p *exporter) node(n *Node) { - p.op(n.Op) +func (p *exporter) expr(n *Node) { + if p.trace { + p.tracef("( ") + defer p.tracef(") ") + } - switch n.Op { - // names - case ONAME, OPACK, ONONAME: - p.sym(n.Sym) + for n != nil && n.Implicit && (n.Op == OIND || n.Op == OADDR) { + n = n.Left + } - case OTYPE: - if p.bool(n.Type == nil) { - p.sym(n.Sym) - } else { - p.typ(n.Type) - } + switch op := n.Op; op { + // expressions + // (somewhat closely following the structure of exprfmt in fmt.go) + case OPAREN: + p.expr(n.Left) // unparen + + // case ODDDARG: + // unimplemented - handled by default case + + // case OREGISTER: + // unimplemented - handled by default case case OLITERAL: + if n.Val().Ctype() == CTNIL && n.Orig != nil && n.Orig != n { + p.expr(n.Orig) + break + } + p.op(OLITERAL) p.typ(unidealType(n.Type, n.Val())) p.value(n.Val()) - // expressions - case OMAKEMAP, OMAKECHAN, OMAKESLICE: - if p.bool(n.List.Len() != 0) { - p.nodeList(n.List) // TODO(gri) do we still need to export this? + case ONAME: + // Special case: name used as local variable in export. + // _ becomes ~b%d internally; print as _ for export + if n.Sym != nil && n.Sym.Name[0] == '~' && n.Sym.Name[1] == 'b' { + // case 0: mapped to ONAME + p.op(ONAME) + p.bool(true) // indicate blank identifier + break } - p.nodesOrNil(n.Left, n.Right) - p.typ(n.Type) - case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV: - p.node(n.Left) + if n.Sym != nil && !isblank(n) && n.Name.Vargen > 0 { + // case 1: mapped to OPACK + p.op(OPACK) + p.sym(n) + break + } - case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, - OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, - OSUB, OXOR: - p.node(n.Left) - p.node(n.Right) + // 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.Left != nil && n.Left.Op == OTYPE && n.Right != nil && n.Right.Op == ONAME { + // case 2: mapped to ONAME + p.op(ONAME) + // TODO(gri) can we map this case directly to OXDOT + // and then get rid of the bool here? + p.bool(false) // indicate non-blank identifier + p.typ(n.Left.Type) + p.fieldSym(n.Right.Sym, true) + break + } - case OADDSTR: - p.nodeList(n.List) + // case 3: mapped to OPACK + p.op(OPACK) + p.sym(n) // fallthrough inlined here + + case OPACK, ONONAME: + p.op(op) + p.sym(n) + + case OTYPE: + p.op(OTYPE) + if p.bool(n.Type == nil) { + p.sym(n) + } else { + p.typ(n.Type) + } + + case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: + panic("unreachable") // should have been resolved by typechecking + + // case OCLOSURE: + // unimplemented - handled by default case + + // case OCOMPLIT: + // unimplemented - handled by default case case OPTRLIT: - p.node(n.Left) + p.op(OPTRLIT) + p.expr(n.Left) + p.bool(n.Implicit) case OSTRUCTLIT: - p.typ(n.Type) - p.nodeList(n.List) - p.bool(n.Implicit) + p.op(OSTRUCTLIT) + if !p.bool(n.Implicit) { + p.typ(n.Type) + } + p.elemList(n.List) // special handling of field names case OARRAYLIT, OMAPLIT: - p.typ(n.Type) - p.nodeList(n.List) - p.bool(n.Implicit) + p.op(op) + if !p.bool(n.Implicit) { + p.typ(n.Type) + } + p.exprList(n.List) case OKEY: - p.nodesOrNil(n.Left, n.Right) + p.op(OKEY) + p.exprsOrNil(n.Left, n.Right) - case OCOPY, OCOMPLEX: - p.node(n.Left) - p.node(n.Right) + // case OCALLPART: + // unimplemented - handled by default case - case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR: - p.typ(n.Type) - if p.bool(n.Left != nil) { - p.node(n.Left) - } else { - p.nodeList(n.List) + case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: + p.op(OXDOT) + p.expr(n.Left) + if n.Sym == nil { + panic("unreachable") // can this happen during export? } - - case ODOT, ODOTPTR, ODOTMETH, ODOTINTER, OXDOT: - p.node(n.Left) - p.sym(n.Sym) + p.fieldSym(n.Sym, true) case ODOTTYPE, ODOTTYPE2: - p.node(n.Left) + p.op(ODOTTYPE) + p.expr(n.Left) if p.bool(n.Right != nil) { - p.node(n.Right) + p.expr(n.Right) } else { p.typ(n.Type) } - case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: - p.node(n.Left) - p.node(n.Right) + case OINDEX, OINDEXMAP: + p.op(OINDEX) + p.expr(n.Left) + p.expr(n.Right) - case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, - ORECOVER, OPRINT, OPRINTN: - p.nodesOrNil(n.Left, nil) - p.nodeList(n.List) - p.bool(n.Isddd) + case OSLICE, OSLICESTR, OSLICEARR: + p.op(OSLICE) + p.expr(n.Left) + p.expr(n.Right) + + case OSLICE3, OSLICE3ARR: + p.op(OSLICE3) + p.expr(n.Left) + p.expr(n.Right) + + case OCOPY, OCOMPLEX: + p.op(op) + p.expr(n.Left) + p.expr(n.Right) + + case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR: + p.op(OCONV) + p.typ(n.Type) + if p.bool(n.Left != nil) { + p.expr(n.Left) + } else { + p.exprList(n.List) + } + + case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: + p.op(op) + if p.bool(n.Left != nil) { + p.expr(n.Left) + } else { + p.exprList(n.List) + p.bool(n.Isddd) + } case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: - p.node(n.Left) - p.nodeList(n.List) + p.op(OCALL) + p.expr(n.Left) + p.exprList(n.List) p.bool(n.Isddd) + case OMAKEMAP, OMAKECHAN, OMAKESLICE: + p.op(op) // must keep separate from OMAKE for importer + p.typ(n.Type) + switch { + default: + // empty list + p.op(OEND) + case n.List.Len() != 0: // pre-typecheck + p.exprList(n.List) // emits terminating OEND + case n.Right != nil: + p.expr(n.Left) + p.expr(n.Right) + p.op(OEND) + case n.Left != nil && (n.Op == OMAKESLICE || !n.Left.Type.IsUntyped()): + p.expr(n.Left) + p.op(OEND) + } + + // unary expressions + case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV: + p.op(op) + p.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: + p.op(op) + p.expr(n.Left) + p.expr(n.Right) + + case OADDSTR: + p.op(OADDSTR) + p.exprList(n.List) + case OCMPSTR, OCMPIFACE: - p.node(n.Left) - p.node(n.Right) - p.int(int(n.Etype)) + p.op(Op(n.Etype)) + p.expr(n.Left) + p.expr(n.Right) - case OPAREN: - p.node(n.Left) + case ODCLCONST: + // if exporting, DCLCONST should just be removed as its usage + // has already been replaced with literals + // TODO(gri) these should not be exported in the first place + // TODO(gri) why is this considered an expression in fmt.go? + p.op(ODCLCONST) + + default: + Fatalf("exporter: CANNOT EXPORT: %s\nPlease notify gri@\n", opnames[n.Op]) + } +} + +// 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 (p *exporter) stmt(n *Node) { + if p.trace { + p.tracef("( ") + defer p.tracef(") ") + } + + if n.Ninit.Len() > 0 && !stmtwithinit(n.Op) { + if p.trace { + p.tracef("( /* Ninits */ ") + } + + // can't use stmtList here since we don't want the final OEND + for _, n := range n.Ninit.Slice() { + p.stmt(n) + } + + if p.trace { + p.tracef(") ") + } + } - // statements + switch op := n.Op; op { case ODCL: - p.node(n.Left) // TODO(gri) compare with fmt code + p.op(ODCL) + switch n.Left.Class &^ PHEAP { + case PPARAM, PPARAMOUT, PAUTO: + // TODO(gri) when is this not PAUTO? + // Also, originally this didn't look like + // the default case. Investigate. + fallthrough + default: + // TODO(gri) Can we ever reach here? + p.bool(false) + p.sym(n.Left) + } p.typ(n.Left.Type) + // case ODCLFIELD: + // unimplemented - handled by default case + case OAS, OASWB: - p.nodesOrNil(n.Left, n.Right) // n.Right might be nil - p.bool(n.Colas) + p.op(op) + // 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. + // TODO(gri) if n.Right == nil, don't emit anything + if p.bool(n.Right != nil) { + p.expr(n.Left) + p.expr(n.Right) + } case OASOP: - p.node(n.Left) - // n.Implicit indicates ++ or --, n.Right is 1 in those cases - p.node(n.Right) + p.op(OASOP) p.int(int(n.Etype)) + p.expr(n.Left) + if p.bool(!n.Implicit) { + p.expr(n.Right) + } case OAS2: - p.nodeList(n.List) - p.nodeList(n.Rlist) + p.op(OAS2) + p.exprList(n.List) + p.exprList(n.Rlist) case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - p.nodeList(n.List) - p.nodeList(n.Rlist) + p.op(op) + p.exprList(n.List) + p.exprList(n.Rlist) case ORETURN: - p.nodeList(n.List) + p.op(ORETURN) + p.exprList(n.List) + + case ORETJMP: + // generated by compiler for trampolin routines - not exported + panic("unreachable") case OPROC, ODEFER: - p.node(n.Left) + p.op(op) + p.expr(n.Left) case OIF: - p.nodeList(n.Ninit) - p.node(n.Left) - p.nodeList(n.Nbody) - p.nodeList(n.Rlist) + p.op(OIF) + p.stmtList(n.Ninit) + p.expr(n.Left) + p.stmtList(n.Nbody) + p.stmtList(n.Rlist) case OFOR: - p.nodeList(n.Ninit) - p.nodesOrNil(n.Left, n.Right) - p.nodeList(n.Nbody) + p.op(OFOR) + p.stmtList(n.Ninit) + p.exprsOrNil(n.Left, n.Right) + p.stmtList(n.Nbody) case ORANGE: - if p.bool(n.List.Len() != 0) { - p.nodeList(n.List) - } - p.node(n.Right) - p.nodeList(n.Nbody) + p.op(ORANGE) + p.stmtList(n.List) + p.expr(n.Right) + p.stmtList(n.Nbody) case OSELECT, OSWITCH: - p.nodeList(n.Ninit) - p.nodesOrNil(n.Left, nil) - p.nodeList(n.List) + p.op(op) + p.stmtList(n.Ninit) + p.exprsOrNil(n.Left, nil) + p.stmtList(n.List) case OCASE, OXCASE: - if p.bool(n.List.Len() != 0) { - p.nodeList(n.List) - } - p.nodeList(n.Nbody) + p.op(op) + p.stmtList(n.List) + p.stmtList(n.Nbody) case OBREAK, OCONTINUE, OGOTO, OFALL, OXFALL: - p.nodesOrNil(n.Left, nil) + p.op(op) + p.exprsOrNil(n.Left, nil) - case OEMPTY, ODCLCONST: - // nothing to do + case OEMPTY: + // nothing to emit case OLABEL: - p.node(n.Left) + p.op(OLABEL) + p.expr(n.Left) default: Fatalf("exporter: CANNOT EXPORT: %s\nPlease notify gri@\n", opnames[n.Op]) } } -func (p *exporter) nodesOrNil(a, b *Node) { +func (p *exporter) exprsOrNil(a, b *Node) { ab := 0 if a != nil { ab |= 1 @@ -1005,22 +1358,69 @@ func (p *exporter) nodesOrNil(a, b *Node) { } p.int(ab) if ab&1 != 0 { - p.node(a) + p.expr(a) } if ab&2 != 0 { - p.node(b) + p.expr(b) } } -func (p *exporter) sym(s *Sym) { +func (p *exporter) fieldSym(s *Sym, short bool) { name := s.Name + + // remove leading "type." in method names ("(T).m" -> "m") + if short { + if i := strings.LastIndex(name, "."); i >= 0 { + name = name[i+1:] + } + } + p.string(name) - if name == "?" || name != "_" && name != "" && !exportname(name) { + if !exportname(name) { + p.pkg(s.Pkg) + } +} + +func (p *exporter) sym(n *Node) { + s := n.Sym + if s.Pkg != nil { + if len(s.Name) > 0 && s.Name[0] == '.' { + Fatalf("exporter: exporting synthetic symbol %s", s.Name) + } + } + + if p.trace { + p.tracef("{ SYM ") + defer p.tracef("} ") + } + + name := s.Name + + // remove leading "type." in method names ("(T).m" -> "m") + if i := strings.LastIndex(name, "."); i >= 0 { + name = name[i+1:] + } + + if strings.Contains(name, "·") && n.Name.Vargen > 0 { + Fatalf("exporter: unexpected · in symbol name") + } + + if i := n.Name.Vargen; i > 0 { + name = fmt.Sprintf("%s·%d", name, i) + } + + p.string(name) + if name != "_" { p.pkg(s.Pkg) } } func (p *exporter) bool(b bool) bool { + if p.trace { + p.tracef("[") + defer p.tracef("= %v] ", b) + } + x := 0 if b { x = 1 @@ -1030,10 +1430,12 @@ func (p *exporter) bool(b bool) bool { } func (p *exporter) op(op Op) { - p.int(int(op)) if p.trace { - p.tracef("%s ", opnames[op]) + p.tracef("[") + defer p.tracef("= %s] ", opnames[op]) } + + p.int(int(op)) } // ---------------------------------------------------------------------------- @@ -1093,10 +1495,15 @@ func (p *exporter) string(s string) { } // marker emits a marker byte and position information which makes -// it easy for a reader to detect if it is "out of sync". Used for -// debugFormat format only. +// it easy for a reader to detect if it is "out of sync". Used only +// if debugFormat is set. func (p *exporter) marker(m byte) { p.byte(m) + // Uncomment this for help tracking down the location + // of an incorrect marker when running in debugFormat. + // if p.trace { + // p.tracef("#%d ", p.written) + // } p.rawInt64(int64(p.written)) } @@ -1166,8 +1573,13 @@ func (p *exporter) tracef(format string, args ...interface{}) { // Tags. Must be < 0. const ( - // Packages + // Objects packageTag = -(iota + 1) + constTag + typeTag + varTag + funcTag + endTag // Types namedTag @@ -1196,10 +1608,15 @@ const ( // Debugging support. // (tagString is only used when tracing is enabled) var tagString = [...]string{ - // Packages: + // Objects -packageTag: "package", + -constTag: "const", + -typeTag: "type", + -varTag: "var", + -funcTag: "func", + -endTag: "end", - // Types: + // Types -namedTag: "named type", -arrayTag: "array", -sliceTag: "slice", @@ -1211,7 +1628,7 @@ var tagString = [...]string{ -mapTag: "map", -chanTag: "chan", - // Values: + // Values -falseTag: "false", -trueTag: "true", -int64Tag: "int64", diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 4bf6f1d286..103a0b354b 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -19,6 +19,19 @@ import ( // in bimport.go. Changing the export format requires making symmetric // changes to bimport.go and bexport.go. +type importer struct { + in *bufio.Reader + buf []byte // for reading strings + bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib + pkgList []*Pkg + typList []*Type + inlined []*Node // functions with pending inlined function bodies + + // debugging support + debugFormat bool + read int // bytes read +} + // Import populates importpkg from the serialized package data. func Import(in *bufio.Reader) { p := importer{in: in} @@ -58,68 +71,70 @@ func Import(in *bufio.Reader) { typecheckok = true defercheckwidth() - // read consts - for i := p.int(); i > 0; i-- { - sym := p.localname() - typ := p.typ() - val := p.value(typ) - importconst(sym, idealType(typ), nodlit(val)) - } + // read objects - // read vars - for i := p.int(); i > 0; i-- { - sym := p.localname() - typ := p.typ() - importvar(sym, typ) + // Phase 1 + objcount := 0 + for { + tag := p.tagOrIndex() + if tag == endTag { + break + } + p.obj(tag) + objcount++ } - // read funcs - for i := p.int(); i > 0; i-- { - // parser.go:hidden_fndcl - sym := p.localname() - params := p.paramList() - result := p.paramList() - inl := p.int() - - sig := functype(nil, params, result) - importsym(sym, ONAME) - if sym.Def != nil && sym.Def.Op == ONAME && !Eqtype(sig, sym.Def.Type) { - Fatalf("importer: inconsistent definition for func %v during import\n\t%v\n\t%v", sym, sym.Def.Type, sig) - } + // self-verification + if count := p.int(); count != objcount { + Fatalf("importer: got %d objects; want %d", objcount, count) + } - n := newfuncname(sym) - n.Type = sig - declare(n, PFUNC) - funchdr(n) + // --- compiler-specific export data --- - // parser.go:hidden_import - n.Func.Inl.Set(nil) - if inl >= 0 { - if inl != len(p.inlined) { - panic(fmt.Sprintf("inlined body list inconsistent: %d != %d", inl, len(p.inlined))) - } - p.inlined = append(p.inlined, n.Func) + // Phase 2 + objcount = 0 + for { + tag := p.tagOrIndex() + if tag == endTag { + break } - funcbody(n) - importlist = append(importlist, n) // TODO(gri) do this only if body is inlineable? + p.obj(tag) + objcount++ } - // read types - for i := p.int(); i > 0; i-- { - // name is parsed as part of named type - p.typ() + // self-verification + if count := p.int(); count != objcount { + Fatalf("importer: got %d objects; want %d", objcount, count) } - // --- compiler-specific export data --- - // read inlined functions bodies - n := p.int() - for i := 0; i < n; i++ { - body := p.block() - const hookup = false // TODO(gri) enable and remove this condition - if hookup { - p.inlined[i].Inl.Set(body) + if dclcontext != PEXTERN { + Fatalf("importer: unexpected context %d", dclcontext) + } + + bcount := p.int() // consistency check only + if bcount != len(p.inlined) { + Fatalf("importer: expected %d inlined function bodies; got %d", bcount, len(p.inlined)) + } + for _, f := range p.inlined { + if Funcdepth != 0 { + Fatalf("importer: unexpected Funcdepth %d", Funcdepth) } + if f != nil { + // function body not yet imported - read body and set it + funchdr(f) + f.Func.Inl.Set(p.stmtList()) + funcbody(f) + } else { + // function already imported - read body but discard declarations + dclcontext = PDISCARD // throw away any declarations + p.stmtList() + dclcontext = PEXTERN + } + } + + if dclcontext != PEXTERN { + Fatalf("importer: unexpected context %d", dclcontext) } // --- end of export data --- @@ -130,26 +145,6 @@ func Import(in *bufio.Reader) { testdclstack() // debugging only } -func idealType(typ *Type) *Type { - if typ.IsUntyped() { - // canonicalize ideal types - typ = Types[TIDEAL] - } - return typ -} - -type importer struct { - in *bufio.Reader - buf []byte // for reading strings - bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib - pkgList []*Pkg - typList []*Type - inlined []*Func - - debugFormat bool - read int // bytes read -} - func (p *importer) pkg() *Pkg { // if the package was seen before, i is its index (>= 0) i := p.tagOrIndex() @@ -184,20 +179,93 @@ func (p *importer) pkg() *Pkg { if pkg.Name == "" { pkg.Name = name } else if pkg.Name != name { - Fatalf("importer: inconsistent package names: got %s; want %s (path = %s)", pkg.Name, name, path) + Fatalf("importer: conflicting names %s and %s for package %q", pkg.Name, name, path) } p.pkgList = append(p.pkgList, pkg) return pkg } -func (p *importer) localname() *Sym { - // parser.go:hidden_importsym - name := p.string() - if name == "" { - Fatalf("importer: unexpected anonymous name") +func idealType(typ *Type) *Type { + if typ.IsUntyped() { + // canonicalize ideal types + typ = Types[TIDEAL] + } + return typ +} + +func (p *importer) obj(tag int) { + switch tag { + case constTag: + sym := p.qualifiedName() + typ := p.typ() + val := p.value(typ) + importconst(sym, idealType(typ), nodlit(val)) + + case typeTag: + p.typ() + + case varTag: + sym := p.qualifiedName() + typ := p.typ() + importvar(sym, typ) + + case funcTag: + sym := p.qualifiedName() + params := p.paramList() + result := p.paramList() + inl := p.int() + + sig := functype(nil, params, result) + importsym(sym, ONAME) + if sym.Def != nil && sym.Def.Op == ONAME { + if Eqtype(sig, sym.Def.Type) { + // function was imported before (via another import) + dclcontext = PDISCARD // since we skip funchdr below + } else { + Fatalf("importer: inconsistent definition for func %v during import\n\t%v\n\t%v", sym, sym.Def.Type, sig) + } + } + + var n *Node + if dclcontext != PDISCARD { + n = newfuncname(sym) + n.Type = sig + declare(n, PFUNC) + if inl < 0 { + funchdr(n) + } + } + + if inl >= 0 { + // function has inlined body - collect for later + if inl != len(p.inlined) { + Fatalf("importer: inlined index = %d; want %d", inl, len(p.inlined)) + } + p.inlined = append(p.inlined, n) + } + + // parser.go:hidden_import + if dclcontext == PDISCARD { + dclcontext = PEXTERN // since we skip the funcbody below + break + } + + if inl < 0 { + funcbody(n) + } + importlist = append(importlist, n) // TODO(gri) may only be needed for inlineable functions + + if Debug['E'] > 0 { + fmt.Printf("import [%q] func %v \n", importpkg.Path, n) + if Debug['m'] > 2 && len(n.Func.Inl.Slice()) != 0 { + fmt.Printf("inl body: %v\n", n.Func.Inl) + } + } + + default: + Fatalf("importer: unexpected object tag") } - return importpkg.Lookup(name) } func (p *importer) newtyp(etype EType) *Type { @@ -235,26 +303,37 @@ func (p *importer) typ() *Type { break } + // set correct import context (since p.typ() may be called + // while importing the body of an inlined function) + savedContext := dclcontext + dclcontext = PEXTERN + // read associated methods for i := p.int(); i > 0; i-- { // parser.go:hidden_fndcl - name := p.string() + + sym := p.fieldSym() + recv := p.paramList() // TODO(gri) do we need a full param list for the receiver? params := p.paramList() result := p.paramList() inl := p.int() - pkg := localpkg - if !exportname(name) { - pkg = tsym.Pkg - } - sym := pkg.Lookup(name) - n := methodname1(newname(sym), recv[0].Right) n.Type = functype(recv[0], params, result) checkwidth(n.Type) addmethod(sym, n.Type, tsym.Pkg, false, false) - funchdr(n) + if inl < 0 { + funchdr(n) + } + + if inl >= 0 { + // method has inlined body - collect for later + if inl != len(p.inlined) { + Fatalf("importer: inlined index = %d; want %d", inl, len(p.inlined)) + } + p.inlined = append(p.inlined, n) + } // (comment from parser.go) // inl.C's inlnode in on a dotmeth node expects to find the inlineable body as @@ -264,17 +343,21 @@ func (p *importer) typ() *Type { n.Type.SetNname(n) // parser.go:hidden_import - n.Func.Inl.Set(nil) - if inl >= 0 { - if inl != len(p.inlined) { - panic(fmt.Sprintf("inlined body list inconsistent: %d != %d", inl, len(p.inlined))) + if inl < 0 { + funcbody(n) + } + importlist = append(importlist, n) // TODO(gri) may only be needed for inlineable functions + + if Debug['E'] > 0 { + fmt.Printf("import [%q] meth %v \n", importpkg.Path, n) + if Debug['m'] > 2 && len(n.Func.Inl.Slice()) != 0 { + fmt.Printf("inl body: %v\n", n.Func.Inl) } - p.inlined = append(p.inlined, n.Func) } - funcbody(n) - importlist = append(importlist, n) // TODO(gri) do this only if body is inlineable? } + dclcontext = savedContext + case arrayTag, sliceTag: t = p.newtyp(TARRAY) if i == arrayTag { @@ -412,7 +495,7 @@ func (p *importer) fieldName() *Sym { // During imports, unqualified non-exported identifiers are from builtinpkg // (see parser.go:sym). The binary exporter only exports blank as a non-exported // identifier without qualification. - pkg = builtinpkg + pkg = localpkg } else if name == "?" || name != "" && !exportname(name) { if name == "?" { name = "" @@ -461,9 +544,10 @@ func (p *importer) param(named bool) *Node { if name == "" { Fatalf("importer: expected named parameter") } - // The parameter package doesn't matter; it's never consulted. - // We use the builtinpkg per parser.go:sym (line 1181). - n.Left = newname(builtinpkg.Lookup(name)) + // TODO(gri) Supply function/method package rather than + // encoding the package for each parameter repeatedly. + pkg := p.pkg() + n.Left = newname(pkg.Lookup(name)) } // TODO(gri) This is compiler-specific (escape info). @@ -546,112 +630,152 @@ func (p *importer) float(x *Mpflt) { // ---------------------------------------------------------------------------- // Inlined function bodies -func (p *importer) block() []*Node { - markdcl() - // TODO(gri) populate "scope" with function parameters so they can be found - // inside the function body - list := p.nodeList() - popdcl() +// Approach: Read nodes and use them to create/declare the same data structures +// as done originally by the (hidden) parser by closely following the parser's +// original code. In other words, "parsing" the import data (which happens to +// be encoded in binary rather textual form) is the best way at the moment to +// re-establish the syntax tree's invariants. At some future point we might be +// able to avoid this round-about way and create the rewritten nodes directly, +// possibly avoiding a lot of duplicate work (name resolution, type checking). + +func (p *importer) stmtList() []*Node { + var list []*Node + for { + n := p.node() + if n == nil { + break + } + // OBLOCK nodes may be created when importing ODCL nodes - unpack them + if n.Op == OBLOCK { + list = append(list, n.List.Slice()...) + } else { + list = append(list, n) + } + } + return list +} + +func (p *importer) exprList() []*Node { + var list []*Node + for { + n := p.expr() + if n == nil { + break + } + list = append(list, n) + } return list } -// parser.go:stmt_list -func (p *importer) nodeList() []*Node { +func (p *importer) elemList() []*Node { c := p.int() - s := make([]*Node, c) - for i := range s { - s[i] = p.node() + list := make([]*Node, c) + for i := range list { + list[i] = Nod(OKEY, mkname(p.fieldSym()), p.expr()) } - return s + return list +} + +func (p *importer) expr() *Node { + n := p.node() + if n != nil && n.Op == OBLOCK { + Fatalf("unexpected block node: %v", n) + } + return n } +// TODO(gri) split into expr and stmt func (p *importer) node() *Node { - // TODO(gri) eventually we may need to allocate in each branch - n := Nod(p.op(), nil, nil) - - switch n.Op { - // names - case ONAME, OPACK, ONONAME: - name := mkname(p.sym()) - // TODO(gri) decide what to do here (this code throws away n) - /* - if name.Op != n.Op { - Fatalf("importer: got node op = %s; want %s", opnames[name.Op], opnames[n.Op]) - } - */ - n = name + switch op := p.op(); op { + // expressions + // case OPAREN: + // unreachable - unpacked by exporter - case OTYPE: - if p.bool() { - n.Sym = p.sym() - } else { - n.Type = p.typ() - } + // case ODDDARG: + // unimplemented + + // case OREGISTER: + // unimplemented case OLITERAL: typ := p.typ() - n.Type = idealType(typ) - n.SetVal(p.value(typ)) + n := nodlit(p.value(typ)) + if !typ.IsUntyped() { + conv := Nod(OCALL, typenod(typ), nil) + conv.List.Set1(n) + n = conv + } + return n - // expressions - case OMAKEMAP, OMAKECHAN, OMAKESLICE: + case ONAME: if p.bool() { - n.List.Set(p.nodeList()) + // "_" + // TODO(gri) avoid repeated "_" lookup + return mkname(Pkglookup("_", localpkg)) } - n.Left, n.Right = p.nodesOrNil() - n.Type = p.typ() + return NodSym(OXDOT, typenod(p.typ()), p.fieldSym()) - case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV: - n.Left = p.node() + case OPACK, ONONAME: + return mkname(p.sym()) - case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, - OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, - OSUB, OXOR: - n.Left = p.node() - n.Right = p.node() + case OTYPE: + if p.bool() { + return mkname(p.sym()) + } + return typenod(p.typ()) - case OADDSTR: - n.List.Set(p.nodeList()) + // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC: + // unreachable - should have been resolved by typechecking + + // case OCLOSURE: + // unimplemented + + // case OCOMPLIT: + // unimplemented case OPTRLIT: - n.Left = p.node() + n := p.expr() + if !p.bool() /* !implicit, i.e. '&' operator*/ { + if n.Op == OCOMPLIT { + // Special case for &T{...}: turn into (*T){...}. + n.Right = Nod(OIND, n.Right, nil) + n.Right.Implicit = true + } else { + n = Nod(OADDR, n, nil) + } + } + return n case OSTRUCTLIT: - n.Type = p.typ() - n.List.Set(p.nodeList()) - n.Implicit = p.bool() + n := Nod(OCOMPLIT, nil, nil) + if !p.bool() { + n.Right = typenod(p.typ()) + } + n.List.Set(p.elemList()) + return n case OARRAYLIT, OMAPLIT: - n.Type = p.typ() - n.List.Set(p.nodeList()) - n.Implicit = p.bool() + n := Nod(OCOMPLIT, nil, nil) + if !p.bool() { + n.Right = typenod(p.typ()) + } + n.List.Set(p.exprList()) + return n case OKEY: - n.Left, n.Right = p.nodesOrNil() + left, right := p.exprsOrNil() + return Nod(OKEY, left, right) - case OCOPY, OCOMPLEX: - n.Left = p.node() - n.Right = p.node() - - case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR: - // n.Type = p.typ() - // if p.bool() { - // n.Left = p.node() - // } else { - // n.List.Set(p.nodeList()) - // } - x := Nod(OCALL, p.typ().Nod, nil) - if p.bool() { - x.List.Set1(p.node()) - } else { - x.List.Set(p.nodeList()) - } - return x + // case OCALLPART: + // unimplemented + + // case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH: + // unreachable - mapped to case OXDOT below by exporter - case ODOT, ODOTPTR, ODOTMETH, ODOTINTER, OXDOT: + case OXDOT: // see parser.new_dotname - obj := p.node() - sel := p.sym() + obj := p.expr() + sel := p.fieldSym() if obj.Op == OPACK { s := restrictlookup(sel.Name, obj.Name.Pkg) obj.Used = true @@ -659,123 +783,253 @@ func (p *importer) node() *Node { } return NodSym(OXDOT, obj, sel) - case ODOTTYPE, ODOTTYPE2: - n.Left = p.node() + // case ODOTTYPE, ODOTTYPE2: + // unreachable - mapped to case ODOTTYPE below by exporter + + case ODOTTYPE: + n := Nod(ODOTTYPE, p.expr(), nil) if p.bool() { - n.Right = p.node() + n.Right = p.expr() } else { - n.Type = p.typ() + n.Right = typenod(p.typ()) } + return n - case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: - n.Left = p.node() - n.Right = p.node() + // case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR: + // unreachable - mapped to cases below by exporter - case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, - ORECOVER, OPRINT, OPRINTN: - n.Left, _ = p.nodesOrNil() - n.List.Set(p.nodeList()) - n.Isddd = p.bool() + case OINDEX, OSLICE, OSLICE3: + return Nod(op, p.expr(), p.expr()) + + case OCOPY, OCOMPLEX: + n := builtinCall(op) + n.List.Set([]*Node{p.expr(), p.expr()}) + return n + + // case OCONV, OCONVIFACE, OCONVNOP, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, ORUNESTR: + // unreachable - mapped to OCONV case below by exporter + + case OCONV: + n := Nod(OCALL, typenod(p.typ()), nil) + if p.bool() { + n.List.Set1(p.expr()) + } else { + n.List.Set(p.exprList()) + } + return n + + case OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN: + n := builtinCall(op) + if p.bool() { + n.List.Set1(p.expr()) + } else { + n.List.Set(p.exprList()) + n.Isddd = p.bool() + } + return n + + // case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: + // unreachable - mapped to OCALL case below by exporter - case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG: - n.Left = p.node() - n.List.Set(p.nodeList()) + case OCALL: + n := Nod(OCALL, p.expr(), nil) + n.List.Set(p.exprList()) n.Isddd = p.bool() + return n - case OCMPSTR, OCMPIFACE: - n.Left = p.node() - n.Right = p.node() - n.Etype = EType(p.int()) + case OMAKEMAP, OMAKECHAN, OMAKESLICE: + n := builtinCall(OMAKE) + n.List.Append(typenod(p.typ())) + n.List.Append(p.exprList()...) + return n + + // unary expressions + case OPLUS, OMINUS, OADDR, OCOM, OIND, ONOT, ORECV: + return Nod(op, p.expr(), nil) + + // binary expressions + case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT, + OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR: + return Nod(op, p.expr(), p.expr()) + + case OADDSTR: + list := p.exprList() + x := list[0] + for _, y := range list[1:] { + x = Nod(OADD, x, y) + } + return x + + // case OCMPSTR, OCMPIFACE: + // unreachable - mapped to std comparison operators by exporter - case OPAREN: - n.Left = p.node() + case ODCLCONST: + // TODO(gri) these should not be exported in the first place + return Nod(OEMPTY, nil, nil) + // -------------------------------------------------------------------- // statements case ODCL: - n.Left = p.node() // TODO(gri) compare with fmt code - n.Left.Type = p.typ() + var lhs *Node + if p.bool() { + lhs = p.expr() + } else { + lhs = dclname(p.sym()) + } + // TODO(gri) avoid list created here! + return liststmt(variter([]*Node{lhs}, typenod(p.typ()), nil)) - case OAS: - n.Left, n.Right = p.nodesOrNil() - n.Colas = p.bool() // TODO(gri) what about complexinit? + // case ODCLFIELD: + // unimplemented + + case OAS, OASWB: + if p.bool() { + lhs := p.expr() + rhs := p.expr() + return Nod(OAS, lhs, rhs) + } + // TODO(gri) we should not have emitted anything here + return Nod(OEMPTY, nil, nil) case OASOP: - n.Left = p.node() - n.Right = p.node() + n := Nod(OASOP, nil, nil) n.Etype = EType(p.int()) + n.Left = p.expr() + if !p.bool() { + n.Right = Nodintconst(1) + n.Implicit = true + } else { + n.Right = p.expr() + } + return n - case OAS2, OASWB: - n.List.Set(p.nodeList()) - n.Rlist.Set(p.nodeList()) + case OAS2: + lhs := p.exprList() + rhs := p.exprList() + n := Nod(OAS2, nil, nil) + n.List.Set(lhs) + n.Rlist.Set(rhs) + return n case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: - n.List.Set(p.nodeList()) - n.Rlist.Set(p.nodeList()) + n := Nod(OAS2, nil, nil) + n.List.Set(p.exprList()) + n.Rlist.Set(p.exprList()) + return n case ORETURN: - n.List.Set(p.nodeList()) + n := Nod(ORETURN, nil, nil) + n.List.Set(p.exprList()) + return n + + // case ORETJMP: + // unreachable - generated by compiler for trampolin routines (not exported) case OPROC, ODEFER: - n.Left = p.node() + return Nod(op, p.expr(), nil) case OIF: - n.Ninit.Set(p.nodeList()) - n.Left = p.node() - n.Nbody.Set(p.nodeList()) - n.Rlist.Set(p.nodeList()) + markdcl() + n := Nod(OIF, nil, nil) + n.Ninit.Set(p.stmtList()) + n.Left = p.expr() + n.Nbody.Set(p.stmtList()) + n.Rlist.Set(p.stmtList()) + popdcl() + return n case OFOR: - n.Ninit.Set(p.nodeList()) - n.Left, n.Right = p.nodesOrNil() - n.Nbody.Set(p.nodeList()) + markdcl() + n := Nod(OFOR, nil, nil) + n.Ninit.Set(p.stmtList()) + n.Left, n.Right = p.exprsOrNil() + n.Nbody.Set(p.stmtList()) + popdcl() + return n case ORANGE: - if p.bool() { - n.List.Set(p.nodeList()) - } - n.Right = p.node() - n.Nbody.Set(p.nodeList()) + markdcl() + n := Nod(ORANGE, nil, nil) + n.List.Set(p.stmtList()) + n.Right = p.expr() + n.Nbody.Set(p.stmtList()) + popdcl() + return n case OSELECT, OSWITCH: - n.Ninit.Set(p.nodeList()) - n.Left, _ = p.nodesOrNil() - n.List.Set(p.nodeList()) + markdcl() + n := Nod(op, nil, nil) + n.Ninit.Set(p.stmtList()) + n.Left, _ = p.exprsOrNil() + n.List.Set(p.stmtList()) + popdcl() + return n case OCASE, OXCASE: - if p.bool() { - n.List.Set(p.nodeList()) - } - n.Nbody.Set(p.nodeList()) + markdcl() + n := Nod(OXCASE, nil, nil) + n.List.Set(p.exprList()) + // TODO(gri) eventually we must declare variables for type switch + // statements (type switch statements are not yet exported) + n.Nbody.Set(p.stmtList()) + popdcl() + return n case OBREAK, OCONTINUE, OGOTO, OFALL, OXFALL: - n.Left, _ = p.nodesOrNil() + if op == OFALL { + op = OXFALL + } + left, _ := p.exprsOrNil() + return Nod(op, left, nil) - case OEMPTY, ODCLCONST: - // nothing to do + // case OEMPTY: + // unreachable - not emitted by exporter case OLABEL: - n.Left = p.node() + n := Nod(OLABEL, p.expr(), nil) + n.Left.Sym = dclstack // context, for goto restrictions + return n + + case OEND: + return nil default: - panic(fmt.Sprintf("importer: %s (%d) node not yet supported", opnames[n.Op], n.Op)) + Fatalf("importer: %s (%d) node not yet supported", opnames[op], op) + panic("unreachable") // satisfy compiler } +} - return n +func builtinCall(op Op) *Node { + return Nod(OCALL, mkname(builtinpkg.Lookup(goopnames[op])), nil) } -func (p *importer) nodesOrNil() (a, b *Node) { +func (p *importer) exprsOrNil() (a, b *Node) { ab := p.int() if ab&1 != 0 { - a = p.node() + a = p.expr() } if ab&2 != 0 { - b = p.node() + b = p.expr() } return } +func (p *importer) fieldSym() *Sym { + name := p.string() + pkg := localpkg + if !exportname(name) { + pkg = p.pkg() + } + return pkg.Lookup(name) +} + func (p *importer) sym() *Sym { - return p.fieldName() + name := p.string() + pkg := localpkg + if name != "_" { + pkg = p.pkg() + } + return pkg.Lookup(name) } func (p *importer) bool() bool { @@ -818,6 +1072,8 @@ func (p *importer) string() string { p.marker('s') } + // TODO(gri) should we intern strings here? + if n := int(p.rawInt64()); n > 0 { if cap(p.buf) < n { p.buf = make([]byte, n) diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index a1a481f243..bd5a1f6f07 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -79,18 +79,17 @@ func markdcl() { block = blockgen } -func dumpdcl(st string) { +// keep around for debugging +func dumpdclstack() { i := 0 for d := dclstack; d != nil; d = d.Link { - i++ - fmt.Printf(" %.2d %p", i, d) - if d.Name == "" { - fmt.Printf("\n") - continue + fmt.Printf("%6d %p", i, d) + if d.Name != "" { + fmt.Printf(" '%s' %v\n", d.Name, Pkglookup(d.Name, d.Pkg)) + } else { + fmt.Printf(" ---\n") } - - fmt.Printf(" '%s'", d.Name) - fmt.Printf(" %v\n", Pkglookup(d.Name, d.Pkg)) + i++ } } @@ -250,11 +249,8 @@ func variter(vl []*Node, t *Node, el []*Node) []*Node { Yyerror("missing expression in var declaration") break } - e = el[0] el = el[1:] - } else { - e = nil } v.Op = ONAME @@ -527,7 +523,7 @@ func ifacedcl(n *Node) { func funchdr(n *Node) { // change the declaration context from extern to auto if Funcdepth == 0 && dclcontext != PEXTERN { - Fatalf("funchdr: dclcontext") + Fatalf("funchdr: dclcontext = %d", dclcontext) } if importpkg == nil && n.Func.Nname != nil { @@ -678,7 +674,7 @@ func funcargs2(t *Type) { func funcbody(n *Node) { // change the declaration context from auto to extern if dclcontext != PAUTO { - Fatalf("funcbody: dclcontext") + Fatalf("funcbody: unexpected dclcontext %d", dclcontext) } popdcl() Funcdepth-- diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 0a0906c5d1..17681d0700 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -380,9 +380,8 @@ func dumpexport() { if forceNewExport || newexport != 0 { // binary export // The linker also looks for the $$ marker - use char after $$ to distinguish format. - exportf("\n$$B\n") // indicate binary format - const verifyExport = true // enable to check format changes - if verifyExport { + exportf("\n$$B\n") // indicate binary format + if debugFormat { // save a copy of the export data var copy bytes.Buffer bcopy := obj.Binitw(©) @@ -430,9 +429,8 @@ func dumpexport() { } // exportlist grows during iteration - cannot use range - for len(exportlist) > 0 { - n := exportlist[0] - exportlist = exportlist[1:] + for i := 0; i < len(exportlist); i++ { + n := exportlist[i] lineno = n.Lineno dumpsym(n.Sym) } diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go index 5eb99489bd..ab9bad3c2a 100644 --- a/src/cmd/compile/internal/gc/fmt.go +++ b/src/cmd/compile/internal/gc/fmt.go @@ -801,7 +801,7 @@ func stmtfmt(n *Node) string { } // Don't export "v = " initializing statements, hope they're always - // preceded by the DCL which will be re-parsed and typecheck to reproduce + // preceded by the DCL which will be re-parsed and typechecked to reproduce // the "v = " again. case OAS, OASWB: if fmtmode == FExp && n.Right == nil { @@ -1146,9 +1146,7 @@ func exprfmt(n *Node, prec int) string { if n.Left != nil { return fmt.Sprintf("[]%v", n.Left) } - var f string - f += fmt.Sprintf("[]%v", n.Right) - return f // happens before typecheck + return fmt.Sprintf("[]%v", n.Right) // happens before typecheck case OTMAP: return fmt.Sprintf("map[%v]%v", n.Left, n.Right) diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go index c21af77f04..ea2394e7f9 100644 --- a/src/cmd/compile/internal/gc/inl.go +++ b/src/cmd/compile/internal/gc/inl.go @@ -66,7 +66,7 @@ func typecheckinl(fn *Node) { return // typecheckinl on local function } - if Debug['m'] > 2 { + if Debug['m'] > 2 || Debug_export != 0 { fmt.Printf("typecheck import [%v] %v { %v }\n", fn.Sym, Nconv(fn, FmtLong), Hconv(fn.Func.Inl, FmtSharp)) } diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index a6eb192310..73ecb09fa5 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -815,6 +815,9 @@ func importfile(f *Val, indent []byte) { case 'B': // new export format + if Debug_export != 0 { + fmt.Printf("importing %s (%s)\n", path_, file) + } imp.ReadByte() // skip \n after $$B Import(imp) diff --git a/src/cmd/compile/internal/gc/parser.go b/src/cmd/compile/internal/gc/parser.go index 4649c4593b..aecd0361be 100644 --- a/src/cmd/compile/internal/gc/parser.go +++ b/src/cmd/compile/internal/gc/parser.go @@ -1588,7 +1588,7 @@ func (p *parser) onew_name() *Node { func (p *parser) sym() *Sym { switch p.tok { case LNAME: - s := p.sym_ + s := p.sym_ // from localpkg p.next() // during imports, unqualified non-exported identifiers are from builtinpkg if importpkg != nil && !exportname(s.Name) { diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 66c87b0414..efe10a419c 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -490,6 +490,12 @@ func compile(fn *Node) { } } +type symByName []*Sym + +func (a symByName) Len() int { return len(a) } +func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name } +func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + // genlegacy compiles Curfn using the legacy non-SSA code generator. func genlegacy(ptxt *obj.Prog, gcargs, gclocals *Sym) { Genlist(Curfn.Func.Enter) diff --git a/src/go/internal/gcimporter/bimport.go b/src/go/internal/gcimporter/bimport.go index c982724418..182e8a1044 100644 --- a/src/go/internal/gcimporter/bimport.go +++ b/src/go/internal/gcimporter/bimport.go @@ -15,6 +15,18 @@ import ( "unicode/utf8" ) +type importer struct { + imports map[string]*types.Package + data []byte + buf []byte // for reading strings + bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib + pkgList []*types.Package + typList []types.Type + + debugFormat bool + read int // bytes read +} + // BImportData imports a package from the serialized package data // and returns the number of bytes consumed and a reference to the package. // If data is obviously malformed, an error is returned but in @@ -39,7 +51,7 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i // --- generic export data --- if v := p.string(); v != "v0" { - return p.read, nil, fmt.Errorf("unknown version: %s", v) + return p.read, nil, fmt.Errorf("unknown export data version: %s", v) } // populate typList with predeclared "known" types @@ -69,37 +81,20 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i // read compiler-specific flags p.string() // discard - // read consts - for i := p.int(); i > 0; i-- { - name := p.string() - typ := p.typ(nil) - val := p.value() - p.declare(types.NewConst(token.NoPos, pkg, name, typ, val)) - } - - // read vars - for i := p.int(); i > 0; i-- { - name := p.string() - typ := p.typ(nil) - p.declare(types.NewVar(token.NoPos, pkg, name, typ)) - } - - // read funcs - for i := p.int(); i > 0; i-- { - name := p.string() - params, isddd := p.paramList() - result, _ := p.paramList() - sig := types.NewSignature(nil, params, result, isddd) - p.int() // read and discard index of inlined function body - p.declare(types.NewFunc(token.NoPos, pkg, name, sig)) + // read objects of phase 1 only (see cmd/compiler/internal/gc/bexport.go) + objcount := 0 + for { + tag := p.tagOrIndex() + if tag == endTag { + break + } + p.obj(tag) + objcount++ } - // read types - for i := p.int(); i > 0; i-- { - // name is parsed as part of named type and the - // type object is added to scope via respective - // named type - _ = p.typ(nil).(*types.Named) + // self-verification + if count := p.int(); count != objcount { + panic(fmt.Sprintf("importer: got %d objects; want %d", objcount, count)) } // ignore compiler-specific import data @@ -122,25 +117,6 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i return p.read, pkg, nil } -type importer struct { - imports map[string]*types.Package - data []byte - buf []byte // for reading strings - bufarray [64]byte // initial underlying array for buf, large enough to avoid allocation when compiling std lib - pkgList []*types.Package - typList []types.Type - - debugFormat bool - read int // bytes read -} - -func (p *importer) declare(obj types.Object) { - if alt := p.pkgList[0].Scope().Insert(obj); alt != nil { - // This can only happen if we import a package a second time. - panic(fmt.Sprintf("%s already declared", alt.Name())) - } -} - func (p *importer) pkg() *types.Package { // if the package was seen before, i is its index (>= 0) i := p.tagOrIndex() @@ -178,6 +154,55 @@ func (p *importer) pkg() *types.Package { return pkg } +func (p *importer) declare(obj types.Object) { + pkg := obj.Pkg() + if alt := pkg.Scope().Insert(obj); alt != nil { + // This could only trigger if we import a (non-type) object a second time. + // This should never happen because 1) we only import a package once; and + // b) we ignore compiler-specific export data which may contain functions + // whose inlined function bodies refer to other functions that were already + // imported. + // (See also the comment in cmd/compile/internal/gc/bimport.go importer.obj, + // switch case importing functions). + panic(fmt.Sprintf("%s already declared", alt.Name())) + } +} + +func (p *importer) obj(tag int) { + switch tag { + case constTag: + pkg, name := p.qualifiedName() + typ := p.typ(nil) + val := p.value() + p.declare(types.NewConst(token.NoPos, pkg, name, typ, val)) + + case typeTag: + _ = p.typ(nil) + + case varTag: + pkg, name := p.qualifiedName() + typ := p.typ(nil) + p.declare(types.NewVar(token.NoPos, pkg, name, typ)) + + case funcTag: + pkg, name := p.qualifiedName() + params, isddd := p.paramList() + result, _ := p.paramList() + sig := types.NewSignature(nil, params, result, isddd) + p.int() // read and discard index of inlined function body + p.declare(types.NewFunc(token.NoPos, pkg, name, sig)) + + default: + panic("unexpected object tag") + } +} + +func (p *importer) qualifiedName() (pkg *types.Package, name string) { + name = p.string() + pkg = p.pkg() + return +} + func (p *importer) record(t types.Type) { p.typList = append(p.typList, t) } @@ -239,11 +264,17 @@ func (p *importer) typ(parent *types.Package) types.Type { // read associated methods for i := p.int(); i > 0; i-- { + // TODO(gri) replace this with something closer to fieldName name := p.string() + if !exported(name) { + p.pkg() + } + recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver? params, isddd := p.paramList() result, _ := p.paramList() p.int() // read and discard index of inlined function body + sig := types.NewSignature(recv.At(0), params, result, isddd) t0.AddMethod(types.NewFunc(token.NoPos, parent, name, sig)) } @@ -432,18 +463,20 @@ func (p *importer) param(named bool) (*types.Var, bool) { t = types.NewSlice(td.elem) } + var pkg *types.Package var name string if named { name = p.string() if name == "" { panic("expected named parameter") } + pkg = p.pkg() } // read and discard compiler-specific info p.string() - return types.NewVar(token.NoPos, nil, name, t), isddd + return types.NewVar(token.NoPos, pkg, name, t), isddd } func exported(name string) bool { @@ -617,8 +650,13 @@ func (p *importer) byte() byte { // Tags. Must be < 0. const ( - // Packages + // Objects packageTag = -(iota + 1) + constTag + typeTag + varTag + funcTag + endTag // Types namedTag @@ -640,6 +678,7 @@ const ( fractionTag // not used by gc complexTag stringTag + unknownTag // not used by gc (only appears in packages with errors) ) var predeclared = []types.Type{ -- 2.48.1