Skip to content

Commit e1c4f12

Browse files
Moving DCE to isolate filter naming
1 parent eba1537 commit e1c4f12

File tree

5 files changed

+199
-91
lines changed

5 files changed

+199
-91
lines changed

compiler/compiler.go

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"strings"
1818
"time"
1919

20+
"github.com/gopherjs/gopherjs/compiler/internal/dce"
2021
"github.com/gopherjs/gopherjs/compiler/prelude"
2122
"golang.org/x/tools/go/gcexportdata"
2223
)
@@ -125,12 +126,6 @@ func ImportDependencies(archive *Archive, importPkg func(string) (*Archive, erro
125126
return deps, nil
126127
}
127128

128-
type dceInfo struct {
129-
decl *Decl
130-
objectFilter string
131-
methodFilter string
132-
}
133-
134129
func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) error {
135130
mainPkg := pkgs[len(pkgs)-1]
136131
minify := mainPkg.Minified
@@ -141,61 +136,21 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) err
141136
gls.Add(pkg.GoLinknames)
142137
}
143138

144-
byFilter := make(map[string][]*dceInfo)
145-
var pendingDecls []*Decl // A queue of live decls to find other live decls.
139+
sel := &dce.Selector[*Decl]{}
146140
for _, pkg := range pkgs {
147141
for _, d := range pkg.Declarations {
148-
if d.DceObjectFilter == "" && d.DceMethodFilter == "" {
149-
// This is an entry point (like main() or init() functions) or a variable
150-
// initializer which has a side effect, consider it live.
151-
pendingDecls = append(pendingDecls, d)
152-
continue
153-
}
142+
implementsLink := false
154143
if gls.IsImplementation(d.LinkingName) {
155144
// If a decl is referenced by a go:linkname directive, we just assume
156145
// it's not dead.
157146
// TODO(nevkontakte): This is a safe, but imprecise assumption. We should
158147
// try and trace whether the referencing functions are actually live.
159-
pendingDecls = append(pendingDecls, d)
160-
}
161-
info := &dceInfo{decl: d}
162-
if d.DceObjectFilter != "" {
163-
info.objectFilter = pkg.ImportPath + "." + d.DceObjectFilter
164-
byFilter[info.objectFilter] = append(byFilter[info.objectFilter], info)
165-
}
166-
if d.DceMethodFilter != "" {
167-
info.methodFilter = pkg.ImportPath + "." + d.DceMethodFilter
168-
byFilter[info.methodFilter] = append(byFilter[info.methodFilter], info)
169-
}
170-
}
171-
}
172-
173-
dceSelection := make(map[*Decl]struct{}) // Known live decls.
174-
for len(pendingDecls) != 0 {
175-
d := pendingDecls[len(pendingDecls)-1]
176-
pendingDecls = pendingDecls[:len(pendingDecls)-1]
177-
178-
dceSelection[d] = struct{}{} // Mark the decl as live.
179-
180-
// Consider all decls the current one is known to depend on and possible add
181-
// them to the live queue.
182-
for _, dep := range d.DceDeps {
183-
if infos, ok := byFilter[dep]; ok {
184-
delete(byFilter, dep)
185-
for _, info := range infos {
186-
if info.objectFilter == dep {
187-
info.objectFilter = ""
188-
}
189-
if info.methodFilter == dep {
190-
info.methodFilter = ""
191-
}
192-
if info.objectFilter == "" && info.methodFilter == "" {
193-
pendingDecls = append(pendingDecls, info.decl)
194-
}
195-
}
148+
implementsLink = true
196149
}
150+
sel.Include(d, implementsLink)
197151
}
198152
}
153+
dceSelection := sel.AliveDecls()
199154

