Skip to content

Commit a1db63c

Browse files
committed
go/internal/gcimporter: add "bundled" export data formats
Export data is self-contained, which is convenient for ensuring the compiler only needs to open export data files directly referenced in source file import declarations. But it does have a consequence that export data files can redundantly encode information. This CL adds a new "bundled" export data format that allows encoding multiple complete packages together so they can share transitive declarations. This is intended to be useful for storing a compilation unit's source files along with any export data they depend upon for efficient re-type-checking and analysis later. Change-Id: I720a12cb051767f9d7ce95d645b448043a1e2167 Reviewed-on: https://go-review.googlesource.com/c/tools/+/292352 Trust: Matthew Dempsky <[email protected]> Run-TryBot: Matthew Dempsky <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent b79f76f commit a1db63c

File tree

4 files changed

+151
-34
lines changed

4 files changed

+151
-34
lines changed

go/gcexportdata/gcexportdata.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,25 @@ func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
105105
}
106106
return gcimporter.IExportData(out, fset, pkg)
107107
}
108+
109+
// ReadBundle reads an export bundle from in, decodes it, and returns type
110+
// information for the packages.
111+
// File position information is added to fset.
112+
//
113+
// ReadBundle may inspect and add to the imports map to ensure that references
114+
// within the export bundle to other packages are consistent.
115+
//
116+
// On return, the state of the reader is undefined.
117+
func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
118+
data, err := ioutil.ReadAll(in)
119+
if err != nil {
120+
return nil, fmt.Errorf("reading export bundle: %v", err)
121+
}
122+
return gcimporter.IImportBundle(fset, imports, data)
123+
}
124+
125+
// WriteBundle writes encoded type information for the specified packages to out.
126+
// The FileSet provides file position information for named objects.
127+
func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
128+
return gcimporter.IExportBundle(out, fset, pkgs)
129+
}

