cmd/compile: import/export of alias declarations

This CL completes support for alias declarations in the compiler.

Also:
- increased export format version
- updated various comments

For #16339.
Fixes #17487.

Change-Id: Ic6945fc44c0041771eaf9dcfe973f601d14de069
Reviewed-on: https://go-review.googlesource.com/32090
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Robert Griesemer 2016-10-25 14:09:18 -07:00
parent 81038d2e2b
commit 03d81b5ed9
11 changed files with 291 additions and 61 deletions

View file

@ -9,10 +9,12 @@
1) Export data encoding principles:
The export data is a serialized description of the graph of exported
"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.
"objects": constants, types, variables, and functions. Aliases may be
directly reexported, and unaliased types may be indirectly reexported
(as part of the type of a directly exorted object). More generally,
objects referred to from inlined function bodies can be reexported.
We need to know which package declares these reexported objects, and
therefore packages are also part of the export graph.
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.
@ -30,9 +32,9 @@ 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 integer or string values.
If the field is a pointer to another object, that object is serialized in
place, recursively. Otherwise the field is written in place. Non-pointer
fields are all encoded as integer or string values.
Some objects (packages, types) may be referred to more than once. When
reaching an object that was not serialized before, an integer _index_
@ -43,7 +45,7 @@ If the object was already serialized, the encoding is simply the object
index >= 0. An importer can trivially determine if an object needs to
be read in for the first time (tag < 0) and entered into the respective
object table, or if the object was seen already (index >= 0), in which
case the index is used to look up the object in a table.
case the index is used to look up the object in the respective table.
Before exporting or importing, the type tables are populated with the
predeclared types (int, string, error, unsafe.Pointer, etc.). This way
@ -59,7 +61,7 @@ format. These strings are followed by version-specific encoding options.
That format encoding is no longer used but is supported to avoid spurious
errors when importing old installed package files.)
The header is followed by the package object for the exported package,
This header is followed by the package object for the exported package,
two lists of objects, and the list of inlined function bodies.
The encoding of objects is straight-forward: Constants, variables, and
@ -69,6 +71,8 @@ same type was imported before via another import, the importer must use
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.
Aliases are encoded starting with their name followed by the original
(aliased) object.
In the encoding, some lists start with the list length. Some lists are
terminated with an end marker (usually for lists where we may not know
@ -101,30 +105,8 @@ compatibility with both the last release of the compiler, and with the
corresponding compiler at tip. That change is necessarily more involved,
as it must switch based on the version number in the export data file.
It is recommended to turn on debugFormat when working on format changes
as it will help finding encoding/decoding inconsistencies quickly.
Special care must be taken to update builtin.go when the export format
changes: builtin.go contains the export data obtained by compiling the
builtin/runtime.go and builtin/unsafe.go files; those compilations in
turn depend on importing the data in builtin.go. Thus, when the export
data format changes, the compiler must be able to import the data in
builtin.go even if its format has not yet changed. Proceed in several
steps as follows:
- Change the exporter to use the new format, and use a different version
string as well.
- Update the importer accordingly, but accept both the old and the new
format depending on the version string.
- all.bash should pass at this point.
- Run mkbuiltin.go: this will create a new builtin.go using the new
export format.
- go test -run Builtin should pass at this point.
- Remove importer support for the old export format and (maybe) revert
the version string again (it's only needed to mark the transition).
- all.bash should still pass.
Don't forget to set debugFormat to false.
It is recommended to turn on debugFormat temporarily when working on format
changes as it will help finding encoding/decoding inconsistencies quickly.
*/
package gc
@ -158,7 +140,11 @@ const debugFormat = false // default: false
const forceObjFileStability = true
// Current export format version. Increase with each format change.
const exportVersion = 2
// 3: added aliasTag and export of aliases
// 2: removed unused bool in ODCL export
// 1: header format change (more regular), export package for _ struct fields
// 0: Go1.7 encoding
const exportVersion = 3
// exportInlined enables the export of inlined function bodies and related
// dependencies. The compiler should work w/o any loss of functionality with
@ -364,6 +350,11 @@ func export(out *bufio.Writer, trace bool) int {
if p.trace {
p.tracef("\n")
}
if sym.Flags&SymAlias != 0 {
Fatalf("exporter: unexpected alias %v in inlined function body", sym)
}
p.obj(sym)
objcount++
}
@ -455,16 +446,44 @@ func unidealType(typ *Type, val Val) *Type {
}
func (p *exporter) obj(sym *Sym) {
if sym.Flags&SymAlias != 0 {
p.tag(aliasTag)
p.pos(nil) // TODO(gri) fix position information
// Aliases can only be exported from the package that
// declares them (aliases to aliases are resolved to the
// original object, and so are uses of aliases in inlined
// exported function bodies). Thus, we only need the alias
// name without package qualification.
if sym.Pkg != localpkg {
Fatalf("exporter: export of non-local alias: %v", sym)
}
p.string(sym.Name)
sym = sym.Def.Sym // original object
// fall through to export original
// Multiple aliases to the same original will cause that
// original to be exported multiple times (issue #17636).
// TODO(gri) fix this
}
if sym != sym.Def.Sym {
Fatalf("exporter: exported object %v is not original %v", sym, sym.Def.Sym)
}
if sym.Flags&SymAlias != 0 {
Fatalf("exporter: original object %v marked as alias", 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.
// may be re-exported via an exported alias or as dependencies in
// exported 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.
// (This can only happen for aliased objects or during phase 2
// (exportInlined enabled) of object export. Unaliased 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 {
@ -1780,6 +1799,9 @@ const (
stringTag
nilTag
unknownTag // not used by gc (only appears in packages with errors)
// Aliases
aliasTag
)
// Debugging support.
@ -1815,6 +1837,9 @@ var tagString = [...]string{
-stringTag: "string",
-nilTag: "nil",
-unknownTag: "unknown",
// Aliases
-aliasTag: "alias",
}
// untype returns the "pseudo" untyped type for a Ctype (import/export use only).

View file

@ -86,10 +86,10 @@ func Import(in *bufio.Reader) {
// read version specific flags - extend as necessary
switch p.version {
// case 3:
// case 4:
// ...
// fallthrough
case 2, 1:
case 3, 2, 1:
p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
p.trackAllTypes = p.bool()
p.posInfoFormat = p.bool()
@ -307,26 +307,35 @@ func idealType(typ *Type) *Type {
}
func (p *importer) obj(tag int) {
var alias *Sym
if tag == aliasTag {
p.pos()
alias = importpkg.Lookup(p.string())
alias.Flags |= SymAlias
tag = p.tagOrIndex()
}
var sym *Sym
switch tag {
case constTag:
p.pos()
sym := p.qualifiedName()
sym = p.qualifiedName()
typ := p.typ()
val := p.value(typ)
importconst(sym, idealType(typ), nodlit(val))
case typeTag:
p.typ()
sym = p.typ().Sym
case varTag:
p.pos()
sym := p.qualifiedName()
sym = p.qualifiedName()
typ := p.typ()
importvar(sym, typ)
case funcTag:
p.pos()
sym := p.qualifiedName()
sym = p.qualifiedName()
params := p.paramList()
result := p.paramList()
@ -357,6 +366,11 @@ func (p *importer) obj(tag int) {
default:
formatErrorf("unexpected object (tag = %d)", tag)
}
if alias != nil {
alias.Def = sym.Def
importsym(alias, sym.Def.Op)
}
}
func (p *importer) pos() {

View file

@ -63,6 +63,7 @@ const (
SymSiggen
SymAsm
SymAlgGen
SymAlias // alias, original is Sym.Def.Sym
)
// The Class of a variable/function describes the "storage class"

View file

@ -928,7 +928,7 @@ func mkpackage(pkgname string) {
continue
}
if s.Def.Sym != s {
if s.Def.Sym != s && s.Flags&SymAlias == 0 {
// throw away top-level name left over
// from previous import . "x"
if s.Def.Name != nil && s.Def.Name.Pack != nil && !s.Def.Name.Pack.Used && nsyntaxerrors == 0 {
@ -936,8 +936,6 @@ func mkpackage(pkgname string) {
s.Def.Name.Pack.Used = true
}
// TODO(gri) This will also affect exported aliases.
// Need to fix this.
s.Def = nil
continue
}

View file

@ -174,7 +174,11 @@ func (p *noder) aliasDecl(decl *syntax.AliasDecl) {
}
pkg.Used = true
// Resolve original entity
orig := oldname(restrictlookup(qident.Sel.Value, pkg.Name.Pkg))
if orig.Sym.Flags&SymAlias != 0 {
Fatalf("original %v marked as alias", orig.Sym)
}
// An alias declaration must not refer to package unsafe.
if orig.Sym.Pkg == unsafepkg {
@ -222,16 +226,16 @@ func (p *noder) aliasDecl(decl *syntax.AliasDecl) {
redeclare(asym, "in alias declaration")
return
}
asym.Flags |= SymAlias
asym.Def = orig
asym.Block = block
asym.Lastlineno = lineno
if exportname(asym.Name) {
yyerror("cannot export alias %v: not yet implemented", asym)
// TODO(gri) newname(asym) is only needed to satisfy exportsym
// (and indirectly, exportlist). We should be able to just
// collect the Syms, eventually.
// exportsym(newname(asym))
exportsym(newname(asym))
}
}

View file

@ -98,10 +98,10 @@ func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []
// read version specific flags - extend as necessary
switch p.version {
// case 3:
// case 4:
// ...
// fallthrough
case 2, 1:
case 3, 2, 1:
p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
p.trackAllTypes = p.int() != 0
p.posInfoFormat = p.int() != 0

View file

@ -50,7 +50,7 @@ func f => before.f // ERROR "before is not a package"
var v => after.m // ERROR "after is not a package"
func f => after.m // ERROR "after is not a package"
// TODO(gri) fix error printing - should not print a qualified identifier...
// TODO(gri) fix error printing - should print correct qualified identifier...
var _ => Default.ARCH // ERROR "build.Default is not a package"
// aliases may not refer to package unsafe
@ -77,11 +77,11 @@ func sin1 => math.Pi // ERROR "math.Pi is not a function"
// alias reference to a package marks package as used
func _ => fmt.Println
// TODO(gri) aliased cannot be exported yet - fix this
const Pi => math.Pi // ERROR "cannot export alias Pi"
type Writer => io.Writer // ERROR "cannot export alias Writer"
var Def => build.Default // ERROR "cannot export alias Def"
func Sin => math.Sin // ERROR "cannot export alias Sin"
// re-exported aliases
const Pi => math.Pi
type Writer => io.Writer
var Def => build.Default
func Sin => math.Sin
// type aliases denote identical types
type myPackage => build.Package

54
test/alias3.dir/a.go Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"bytes"
"go/build"
"io"
"math"
)
func F(c *build.Context, w io.Writer) {}
func Inlined() bool { var w Writer; return w == nil }
func Check() {
if Pi != math.Pi {
panic(0)
}
var w Writer
F(new(Context), w)
F(new(build.Context), bytes.NewBuffer(nil))
if &Default != &build.Default {
panic(1)
}
if Sin(1) != math.Sin(1) {
panic(2)
}
var _ *LimitedReader = new(LimitedReader2)
}
// export aliases
const Pi => math.Pi
type (
Context => build.Context // not an interface
Writer => io.Writer // interface
)
// different aliases may refer to the same original
type LimitedReader => io.LimitedReader
type LimitedReader2 => io.LimitedReader
var Default => build.Default
var Default2 => build.Default
func Sin => math.Sin
func Sin2 => math.Sin

61
test/alias3.dir/b.go Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package b
import (
"./a"
"bytes"
"go/build"
"io"
"math"
)
func F => a.F
func Inlined => a.Inlined
var _ func(*Context, io.Writer) = a.F
// check aliases
func Check() {
if Pi != math.Pi {
panic(0)
}
var w Writer
a.F(new(Context), w)
F(new(build.Context), bytes.NewBuffer(nil))
if !Inlined() {
panic(1)
}
if &Default != &build.Default {
panic(2)
}
if Sin(1) != math.Sin(1) {
panic(3)
}
var _ *LimitedReader = new(LimitedReader2)
}
// re-export aliases
const Pi => a.Pi
type (
Context => a.Context // not an interface
Writer => a.Writer // interface
)
// different aliases may refer to the same original
type LimitedReader => a.LimitedReader
type LimitedReader2 => a.LimitedReader2
var Default => a.Default
var Default2 => a.Default2
func Sin => a.Sin
func Sin2 => a.Sin

66
test/alias3.dir/c.go Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"./a"
"./b"
"bytes"
"go/build"
"math"
)
func f => b.F
func inlined => b.Inlined
var _ func(*context, a.Writer) = f
func Check() {
if pi != math.Pi {
panic(0)
}
var w writer
b.F(new(context), w)
f(new(build.Context), bytes.NewBuffer(nil))
if !inlined() {
panic(1)
}
if &default_ != &build.Default {
panic(2)
}
if sin(1) != math.Sin(1) {
panic(3)
}
var _ *limitedReader = new(limitedReader2)
}
// local aliases
const pi => b.Pi
type (
context => b.Context // not an interface
writer => b.Writer // interface
)
// different aliases may refer to the same original
type limitedReader => b.LimitedReader
type limitedReader2 => b.LimitedReader2
var default_ => b.Default
var default2 => b.Default2
func sin => b.Sin
func sin2 => b.Sin
func main() {
a.Check()
b.Check()
Check()
}

7
test/alias3.go Normal file
View file

@ -0,0 +1,7 @@
// rundir
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ignored