// 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.
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_
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
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
(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
"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,
trace: trace,
}
- // write low-level encoding format
+ // first byte indicates low-level encoding format
var format byte = 'c' // compact
if debugFormat {
format = 'd'
p.pkg(localpkg)
// write compiler-specific flags
+ // TODO(gri) move this into the compiler-specific export data section
{
var flags string
if safemode != 0 {
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
}
// 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[ ")
}
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")
}
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")
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")
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)
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)
}
}
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)
}
}
func (p *exporter) field(f *Field) {
- p.fieldName(f)
+ p.fieldName(f.Sym, f)
p.typ(f.Type)
p.note(f.Note)
}
}
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)
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 ""
// 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
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?
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
// ----------------------------------------------------------------------------
// 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("[ ")
}
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 = <N>" initializing statements, hope they're always
+ // preceded by the DCL which will be re-parsed and typecheck to reproduce
+ // the "v = <N>" 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
}
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
}
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))
}
// ----------------------------------------------------------------------------
}
// 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))
}
// Tags. Must be < 0.
const (
- // Packages
+ // Objects
packageTag = -(iota + 1)
+ constTag
+ typeTag
+ varTag
+ funcTag
+ endTag
// Types
namedTag
// 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",
-mapTag: "map",
-chanTag: "chan",
- // Values:
+ // Values
-falseTag: "false",
-trueTag: "true",
-int64Tag: "int64",
// 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}
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 ---
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()
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 {
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
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 {
// 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 = ""
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).
// ----------------------------------------------------------------------------
// 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
}
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 {
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)