go/internal/gcimporter/iexport.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,25 @@ import (
2525
// 0: Go1.11 encoding
2626
const iexportVersion = 0
2727

28+
// Current bundled export format version. Increase with each format change.
29+
// 0: initial implementation
30+
const bundleVersion = 0
31+
2832
// IExportData writes indexed export data for pkg to out.
2933
//
3034
// If no file set is provided, position info will be missing.
3135
// The package path of the top-level package will not be recorded,
3236
// so that calls to IImportData can override with a provided package path.
33-
func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) (err error) {
37+
func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
38+
return iexportCommon(out, fset, false, []*types.Package{pkg})
39+
}
40+
41+
// IExportBundle writes an indexed export bundle for pkgs to out.
42+
func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
43+
return iexportCommon(out, fset, true, pkgs)
44+
}
45+
46+
func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, pkgs []*types.Package) (err error) {
3447
defer func() {
3548
if e := recover(); e != nil {
3649
if ierr, ok := e.(internalError); ok {
@@ -48,7 +61,9 @@ func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) (err er
4861
stringIndex: map[string]uint64{},
4962
declIndex: map[types.Object]uint64{},
5063
typIndex: map[types.Type]uint64{},
51-
localpkg: pkg,
64+
}
65+
if !bundle {
66+
p.localpkg = pkgs[0]
5267
}
5368

5469
for i, pt := range predeclared() {
@@ -59,10 +74,20 @@ func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) (err er
5974
}
6075

6176
// Initialize work queue with exported declarations.
62-
scope := pkg.Scope()
63-
for _, name := range scope.Names() {
64-
if ast.IsExported(name) {
65-
p.pushDecl(scope.Lookup(name))
77+
for _, pkg := range pkgs {
78+
scope := pkg.Scope()
79+
for _, name := range scope.Names() {
80+
if ast.IsExported(name) {
81+
p.pushDecl(scope.Lookup(name))
82+
}
83+
}
84+
85+
if bundle {
86+
// Ensure pkg and its imports are included in the index.
87+
p.allPkgs[pkg] = true
88+
for _, imp := range pkg.Imports() {
89+
p.allPkgs[imp] = true
90+
}
6691
}
6792
}
6893

@@ -75,10 +100,25 @@ func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) (err er
75100
dataLen := uint64(p.data0.Len())
76101
w := p.newWriter()
77102
w.writeIndex(p.declIndex)
103+
104+
if bundle {
105+
w.uint64(uint64(len(pkgs)))
106+
for _, pkg := range pkgs {
107+
w.pkg(pkg)
108+
imps := pkg.Imports()
109+
w.uint64(uint64(len(imps)))
110+
for _, imp := range imps {
111+
w.pkg(imp)
112+
}
113+
}
114+
}
78115
w.flush()
79116

80117
// Assemble header.
81118
var hdr intWriter
119+
if bundle {
120+
hdr.uint64(bundleVersion)
121+
}
82122
hdr.uint64(iexportVersion)
83123
hdr.uint64(uint64(p.strings.Len()))
84124
hdr.uint64(dataLen)
@@ -102,7 +142,9 @@ func (w *exportWriter) writeIndex(index map[types.Object]uint64) {
102142
// For the main index, make sure to include every package that
103143
// we reference, even if we're not exporting (or reexporting)
104144
// any symbols from it.
105-
pkgObjs[w.p.localpkg] = nil
145+
if w.p.localpkg != nil {
146+
pkgObjs[w.p.localpkg] = nil
147+
}
106148
for pkg := range w.p.allPkgs {
107149
pkgObjs[pkg] = nil
108150
}

go/internal/gcimporter/iexport_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ type UnknownType undefined
134134
testPkgData(t, conf.Fset, pkg, exportdata)
135135
}
136136
}
137+
138+
var bundle bytes.Buffer
139+
if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil {
140+
t.Fatal(err)
141+
}
142+
fset2 := token.NewFileSet()
143+
imports := make(map[string]*types.Package)
144+
pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes())
145+
if err != nil {
146+
t.Fatal(err)
147+
}
148+
149+
for i, pkg := range sorted {
150+
testPkg(t, conf.Fset, pkg, fset2, pkgs2[i])
151+
}
137152
}
138153

139154
func testPkgData(t *testing.T, fset *token.FileSet, pkg *types.Package, exportdata []byte) {

go/internal/gcimporter/iimport.go

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,23 @@ const (
5959
)
6060

6161
// IImportData imports a package from the serialized package data
62-
// and returns the number of bytes consumed and a reference to the package.
62+
// and returns 0 and a reference to the package.
6363
// If the export data version is not recognized or the format is otherwise
6464
// compromised, an error is returned.
65-
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
65+
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
66+
pkgs, err := iimportCommon(fset, imports, data, false, path)
67+
if err != nil {
68+
return 0, nil, err
69+
}
70+
return 0, pkgs[0], nil
71+
}
72+
73+
// IImportBundle imports a set of packages from the serialized package bundle.
74+
func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) {
75+
return iimportCommon(fset, imports, data, true, "")
76+
}
77+
78+
func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string) (pkgs []*types.Package, err error) {
6679
const currentVersion = 1
6780
version := int64(-1)
6881
defer func() {
@@ -77,6 +90,15 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
7790

7891
r := &intReader{bytes.NewReader(data), path}
7992

93+
if bundle {
94+
bundleVersion := r.uint64()
95+
switch bundleVersion {
96+
case bundleVersion:
97+
default:
98+
errorf("unknown bundle format version %d", bundleVersion)
99+
}
100+
}
101+
80102
version = int64(r.uint64())
81103
switch version {
82104
case currentVersion, 0:
@@ -143,39 +165,58 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
143165
p.pkgIndex[pkg] = nameIndex
144166
pkgList[i] = pkg
145167
}
146-
if len(pkgList) == 0 {
147-
errorf("no packages found for %s", path)
148-
panic("unreachable")
149-
}
150-
p.ipkg = pkgList[0]
151-
names := make([]string, 0, len(p.pkgIndex[p.ipkg]))
152-
for name := range p.pkgIndex[p.ipkg] {
153-
names = append(names, name)
168+
169+
if bundle {
170+
pkgs = make([]*types.Package, r.uint64())
171+
for i := range pkgs {
172+
pkg := p.pkgAt(r.uint64())
173+
imps := make([]*types.Package, r.uint64())
174+
for j := range imps {
175+
imps[j] = p.pkgAt(r.uint64())
176+
}
177+
pkg.SetImports(imps)
178+
pkgs[i] = pkg
179+
}
180+
} else {
181+
if len(pkgList) == 0 {
182+
errorf("no packages found for %s", path)
183+
panic("unreachable")
184+
}
185+
pkgs = pkgList[:1]
186+
187+
// record all referenced packages as imports
188+
list := append(([]*types.Package)(nil), pkgList[1:]...)
189+
sort.Sort(byPath(list))
190+
pkgs[0].SetImports(list)
154191
}
155-
sort.Strings(names)
156-
for _, name := range names {
157-
p.doDecl(p.ipkg, name)
192+
193+
for _, pkg := range pkgs {
194+
if pkg.Complete() {
195+
continue
196+
}
197+
198+
names := make([]string, 0, len(p.pkgIndex[pkg]))
199+
for name := range p.pkgIndex[pkg] {
200+
names = append(names, name)
201+
}
202+
sort.Strings(names)
203+
for _, name := range names {
204+
p.doDecl(pkg, name)
205+
}
206+
207+
// package was imported completely and without errors
208+
pkg.MarkComplete()
158209
}
159210

160211
for _, typ := range p.interfaceList {
161212
typ.Complete()
162213
}
163214

164-
// record all referenced packages as imports
165-
list := append(([]*types.Package)(nil), pkgList[1:]...)
166-
sort.Sort(byPath(list))
167-
p.ipkg.SetImports(list)
168-
169-
// package was imported completely and without errors
170-
p.ipkg.MarkComplete()
171-
172-
consumed, _ := r.Seek(0, io.SeekCurrent)
173-
return int(consumed), p.ipkg, nil
215+
return pkgs, nil
174216
}
175217

176218
type iimporter struct {
177219
ipath string
178-
ipkg *types.Package
179220
version int
180221

181222
stringData []byte
@@ -227,9 +268,6 @@ func (p *iimporter) pkgAt(off uint64) *types.Package {
227268
return pkg
228269
}
229270
path := p.stringAt(off)
230-
if path == p.ipath {
231-
return p.ipkg
232-
}
233271
errorf("missing package %q in %q", path, p.ipath)
234272
return nil
235273
}

0 commit comments

Comments
 (0)