200155
if _, err := w.Write([]byte("\"use strict\";\n(function() {\n\n")); err != nil {
201156
return err

compiler/decls.go

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
"github.com/gopherjs/gopherjs/compiler/analysis"
16+
"github.com/gopherjs/gopherjs/compiler/internal/dce"
1617
"github.com/gopherjs/gopherjs/compiler/internal/symbol"
1718
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
1819
"github.com/gopherjs/gopherjs/compiler/typesutil"
@@ -51,16 +52,8 @@ type Decl struct {
5152
// JavaScript code that needs to be executed during the package init phase to
5253
// set the symbol up (e.g. initialize package-level variable value).
5354
InitCode []byte
54-
// Symbol's identifier used by the dead-code elimination logic, not including
55-
// package path. If empty, the symbol is assumed to be alive and will not be
56-
// eliminated. For methods it is the same as its receiver type identifier.
57-
DceObjectFilter string
58-
// The second part of the identified used by dead-code elimination for methods.
59-
// Empty for other types of symbols.
60-
DceMethodFilter string
61-
// List of fully qualified (including package path) DCE symbol identifiers the
62-
// symbol depends on for dead code elimination purposes.
63-
DceDeps []string
55+
// dce stores the information for dead-code elimination.
56+
dce dce.Info
6457
// Set to true if a function performs a blocking operation (I/O or
6558
// synchronization). The compiler will have to generate function code such
6659
// that it can be resumed after a blocking operation completes without
@@ -78,6 +71,11 @@ func (d Decl) minify() Decl {
7871
return d
7972
}
8073

74+
// Dce gets the information for dead-code elimination.
75+
func (d *Decl) Dce() *dce.Info {
76+
return &d.dce
77+
}
78+
8179
// topLevelObjects extracts package-level variables, functions and named types
8280
// from the package AST.
8381
func (fc *funcContext) topLevelObjects(srcs sources) (vars []*types.Var, functions []*ast.FuncDecl, typeNames typesutil.TypeNames) {
@@ -241,7 +239,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl {
241239
}
242240
}
243241

244-
d.DceDeps = fc.CollectDCEDeps(func() {
242+
fc.CollectDCEDeps(d.Dce(), func() {
245243
fc.localVars = nil
246244
d.InitCode = fc.CatchOutput(1, func() {
247245
fc.translateStmt(&ast.AssignStmt{
@@ -259,7 +257,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl {
259257

260258
if len(init.Lhs) == 1 {
261259
if !analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) {
262-
d.DceObjectFilter = init.Lhs[0].Name()
260+
d.Dce().SetName(init.Lhs[0])
263261
}
264262
}
265263
return &d
@@ -322,29 +320,25 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance)
322320
Blocking: fc.pkgCtx.IsBlocking(o),
323321
LinkingName: symbol.New(o),
324322
}
323+
d.Dce().SetName(o)
325324

326325
if typesutil.IsMethod(o) {
327326
recv := typesutil.RecvType(o.Type().(*types.Signature)).Obj()
328327
d.NamedRecvType = fc.objectName(recv)
329-
d.DceObjectFilter = recv.Name()
330-
if !fun.Name.IsExported() {
331-
d.DceMethodFilter = o.Name() + "~"
332-
}
333328
} else {
334329
d.RefExpr = fc.instName(inst)
335-
d.DceObjectFilter = o.Name()
336330
switch o.Name() {
337331
case "main":
338332
if fc.pkgCtx.isMain() { // Found main() function of the program.
339-
d.DceObjectFilter = "" // Always reachable.
333+
d.Dce().SetAsAlive() // Always reachable.
340334
}
341335
case "init":
342336
d.InitCode = fc.CatchOutput(1, func() { fc.translateStmt(fc.callInitFunc(o), nil) })
343-
d.DceObjectFilter = "" // init() function is always reachable.
337+
d.Dce().SetAsAlive() // init() function is always reachable.
344338
}
345339
}
346340

347-
d.DceDeps = fc.CollectDCEDeps(func() {
341+
fc.CollectDCEDeps(d.Dce(), func() {
348342
d.DeclCode = fc.translateTopLevelFunction(fun, inst)
349343
})
350344
return d
@@ -455,10 +449,9 @@ func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, er
455449
}
456450

457451
underlying := instanceType.Underlying()
458-
d := &Decl{
459-
DceObjectFilter: inst.Object.Name(),
460-
}
461-
d.DceDeps = fc.CollectDCEDeps(func() {
452+
d := &Decl{}
453+
d.Dce().SetName(inst.Object)
454+
fc.CollectDCEDeps(d.Dce(), func() {
462455
// Code that declares a JS type (i.e. prototype) for each Go type.
463456
d.DeclCode = fc.CatchOutput(0, func() {
464457
size := int64(0)
@@ -578,10 +571,10 @@ func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl {
578571
decls := []*Decl{}
579572
for _, t := range anonTypes {
580573
d := Decl{
581-
Vars: []string{t.Name()},
582-
DceObjectFilter: t.Name(),
574+
Vars: []string{t.Name()},
583575
}
584-
d.DceDeps = fc.CollectDCEDeps(func() {
576+
d.Dce().SetName(t)
577+
fc.CollectDCEDeps(d.Dce(), func() {
585578
d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type())))
586579
})
587580
decls = append(decls, &d)

compiler/internal/dce/info.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package dce
2+
3+
import (
4+
"go/types"
5+
"sort"
6+
7+
"github.com/gopherjs/gopherjs/compiler/typesutil"
8+
)
9+
10+
// Info contains information used by the dead-code elimination (DCE) logic to
11+
// determine whether a declaration is alive or dead.
12+
type Info struct {
13+
14+
// importPath is the package path of the package the declaration is in.
15+
importPath string
16+
17+
// Symbol's identifier used by the dead-code elimination logic, not including
18+
// package path. If empty, the symbol is assumed to be alive and will not be
19+
// eliminated. For methods it is the same as its receiver type identifier.
20+
objectFilter string
21+
22+
// The second part of the identified used by dead-code elimination for methods.
23+
// Empty for other types of symbols.
24+
methodFilter string
25+
26+
// List of fully qualified (including package path) DCE symbol identifiers the
27+
// symbol depends on for dead code elimination purposes.
28+
deps []string
29+
}
30+
31+
// isAlive returns true if the declaration is marked as alive.
32+
func (d *Info) isAlive() bool {
33+
return d.objectFilter == "" && d.methodFilter == ""
34+
}
35+
36+
// SetAsAlive marks the declaration as alive, meaning it will not be eliminated.
37+
// This must be done after the SetName method is called.
38+
//
39+
// This should be called by an entry point (like main() or init() functions)
40+
// or a variable initializer which has a side effect, consider it live.
41+
func (d *Info) SetAsAlive() {
42+
d.objectFilter = ""
43+
d.methodFilter = ""
44+
}
45+
46+
// SetName sets the name used by DCE to represent the declaration
47+
// this DCE info is attached to.
48+
func (d *Info) SetName(o types.Object) {
49+
d.importPath = o.Pkg().Path()
50+
if typesutil.IsMethod(o) {
51+
recv := typesutil.RecvType(o.Type().(*types.Signature)).Obj()
52+
d.objectFilter = recv.Name()
53+
if !o.Exported() {
54+
d.methodFilter = o.Name() + "~"
55+
}
56+
} else {
57+
d.objectFilter = o.Name()
58+
}
59+
}
60+
61+
// SetDeps sets the declaration dependencies used by DCE
62+
// for the declaration this DCE info is attached to.
63+
// This overwrites any prior dependencies set so only call it once.
64+
func (d *Info) SetDeps(objectSet map[types.Object]bool) {
65+
var deps []string
66+
for o := range objectSet {
67+
qualifiedName := o.Pkg().Path() + "." + o.Name()
68+
if typesutil.IsMethod(o) {
69+
qualifiedName += "~"
70+
}
71+
deps = append(deps, qualifiedName)
72+
}
73+
sort.Strings(deps)
74+
d.deps = deps
75+
}

compiler/internal/dce/selector.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package dce
2+
3+
// Decl is any code declaration that has dead-code elimination (DCE)
4+
// information attached to it.
5+
// Since this will be used in a set, it must also be comparable.
6+
type Decl interface {
7+
Dce() *Info
8+
comparable
9+
}
10+
11+
// Selector gathers all declarations that are still alive after dead-code elimination.
12+
type Selector[D Decl] struct {
13+
byFilter map[string][]*declInfo[D]
14+
15+
// A queue of live decls to find other live decls.
16+
pendingDecls []D
17+
}
18+
19+
type declInfo[D Decl] struct {
20+
decl D
21+
objectFilter string
22+
methodFilter string
23+
}
24+
25+
// Include will add a new declaration to be checked as alive or not.
26+
func (s *Selector[D]) Include(decl D, implementsLink bool) {
27+
if s.byFilter == nil {
28+
s.byFilter = make(map[string][]*declInfo[D])
29+
}
30+
31+
dce := decl.Dce()
32+
33+
if dce.isAlive() {
34+
s.pendingDecls = append(s.pendingDecls, decl)
35+
return
36+
}
37+
38+
if implementsLink {
39+
s.pendingDecls = append(s.pendingDecls, decl)
40+
}
41+
42+
info := &declInfo[D]{decl: decl}
43+
44+
if dce.objectFilter != "" {
45+
info.objectFilter = dce.importPath + "." + dce.objectFilter
46+
s.byFilter[info.objectFilter] = append(s.byFilter[info.objectFilter], info)
47+
}
48+
49+
if dce.methodFilter != "" {
50+
info.methodFilter = dce.importPath + "." + dce.methodFilter
51+
s.byFilter[info.methodFilter] = append(s.byFilter[info.methodFilter], info)
52+
}
53+
}
54+
55+
func (s *Selector[D]) popPending() D {
56+
max := len(s.pendingDecls) - 1
57+
d := s.pendingDecls[max]
58+
s.pendingDecls = s.pendingDecls[:max]
59+
return d
60+
}
61+
62+
// AliveDecls returns a set of declarations that are still alive
63+
// after dead-code elimination.
64+
// This should only be called once all declarations have been included.
65+
func (s *Selector[D]) AliveDecls() map[D]struct{} {
66+
dceSelection := make(map[D]struct{}) // Known live decls.
67+
for len(s.pendingDecls) != 0 {
68+
d := s.popPending()
69+
dce := d.Dce()
70+
71+
dceSelection[d] = struct{}{} // Mark the decl as live.
72+
73+
// Consider all decls the current one is known to depend on and possible add
74+
// them to the live queue.
75+
for _, dep := range dce.deps {
76+
if infos, ok := s.byFilter[dep]; ok {
77+
delete(s.byFilter, dep)
78+
for _, info := range infos {
79+
if info.objectFilter == dep {
80+
info.objectFilter = ""
81+
}
82+
if info.methodFilter == dep {
83+
info.methodFilter = ""
84+
}
85+
if info.objectFilter == "" && info.methodFilter == "" {
86+
s.pendingDecls = append(s.pendingDecls, info.decl)
87+
}
88+
}
89+
}
90+
}
91+
}
92+
return dceSelection
93+
}

0 commit comments

Comments
 (0)