diff --git a/compiler/analysis/info.go b/compiler/analysis/info.go index f7c28d3c4..44ea6c165 100644 --- a/compiler/analysis/info.go +++ b/compiler/analysis/info.go @@ -92,6 +92,7 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { return funcInfo } +// IsBlocking returns true if the function may contain blocking calls or operations. func (info *Info) IsBlocking(fun *types.Func) bool { if funInfo := info.FuncDeclInfos[fun]; funInfo != nil { return len(funInfo.Blocking) > 0 @@ -99,6 +100,18 @@ func (info *Info) IsBlocking(fun *types.Func) bool { panic(fmt.Errorf(`info did not have function declaration for %s`, fun.FullName())) } +// VarsWithInitializers returns a set of package-level variables that have +// explicit initializers. +func (info *Info) VarsWithInitializers() map[*types.Var]bool { + result := map[*types.Var]bool{} + for _, init := range info.InitOrder { + for _, o := range init.Lhs { + result[o] = true + } + } + return result +} + func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info { info := &Info{ Info: typesInfo, diff --git a/compiler/compiler.go b/compiler/compiler.go index 91578e884..b8f6a49bc 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -17,7 +17,6 @@ import ( "strings" "time" - "github.com/gopherjs/gopherjs/compiler/internal/symbol" "github.com/gopherjs/gopherjs/compiler/prelude" "golang.org/x/tools/go/gcexportdata" ) @@ -85,56 +84,6 @@ func (a *Archive) RegisterTypes(packages map[string]*types.Package) error { return err } -// Decl represents a package-level symbol (e.g. a function, variable or type). -// -// It contains code generated by the compiler for this specific symbol, which is -// grouped by the execution stage it belongs to in the JavaScript runtime. -type Decl struct { - // The package- or receiver-type-qualified name of function or method obj. - // See go/types.Func.FullName(). - FullName string - // A logical equivalent of a symbol name in an object file in the traditional - // Go compiler/linker toolchain. Used by GopherJS to support go:linkname - // directives. Must be set for decls that are supported by go:linkname - // implementation. - LinkingName symbol.Name - // A list of package-level JavaScript variable names this symbol needs to declare. - Vars []string - // A JS expression by which the object represented by this decl may be - // referenced within the package context. Empty if the decl represents no such - // object. - RefExpr string - // NamedRecvType is method named recv declare. - NamedRecvType string - // JavaScript code that declares basic information about a symbol. For a type - // it configures basic information about the type and its identity. For a function - // or method it contains its compiled body. - DeclCode []byte - // JavaScript code that initializes reflection metadata about type's method list. - MethodListCode []byte - // JavaScript code that initializes the rest of reflection metadata about a type - // (e.g. struct fields, array type sizes, element types, etc.). - TypeInitCode []byte - // JavaScript code that needs to be executed during the package init phase to - // set the symbol up (e.g. initialize package-level variable value). - InitCode []byte - // Symbol's identifier used by the dead-code elimination logic, not including - // package path. If empty, the symbol is assumed to be alive and will not be - // eliminated. For methods it is the same as its receiver type identifier. - DceObjectFilter string - // The second part of the identified used by dead-code elimination for methods. - // Empty for other types of symbols. - DceMethodFilter string - // List of fully qualified (including package path) DCE symbol identifiers the - // symbol depends on for dead code elimination purposes. - DceDeps []string - // Set to true if a function performs a blocking operation (I/O or - // synchronization). The compiler will have to generate function code such - // that it can be resumed after a blocking operation completes without - // blocking the main thread in the meantime. - Blocking bool -} - type Dependency struct { Pkg string Type string diff --git a/compiler/decls.go b/compiler/decls.go new file mode 100644 index 000000000..36f97d3ff --- /dev/null +++ b/compiler/decls.go @@ -0,0 +1,590 @@ +package compiler + +// decls.go contains logic responsible for compiling top-level declarations, +// such as imports, types, functions, etc. + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "sort" + "strings" + + "github.com/gopherjs/gopherjs/compiler/analysis" + "github.com/gopherjs/gopherjs/compiler/internal/symbol" + "github.com/gopherjs/gopherjs/compiler/internal/typeparams" + "github.com/gopherjs/gopherjs/compiler/typesutil" +) + +// Decl represents a package-level symbol (e.g. a function, variable or type). +// +// It contains code generated by the compiler for this specific symbol, which is +// grouped by the execution stage it belongs to in the JavaScript runtime. +type Decl struct { + // The package- or receiver-type-qualified name of function or method obj. + // See go/types.Func.FullName(). + FullName string + // A logical equivalent of a symbol name in an object file in the traditional + // Go compiler/linker toolchain. Used by GopherJS to support go:linkname + // directives. Must be set for decls that are supported by go:linkname + // implementation. + LinkingName symbol.Name + // A list of package-level JavaScript variable names this symbol needs to declare. + Vars []string + // A JS expression by which the object represented by this decl may be + // referenced within the package context. Empty if the decl represents no such + // object. + RefExpr string + // NamedRecvType is method named recv declare. + NamedRecvType string + // JavaScript code that declares basic information about a symbol. For a type + // it configures basic information about the type and its identity. For a function + // or method it contains its compiled body. + DeclCode []byte + // JavaScript code that initializes reflection metadata about type's method list. + MethodListCode []byte + // JavaScript code that initializes the rest of reflection metadata about a type + // (e.g. struct fields, array type sizes, element types, etc.). + TypeInitCode []byte + // JavaScript code that needs to be executed during the package init phase to + // set the symbol up (e.g. initialize package-level variable value). + InitCode []byte + // Symbol's identifier used by the dead-code elimination logic, not including + // package path. If empty, the symbol is assumed to be alive and will not be + // eliminated. For methods it is the same as its receiver type identifier. + DceObjectFilter string + // The second part of the identified used by dead-code elimination for methods. + // Empty for other types of symbols. + DceMethodFilter string + // List of fully qualified (including package path) DCE symbol identifiers the + // symbol depends on for dead code elimination purposes. + DceDeps []string + // Set to true if a function performs a blocking operation (I/O or + // synchronization). The compiler will have to generate function code such + // that it can be resumed after a blocking operation completes without + // blocking the main thread in the meantime. + Blocking bool +} + +// minify returns a copy of Decl with unnecessary whitespace removed from the +// JS code. +func (d Decl) minify() Decl { + d.DeclCode = removeWhitespace(d.DeclCode, true) + d.MethodListCode = removeWhitespace(d.MethodListCode, true) + d.TypeInitCode = removeWhitespace(d.TypeInitCode, true) + d.InitCode = removeWhitespace(d.InitCode, true) + return d +} + +// topLevelObjects extracts package-level variables, functions and named types +// from the package AST. +func (fc *funcContext) topLevelObjects(srcs sources) (vars []*types.Var, functions []*ast.FuncDecl, typeNames typesutil.TypeNames) { + if !fc.isRoot() { + panic(bailout(fmt.Errorf("functionContext.discoverObjects() must be only called on the package-level context"))) + } + + for _, file := range srcs.Files { + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + sig := fc.pkgCtx.Defs[d.Name].(*types.Func).Type().(*types.Signature) + if sig.Recv() == nil { + fc.objectName(fc.pkgCtx.Defs[d.Name]) // register toplevel name + } + if !isBlank(d.Name) { + functions = append(functions, d) + } + case *ast.GenDecl: + switch d.Tok { + case token.TYPE: + for _, spec := range d.Specs { + o := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) + typeNames.Add(o) + fc.objectName(o) // register toplevel name + } + case token.VAR: + for _, spec := range d.Specs { + for _, name := range spec.(*ast.ValueSpec).Names { + if !isBlank(name) { + o := fc.pkgCtx.Defs[name].(*types.Var) + vars = append(vars, o) + fc.objectName(o) // register toplevel name + } + } + } + case token.CONST: + // skip, constants are inlined + } + } + } + } + + return vars, functions, typeNames +} + +// importDecls processes import declarations. +// +// For each imported package: +// - A new package-level variable is reserved to refer to symbols from that +// package. +// - A Decl instance is generated to be included in the Archive. +// +// Lists of imported package paths and corresponding Decls is returned to the caller. +func (fc *funcContext) importDecls() (importedPaths []string, importDecls []*Decl) { + if !fc.isRoot() { + panic(bailout(fmt.Errorf("functionContext.importDecls() must be only called on the package-level context"))) + } + + imports := []*types.Package{} + for _, pkg := range fc.pkgCtx.Pkg.Imports() { + if pkg == types.Unsafe { + // Prior to Go 1.9, unsafe import was excluded by Imports() method, + // but now we do it here to maintain previous behavior. + continue + } + imports = append(imports, pkg) + } + + // Deterministic processing order. + sort.Slice(imports, func(i, j int) bool { return imports[i].Path() < imports[j].Path() }) + + for _, pkg := range imports { + importedPaths = append(importedPaths, pkg.Path()) + importDecls = append(importDecls, fc.newImportDecl(pkg)) + } + + return importedPaths, importDecls +} + +// newImportDecl registers the imported package and returns a Decl instance for it. +func (fc *funcContext) newImportDecl(importedPkg *types.Package) *Decl { + pkgVar := fc.importedPkgVar(importedPkg) + return &Decl{ + Vars: []string{pkgVar}, + DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", pkgVar, importedPkg.Path())), + InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.importInitializer(importedPkg.Path()), nil) }), + } +} + +// importInitializer calls the imported package $init() function to ensure it is +// initialized before any code in the importer package runs. +func (fc *funcContext) importInitializer(impPath string) ast.Stmt { + pkgVar := fc.pkgCtx.pkgVars[impPath] + id := fc.newIdent(fmt.Sprintf(`%s.$init`, pkgVar), types.NewSignatureType(nil, nil, nil, nil, nil, false)) + call := &ast.CallExpr{Fun: id} + fc.Blocking[call] = true + fc.Flattened[call] = true + + return &ast.ExprStmt{X: call} +} + +// varDecls translates all package-level variables. +// +// `vars` argument must contain all package-level variables found in the package. +// The method returns corresponding Decls that declare and initialize the vars +// as appropriate. Decls are returned in order necessary to correctly initialize +// the variables, considering possible dependencies between them. +func (fc *funcContext) varDecls(vars []*types.Var) []*Decl { + if !fc.isRoot() { + panic(bailout(fmt.Errorf("functionContext.varDecls() must be only called on the package-level context"))) + } + + var varDecls []*Decl + varsWithInit := fc.pkgCtx.VarsWithInitializers() + + initializers := []*types.Initializer{} + + // For implicitly-initialized vars we generate synthetic zero-value + // initializers and then process them the same way as explicitly initialized. + for _, o := range vars { + if varsWithInit[o] { + continue + } + initializer := &types.Initializer{ + Lhs: []*types.Var{o}, + Rhs: fc.zeroValue(o.Type()), + } + initializers = append(initializers, initializer) + } + + // Add explicitly-initialized variables to the list. Implicitly-initialized + // variables should be declared first in case explicit initializers depend on + // them. + initializers = append(initializers, fc.pkgCtx.InitOrder...) + + for _, init := range initializers { + varDecls = append(varDecls, fc.newVarDecl(init)) + } + + return varDecls +} + +// newVarDecl creates a new Decl describing a variable, given an explicit +// initializer. +func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { + var d Decl + + assignLHS := []ast.Expr{} + for _, o := range init.Lhs { + assignLHS = append(assignLHS, fc.newIdentFor(o)) + + // For non-exported package-level variables we need to declared a local JS + // variable. Exported variables are represented as properties of the $pkg + // JS object. + if !o.Exported() { + d.Vars = append(d.Vars, fc.objectName(o)) + } + if fc.pkgCtx.HasPointer[o] && !o.Exported() { + d.Vars = append(d.Vars, fc.varPtrName(o)) + } + } + + d.DceDeps = fc.CollectDCEDeps(func() { + fc.localVars = nil + d.InitCode = fc.CatchOutput(1, func() { + fc.translateStmt(&ast.AssignStmt{ + Lhs: assignLHS, + Tok: token.DEFINE, + Rhs: []ast.Expr{init.Rhs}, + }, nil) + }) + + // Initializer code may have introduced auxiliary variables (e.g. for + // handling multi-assignment or blocking calls), add them to the decl too. + d.Vars = append(d.Vars, fc.localVars...) + fc.localVars = nil // Clean up after ourselves. + }) + + if len(init.Lhs) == 1 { + if !analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) { + d.DceObjectFilter = init.Lhs[0].Name() + } + } + return &d +} + +// funcDecls translates all package-level function and methods. +// +// `functions` must contain all package-level function and method declarations +// found in the AST. The function returns Decls that define corresponding JS +// functions at runtime. For special functions like init() and main() decls will +// also contain code necessary to invoke them. +func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { + var funcDecls []*Decl + var mainFunc *types.Func + for _, fun := range functions { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + + if fun.Recv == nil { + // Auxiliary decl shared by all instances of the function that defines + // package-level variable by which they all are referenced. + // TODO(nevkontakte): Set DCE attributes such that it is eliminated if all + // instances are dead. + varDecl := Decl{} + varDecl.Vars = []string{fc.objectName(o)} + if o.Type().(*types.Signature).TypeParams().Len() != 0 { + varDecl.DeclCode = fc.CatchOutput(0, func() { + fc.Printf("%s = {};", fc.objectName(o)) + }) + } + funcDecls = append(funcDecls, &varDecl) + } + + for _, inst := range fc.knownInstances(o) { + funcDecls = append(funcDecls, fc.newFuncDecl(fun, inst)) + + if o.Name() == "main" { + mainFunc = o // main() function candidate. + } + } + } + if fc.pkgCtx.isMain() { + if mainFunc == nil { + return nil, fmt.Errorf("missing main function") + } + // Add a special Decl for invoking main() function after the program has + // been initialized. It must come after all other functions, especially all + // init() functions, otherwise main() will be invoked too early. + funcDecls = append(funcDecls, &Decl{ + InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.callMainFunc(mainFunc), nil) }), + }) + } + return funcDecls, nil +} + +// newFuncDecl returns a Decl that defines a package-level function or a method. +func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) *Decl { + o := fc.pkgCtx.Defs[fun.Name].(*types.Func) + d := &Decl{ + FullName: o.FullName(), + Blocking: fc.pkgCtx.IsBlocking(o), + LinkingName: symbol.New(o), + } + + if typesutil.IsMethod(o) { + recv := typesutil.RecvType(o.Type().(*types.Signature)).Obj() + d.NamedRecvType = fc.objectName(recv) + d.DceObjectFilter = recv.Name() + if !fun.Name.IsExported() { + d.DceMethodFilter = o.Name() + "~" + } + } else { + d.RefExpr = fc.instName(inst) + d.DceObjectFilter = o.Name() + switch o.Name() { + case "main": + if fc.pkgCtx.isMain() { // Found main() function of the program. + d.DceObjectFilter = "" // Always reachable. + } + case "init": + d.InitCode = fc.CatchOutput(1, func() { fc.translateStmt(fc.callInitFunc(o), nil) }) + d.DceObjectFilter = "" // init() function is always reachable. + } + } + + d.DceDeps = fc.CollectDCEDeps(func() { + d.DeclCode = fc.translateTopLevelFunction(fun, inst) + }) + return d +} + +// callInitFunc returns an AST statement for calling the given instance of the +// package's init() function. +func (fc *funcContext) callInitFunc(init *types.Func) ast.Stmt { + id := fc.newIdentFor(init) + call := &ast.CallExpr{Fun: id} + if fc.pkgCtx.IsBlocking(init) { + fc.Blocking[call] = true + } + return &ast.ExprStmt{X: call} +} + +// callMainFunc returns an AST statement for calling the main() function of the +// program, which should be included in the $init() function of the main package. +func (fc *funcContext) callMainFunc(main *types.Func) ast.Stmt { + id := fc.newIdentFor(main) + call := &ast.CallExpr{Fun: id} + ifStmt := &ast.IfStmt{ + Cond: fc.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]), + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ExprStmt{X: call}, + &ast.AssignStmt{ + Lhs: []ast.Expr{fc.newIdent("$mainFinished", types.Typ[types.Bool])}, + Tok: token.ASSIGN, + Rhs: []ast.Expr{fc.newConst(types.Typ[types.Bool], constant.MakeBool(true))}, + }, + }, + }, + } + if fc.pkgCtx.IsBlocking(main) { + fc.Blocking[call] = true + fc.Flattened[ifStmt] = true + } + + return ifStmt +} + +// namedTypeDecls returns Decls that define all names Go types. +// +// `typeNames` must contain all named types defined in the package, including +// those defined inside function bodies. +func (fc *funcContext) namedTypeDecls(typeNames typesutil.TypeNames) ([]*Decl, error) { + if !fc.isRoot() { + panic(bailout(fmt.Errorf("functionContext.namedTypeDecls() must be only called on the package-level context"))) + } + + var typeDecls []*Decl + for _, o := range typeNames.Slice() { + if o.IsAlias() { + continue + } + + typeDecls = append(typeDecls, fc.newNamedTypeVarDecl(o)) + + for _, inst := range fc.knownInstances(o) { + d, err := fc.newNamedTypeInstDecl(inst) + if err != nil { + return nil, err + } + typeDecls = append(typeDecls, d) + } + } + + return typeDecls, nil +} + +// newNamedTypeVarDecl returns a Decl that defines a JS variable to store named +// type definition. +// +// For generic types, the variable is an object containing known instantiations +// of the type, keyed by the type argument combination. Otherwise it contains +// the type definition directly. +func (fc *funcContext) newNamedTypeVarDecl(obj *types.TypeName) *Decl { + varDecl := &Decl{Vars: []string{fc.objectName(obj)}} + if typeparams.HasTypeParams(obj.Type()) { + varDecl.DeclCode = fc.CatchOutput(0, func() { + fc.Printf("%s = {};", fc.objectName(obj)) + }) + } + if isPkgLevel(obj) { + varDecl.TypeInitCode = fc.CatchOutput(0, func() { + fc.Printf("$pkg.%s = %s;", encodeIdent(obj.Name()), fc.objectName(obj)) + }) + } + return varDecl +} + +// newNamedTypeInstDecl returns a Decl that represents an instantiation of a +// named Go type. +func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, error) { + originType := inst.Object.Type().(*types.Named) + + fc.typeResolver = typeparams.NewResolver(fc.pkgCtx.typesCtx, typeparams.ToSlice(originType.TypeParams()), inst.TArgs) + defer func() { fc.typeResolver = nil }() + + instanceType := originType + if !inst.IsTrivial() { + instantiated, err := types.Instantiate(fc.pkgCtx.typesCtx, originType, inst.TArgs, true) + if err != nil { + return nil, fmt.Errorf("failed to instantiate type %v with args %v: %w", originType, inst.TArgs, err) + } + instanceType = instantiated.(*types.Named) + } + + underlying := instanceType.Underlying() + d := &Decl{ + DceObjectFilter: inst.Object.Name(), + } + d.DceDeps = fc.CollectDCEDeps(func() { + // Code that declares a JS type (i.e. prototype) for each Go type. + d.DeclCode = fc.CatchOutput(0, func() { + size := int64(0) + constructor := "null" + + switch t := underlying.(type) { + case *types.Struct: + constructor = fc.structConstructor(t) + case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map: + size = sizes32.Sizeof(t) + } + if tPointer, ok := underlying.(*types.Pointer); ok { + if _, ok := tPointer.Elem().Underlying().(*types.Array); ok { + // Array pointers have non-default constructors to support wrapping + // of the native objects. + constructor = "$arrayPtrCtor()" + } + } + fc.Printf(`%s = $newType(%d, %s, %q, %t, "%s", %t, %s);`, + fc.instName(inst), size, typeKind(originType), inst.TypeString(), inst.Object.Name() != "", inst.Object.Pkg().Path(), inst.Object.Exported(), constructor) + }) + + // Reflection metadata about methods the type has. + d.MethodListCode = fc.CatchOutput(0, func() { + if _, ok := underlying.(*types.Interface); ok { + return + } + var methods []string + var ptrMethods []string + for i := 0; i < instanceType.NumMethods(); i++ { + entry, isPtr := fc.methodListEntry(instanceType.Method(i)) + if isPtr { + ptrMethods = append(ptrMethods, entry) + } else { + methods = append(methods, entry) + } + } + if len(methods) > 0 { + fc.Printf("%s.methods = [%s];", fc.instName(inst), strings.Join(methods, ", ")) + } + if len(ptrMethods) > 0 { + fc.Printf("%s.methods = [%s];", fc.typeName(types.NewPointer(instanceType)), strings.Join(ptrMethods, ", ")) + } + }) + + // Certain types need to run additional type-specific logic to fully + // initialize themselves. + switch t := underlying.(type) { + case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: + d.TypeInitCode = fc.CatchOutput(0, func() { + fc.Printf("%s.init(%s);", fc.instName(inst), fc.initArgs(t)) + }) + } + }) + return d, nil +} + +// structConstructor returns JS constructor function for a struct type. +func (fc *funcContext) structConstructor(t *types.Struct) string { + constructor := &strings.Builder{} + + ctrArgs := make([]string, t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + ctrArgs[i] = fieldName(t, i) + "_" + } + + fmt.Fprintf(constructor, "function(%s) {\n", strings.Join(ctrArgs, ", ")) + fmt.Fprintf(constructor, "\t\tthis.$val = this;\n") + + // If no arguments were passed, zero-initialize all fields. + fmt.Fprintf(constructor, "\t\tif (arguments.length === 0) {\n") + for i := 0; i < t.NumFields(); i++ { + fmt.Fprintf(constructor, "\t\t\tthis.%s = %s;\n", fieldName(t, i), fc.translateExpr(fc.zeroValue(t.Field(i).Type())).String()) + } + fmt.Fprintf(constructor, "\t\t\treturn;\n") + fmt.Fprintf(constructor, "\t\t}\n") + + // Otherwise initialize fields with the provided values. + for i := 0; i < t.NumFields(); i++ { + fmt.Fprintf(constructor, "\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) + } + fmt.Fprintf(constructor, "\t}") + return constructor.String() +} + +// methodListEntry returns a JS code fragment that describes the given method +// function for runtime reflection. It returns isPtr=true if the method belongs +// to the pointer-receiver method list. +func (fc *funcContext) methodListEntry(method *types.Func) (entry string, isPtr bool) { + name := method.Name() + if reservedKeywords[name] { + name += "$" + } + pkgPath := "" + if !method.Exported() { + pkgPath = method.Pkg().Path() + } + t := method.Type().(*types.Signature) + entry = fmt.Sprintf(`{prop: "%s", name: %s, pkg: "%s", typ: $funcType(%s)}`, + name, encodeString(method.Name()), pkgPath, fc.initArgs(t)) + _, isPtr = t.Recv().Type().(*types.Pointer) + return entry, isPtr +} + +// anonTypeDecls returns a list of Decls corresponding to anonymous Go types +// encountered in the package. +// +// `anonTypes` must contain an ordered list of anonymous types with the +// identifiers that were auto-assigned to them. They must be sorted in the +// topological initialization order (e.g. `[]int` is before `struct{f []int}`). +// +// See also typesutil.AnonymousTypes. +func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl { + if !fc.isRoot() { + panic(bailout(fmt.Errorf("functionContext.anonTypeDecls() must be only called on the package-level context"))) + } + decls := []*Decl{} + for _, t := range anonTypes { + d := Decl{ + Vars: []string{t.Name()}, + DceObjectFilter: t.Name(), + } + d.DceDeps = fc.CollectDCEDeps(func() { + d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type()))) + }) + decls = append(decls, &d) + } + return decls +} diff --git a/compiler/expressions.go b/compiler/expressions.go index c748df59a..8bc2bea10 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -592,7 +592,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { return fc.formatExpr(`$methodVal(%s, "%s")`, fc.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: if !sel.Obj().Exported() { - fc.pkgCtx.dependencies[sel.Obj()] = true + fc.DeclareDCEDep(sel.Obj()) } if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return fc.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) @@ -911,7 +911,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { sel, _ := fc.selectionOf(e) if !sel.Obj().Exported() { - fc.pkgCtx.dependencies[sel.Obj()] = true + fc.DeclareDCEDep(sel.Obj()) } x := e.X diff --git a/compiler/internal/typeparams/instance.go b/compiler/internal/typeparams/instance.go index 87240c077..f847a9825 100644 --- a/compiler/internal/typeparams/instance.go +++ b/compiler/internal/typeparams/instance.go @@ -138,6 +138,15 @@ func (iset *InstanceSet) Values() []Instance { return iset.values } +// ByObj returns instances grouped by object they belong to. Order is not specified. +func (iset *InstanceSet) ByObj() map[types.Object][]Instance { + result := map[types.Object][]Instance{} + for _, inst := range iset.values { + result[inst.Object] = append(result[inst.Object], inst) + } + return result +} + // PackageInstanceSets stores an InstanceSet for each package in a program, keyed // by import path. type PackageInstanceSets map[string]*InstanceSet diff --git a/compiler/internal/typeparams/instance_test.go b/compiler/internal/typeparams/instance_test.go index 154e95b82..9b88c87b5 100644 --- a/compiler/internal/typeparams/instance_test.go +++ b/compiler/internal/typeparams/instance_test.go @@ -203,6 +203,15 @@ func TestInstanceQueue(t *testing.T) { if diff := cmp.Diff(wantValues, gotValues, instanceOpts()); diff != "" { t.Errorf("set.Values() returned diff (-want,+got):\n%s", diff) } + + gotByObj := set.ByObj() + wantByObj := map[types.Object][]Instance{ + pkg.Scope().Lookup("Typ"): {i1, i2}, + pkg.Scope().Lookup("Fun"): {i3}, + } + if diff := cmp.Diff(wantByObj, gotByObj, instanceOpts()); diff != "" { + t.Errorf("set.ByObj() returned diff (-want,+got):\n%s", diff) + } } func TestInstancesByPackage(t *testing.T) { diff --git a/compiler/internal/typeparams/utils.go b/compiler/internal/typeparams/utils.go index d473f9b5c..6930fbf23 100644 --- a/compiler/internal/typeparams/utils.go +++ b/compiler/internal/typeparams/utils.go @@ -24,11 +24,24 @@ var ( errDefinesGenerics = errors.New("defines generic type or function") ) +// HasTypeParams returns true if object defines type parameters. +// +// Note: this function doe not check if the object definition actually uses the +// type parameters, neither its own, nor from the outer scope. +func HasTypeParams(typ types.Type) bool { + switch typ := typ.(type) { + case *types.Signature: + return typ.RecvTypeParams().Len() > 0 || typ.TypeParams().Len() > 0 + case *types.Named: + return typ.TypeParams().Len() > 0 + default: + return false + } +} + // RequiresGenericsSupport returns an error if the type-checked code depends on // generics support. func RequiresGenericsSupport(info *types.Info) error { - type withTypeParams interface{ TypeParams() *types.TypeParamList } - for ident := range info.Instances { // Any instantiation means dependency on generics. return fmt.Errorf("%w: %v", errInstantiatesGenerics, info.ObjectOf(ident)) @@ -38,8 +51,7 @@ func RequiresGenericsSupport(info *types.Info) error { if obj == nil { continue } - typ, ok := obj.Type().(withTypeParams) - if ok && typ.TypeParams().Len() > 0 { + if HasTypeParams(obj.Type()) { return fmt.Errorf("%w: %v", errDefinesGenerics, obj) } } diff --git a/compiler/internal/typeparams/utils_test.go b/compiler/internal/typeparams/utils_test.go index 2ef82e52f..dda685273 100644 --- a/compiler/internal/typeparams/utils_test.go +++ b/compiler/internal/typeparams/utils_test.go @@ -2,11 +2,66 @@ package typeparams import ( "errors" + "go/token" + "go/types" "testing" "github.com/gopherjs/gopherjs/internal/srctesting" ) +func TestHasTypeParams(t *testing.T) { + pkg := types.NewPackage("test/pkg", "pkg") + empty := types.NewInterfaceType(nil, nil) + tParams := func() []*types.TypeParam { + return []*types.TypeParam{ + types.NewTypeParam(types.NewTypeName(token.NoPos, pkg, "T", types.Typ[types.String]), empty), + } + } + + tests := []struct { + descr string + typ types.Type + want bool + }{{ + descr: "generic function", + typ: types.NewSignatureType(nil, nil, tParams(), nil, nil, false), + want: true, + }, { + descr: "generic method", + typ: types.NewSignatureType(types.NewVar(token.NoPos, pkg, "t", nil), tParams(), nil, nil, nil, false), + want: true, + }, { + descr: "regular function", + typ: types.NewSignatureType(nil, nil, nil, nil, nil, false), + want: false, + }, { + descr: "generic type", + typ: func() types.Type { + typ := types.NewNamed(types.NewTypeName(token.NoPos, pkg, "Typ", nil), types.Typ[types.String], nil) + typ.SetTypeParams(tParams()) + return typ + }(), + want: true, + }, { + descr: "regular named type", + typ: types.NewNamed(types.NewTypeName(token.NoPos, pkg, "Typ", nil), types.Typ[types.String], nil), + want: false, + }, { + descr: "built-in type", + typ: types.Typ[types.String], + want: false, + }} + + for _, test := range tests { + t.Run(test.descr, func(t *testing.T) { + got := HasTypeParams(test.typ) + if got != test.want { + t.Errorf("Got: HasTypeParams(%v) = %v. Want: %v.", test.typ, got, test.want) + } + }) + } +} + func TestRequiresGenericsSupport(t *testing.T) { t.Run("generic func", func(t *testing.T) { f := srctesting.New(t) diff --git a/compiler/package.go b/compiler/package.go index f82cd798a..249ac3fb6 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "go/ast" - "go/constant" "go/token" "go/types" "sort" @@ -14,7 +13,6 @@ import ( "github.com/gopherjs/gopherjs/compiler/analysis" "github.com/gopherjs/gopherjs/compiler/astutil" - "github.com/gopherjs/gopherjs/compiler/internal/symbol" "github.com/gopherjs/gopherjs/compiler/internal/typeparams" "github.com/gopherjs/gopherjs/compiler/typesutil" "github.com/gopherjs/gopherjs/internal/experiments" @@ -27,8 +25,12 @@ type pkgContext struct { *analysis.Info additionalSelections map[*ast.SelectorExpr]typesutil.Selection - typesCtx *types.Context - typeNames typesutil.TypeNames + typesCtx *types.Context + // List of type names declared in the package, including those defined inside + // functions. + typeNames typesutil.TypeNames + // Mapping from package import paths to JS variables that were assigned to an + // imported package and can be used to access it. pkgVars map[string]string varPtrNames map[*types.Var]string anonTypes []*types.TypeName @@ -42,6 +44,11 @@ type pkgContext struct { instanceSet *typeparams.PackageInstanceSets } +// isMain returns true if this is the main package of the program. +func (pc *pkgContext) isMain() bool { + return pc.Pkg.Name() == "main" +} + // funcContext maintains compiler context for a specific function. // // An instance of this type roughly corresponds to a lexical scope for generated @@ -101,6 +108,42 @@ type funcContext struct { objectNames map[types.Object]string } +func newRootCtx(tContext *types.Context, srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool, minify bool) *funcContext { + tc := typeparams.Collector{ + TContext: tContext, + Info: typesInfo, + Instances: &typeparams.PackageInstanceSets{}, + } + tc.Scan(typesPkg, srcs.Files...) + pkgInfo := analysis.AnalyzePkg(srcs.Files, srcs.FileSet, typesInfo, typesPkg, isBlocking) + funcCtx := &funcContext{ + FuncInfo: pkgInfo.InitFuncInfo, + pkgCtx: &pkgContext{ + Info: pkgInfo, + additionalSelections: make(map[*ast.SelectorExpr]typesutil.Selection), + + typesCtx: tContext, + pkgVars: make(map[string]string), + varPtrNames: make(map[*types.Var]string), + escapingVars: make(map[*types.Var]bool), + indentation: 1, + dependencies: nil, + minify: minify, + fileSet: srcs.FileSet, + instanceSet: tc.Instances, + }, + allVars: make(map[string]int), + flowDatas: map[*types.Label]*flowData{nil: {}}, + caseCounter: 1, + labelCases: make(map[*types.Label]int), + objectNames: map[types.Object]string{}, + } + for name := range reservedKeywords { + funcCtx.allVars[name] = 1 + } + return funcCtx +} + type flowData struct { postStmt func() beginCase int @@ -117,6 +160,27 @@ type ImportContext struct { Import func(importPath string) (*Archive, error) } +// isBlocking returns true if an _imported_ function is blocking. It will panic +// if the function decl is not found in the imported package or the package +// hasn't been compiled yet. +// +// Note: see analysis.FuncInfo.Blocking if you need to determine if a function +// in the _current_ package is blocking. Usually available via functionContext +// object. +func (ic *ImportContext) isBlocking(f *types.Func) bool { + archive, err := ic.Import(f.Pkg().Path()) + if err != nil { + panic(err) + } + fullName := f.FullName() + for _, d := range archive.Declarations { + if string(d.FullName) == fullName { + return d.Blocking + } + } + panic(bailout(fmt.Errorf("can't determine if function %s is blocking: decl not found in package archive", fullName))) +} + // Compile the provided Go sources as a single package. // // Import path must be the absolute import path for a package. Provided sources @@ -161,437 +225,46 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor srcs = srcs.Simplified(typesInfo) - isBlocking := func(f *types.Func) bool { - archive, err := importContext.Import(f.Pkg().Path()) - if err != nil { - panic(err) - } - fullName := f.FullName() - for _, d := range archive.Declarations { - if string(d.FullName) == fullName { - return d.Blocking - } - } - panic(fullName) - } - - tc := typeparams.Collector{ - TContext: tContext, - Info: typesInfo, - Instances: &typeparams.PackageInstanceSets{}, - } - tc.Scan(typesPkg, srcs.Files...) - instancesByObj := map[types.Object][]typeparams.Instance{} - for _, inst := range tc.Instances.Pkg(typesPkg).Values() { - instancesByObj[inst.Object] = append(instancesByObj[inst.Object], inst) - } - - pkgInfo := analysis.AnalyzePkg(srcs.Files, fileSet, typesInfo, typesPkg, isBlocking) - funcCtx := &funcContext{ - FuncInfo: pkgInfo.InitFuncInfo, - pkgCtx: &pkgContext{ - Info: pkgInfo, - additionalSelections: make(map[*ast.SelectorExpr]typesutil.Selection), - - typesCtx: tContext, - pkgVars: make(map[string]string), - varPtrNames: make(map[*types.Var]string), - escapingVars: make(map[*types.Var]bool), - indentation: 1, - dependencies: make(map[types.Object]bool), - minify: minify, - fileSet: srcs.FileSet, - instanceSet: tc.Instances, - }, - allVars: make(map[string]int), - flowDatas: map[*types.Label]*flowData{nil: {}}, - caseCounter: 1, - labelCases: make(map[*types.Label]int), - objectNames: map[types.Object]string{}, - } - for name := range reservedKeywords { - funcCtx.allVars[name] = 1 - } + rootCtx := newRootCtx(tContext, srcs, typesInfo, typesPkg, importContext.isBlocking, minify) - // imports - var importDecls []*Decl - var importedPaths []string - for _, importedPkg := range typesPkg.Imports() { - if importedPkg == types.Unsafe { - // Prior to Go 1.9, unsafe import was excluded by Imports() method, - // but now we do it here to maintain previous behavior. - continue - } - funcCtx.pkgCtx.pkgVars[importedPkg.Path()] = funcCtx.newVariable(importedPkg.Name(), true) - importedPaths = append(importedPaths, importedPkg.Path()) - } - sort.Strings(importedPaths) - for _, impPath := range importedPaths { - id := funcCtx.newIdent(fmt.Sprintf(`%s.$init`, funcCtx.pkgCtx.pkgVars[impPath]), types.NewSignatureType(nil, nil, nil, nil, nil, false)) - call := &ast.CallExpr{Fun: id} - funcCtx.Blocking[call] = true - funcCtx.Flattened[call] = true - importDecls = append(importDecls, &Decl{ - Vars: []string{funcCtx.pkgCtx.pkgVars[impPath]}, - DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", funcCtx.pkgCtx.pkgVars[impPath], impPath)), - InitCode: funcCtx.CatchOutput(1, func() { funcCtx.translateStmt(&ast.ExprStmt{X: call}, nil) }), - }) - } - - var functions []*ast.FuncDecl - var vars []*types.Var - for _, file := range srcs.Files { - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - sig := funcCtx.pkgCtx.Defs[d.Name].(*types.Func).Type().(*types.Signature) - if sig.Recv() == nil { - funcCtx.objectName(funcCtx.pkgCtx.Defs[d.Name].(*types.Func)) // register toplevel name - } - if !isBlank(d.Name) { - functions = append(functions, d) - } - case *ast.GenDecl: - switch d.Tok { - case token.TYPE: - for _, spec := range d.Specs { - o := funcCtx.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) - funcCtx.pkgCtx.typeNames.Add(o) - funcCtx.objectName(o) // register toplevel name - } - case token.VAR: - for _, spec := range d.Specs { - for _, name := range spec.(*ast.ValueSpec).Names { - if !isBlank(name) { - o := funcCtx.pkgCtx.Defs[name].(*types.Var) - vars = append(vars, o) - funcCtx.objectName(o) // register toplevel name - } - } - } - case token.CONST: - // skip, constants are inlined - } - } - } - } + importedPaths, importDecls := rootCtx.importDecls() - collectDependencies := func(f func()) []string { - funcCtx.pkgCtx.dependencies = make(map[types.Object]bool) - f() - var deps []string - for o := range funcCtx.pkgCtx.dependencies { - qualifiedName := o.Pkg().Path() + "." + o.Name() - if f, ok := o.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil { - deps = append(deps, qualifiedName+"~") - continue - } - deps = append(deps, qualifiedName) - } - sort.Strings(deps) - return deps - } + vars, functions, typeNames := rootCtx.topLevelObjects(srcs) + // More named types may be added to the list when function bodies are processed. + rootCtx.pkgCtx.typeNames = typeNames - // variables - var varDecls []*Decl - varsWithInit := make(map[*types.Var]bool) - for _, init := range funcCtx.pkgCtx.InitOrder { - for _, o := range init.Lhs { - varsWithInit[o] = true - } - } - for _, o := range vars { - var d Decl - if !o.Exported() { - d.Vars = []string{funcCtx.objectName(o)} - } - if funcCtx.pkgCtx.HasPointer[o] && !o.Exported() { - d.Vars = append(d.Vars, funcCtx.varPtrName(o)) - } - if _, ok := varsWithInit[o]; !ok { - d.DceDeps = collectDependencies(func() { - d.InitCode = []byte(fmt.Sprintf("\t\t%s = %s;\n", funcCtx.objectName(o), funcCtx.translateExpr(funcCtx.zeroValue(o.Type())).String())) - }) - } - d.DceObjectFilter = o.Name() - varDecls = append(varDecls, &d) - } - for _, init := range funcCtx.pkgCtx.InitOrder { - lhs := make([]ast.Expr, len(init.Lhs)) - for i, o := range init.Lhs { - ident := ast.NewIdent(o.Name()) - ident.NamePos = o.Pos() - funcCtx.pkgCtx.Defs[ident] = o - lhs[i] = funcCtx.setType(ident, o.Type()) - varsWithInit[o] = true - } - var d Decl - d.DceDeps = collectDependencies(func() { - funcCtx.localVars = nil - d.InitCode = funcCtx.CatchOutput(1, func() { - funcCtx.translateStmt(&ast.AssignStmt{ - Lhs: lhs, - Tok: token.DEFINE, - Rhs: []ast.Expr{init.Rhs}, - }, nil) - }) - d.Vars = append(d.Vars, funcCtx.localVars...) - }) - if len(init.Lhs) == 1 { - if !analysis.HasSideEffect(init.Rhs, funcCtx.pkgCtx.Info.Info) { - d.DceObjectFilter = init.Lhs[0].Name() - } - } - varDecls = append(varDecls, &d) + // Translate functions and variables. + varDecls := rootCtx.varDecls(vars) + funcDecls, err := rootCtx.funcDecls(functions) + if err != nil { + return nil, err } - // functions - var funcDecls []*Decl - var mainFunc *types.Func - for _, fun := range functions { - o := funcCtx.pkgCtx.Defs[fun.Name].(*types.Func) - sig := o.Type().(*types.Signature) - - var instances []typeparams.Instance - if typeparams.SignatureTypeParams(sig) != nil { - instances = instancesByObj[o] - } else { - instances = []typeparams.Instance{{Object: o}} - } - - if fun.Recv == nil { - // Auxiliary decl shared by all instances of the function that defines - // package-level variable by which they all are referenced. - // TODO(nevkontakte): Set DCE attributes such that it is eliminated if all - // instances are dead. - varDecl := Decl{} - varDecl.Vars = []string{funcCtx.objectName(o)} - if o.Type().(*types.Signature).TypeParams().Len() != 0 { - varDecl.DeclCode = funcCtx.CatchOutput(0, func() { - funcCtx.Printf("%s = {};", funcCtx.objectName(o)) - }) - } - funcDecls = append(funcDecls, &varDecl) - } - - for _, inst := range instances { - funcInfo := funcCtx.pkgCtx.FuncDeclInfos[o] - d := Decl{ - FullName: o.FullName(), - Blocking: len(funcInfo.Blocking) != 0, - } - d.LinkingName = symbol.New(o) - if fun.Recv == nil { - d.RefExpr = funcCtx.instName(inst) - d.DceObjectFilter = o.Name() - switch o.Name() { - case "main": - mainFunc = o - d.DceObjectFilter = "" - case "init": - d.InitCode = funcCtx.CatchOutput(1, func() { - id := funcCtx.newIdent("", types.NewSignatureType( /*recv=*/ nil /*rectTypeParams=*/, nil /*typeParams=*/, nil /*params=*/, nil /*results=*/, nil /*variadic=*/, false)) - funcCtx.pkgCtx.Uses[id] = o - call := &ast.CallExpr{Fun: id} - if len(funcCtx.pkgCtx.FuncDeclInfos[o].Blocking) != 0 { - funcCtx.Blocking[call] = true - } - funcCtx.translateStmt(&ast.ExprStmt{X: call}, nil) - }) - d.DceObjectFilter = "" - } - } else { - recvType := o.Type().(*types.Signature).Recv().Type() - ptr, isPointer := recvType.(*types.Pointer) - namedRecvType, _ := recvType.(*types.Named) - if isPointer { - namedRecvType = ptr.Elem().(*types.Named) - } - d.NamedRecvType = funcCtx.objectName(namedRecvType.Obj()) - d.DceObjectFilter = namedRecvType.Obj().Name() - if !fun.Name.IsExported() { - d.DceMethodFilter = o.Name() + "~" - } - } - - d.DceDeps = collectDependencies(func() { - d.DeclCode = funcCtx.translateToplevelFunction(fun, funcInfo, inst) - }) - funcDecls = append(funcDecls, &d) - } - } - if typesPkg.Name() == "main" { - if mainFunc == nil { - return nil, fmt.Errorf("missing main function") - } - id := funcCtx.newIdent("", types.NewSignatureType( /*recv=*/ nil /*rectTypeParams=*/, nil /*typeParams=*/, nil /*params=*/, nil /*results=*/, nil /*variadic=*/, false)) - funcCtx.pkgCtx.Uses[id] = mainFunc - call := &ast.CallExpr{Fun: id} - ifStmt := &ast.IfStmt{ - Cond: funcCtx.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]), - Body: &ast.BlockStmt{ - List: []ast.Stmt{ - &ast.ExprStmt{X: call}, - &ast.AssignStmt{ - Lhs: []ast.Expr{funcCtx.newIdent("$mainFinished", types.Typ[types.Bool])}, - Tok: token.ASSIGN, - Rhs: []ast.Expr{funcCtx.newConst(types.Typ[types.Bool], constant.MakeBool(true))}, - }, - }, - }, - } - if len(funcCtx.pkgCtx.FuncDeclInfos[mainFunc].Blocking) != 0 { - funcCtx.Blocking[call] = true - funcCtx.Flattened[ifStmt] = true - } - funcDecls = append(funcDecls, &Decl{ - InitCode: funcCtx.CatchOutput(1, func() { - funcCtx.translateStmt(ifStmt, nil) - }), - }) + // It is important that we translate types *after* we've processed all + // functions to make sure we've discovered all types declared inside function + // bodies. + typeDecls, err := rootCtx.namedTypeDecls(rootCtx.pkgCtx.typeNames) + if err != nil { + return nil, err } - // named types - var typeDecls []*Decl - for _, o := range funcCtx.pkgCtx.typeNames.Slice() { - if o.IsAlias() { - continue - } - typ := o.Type().(*types.Named) - var instances []typeparams.Instance - if typ.TypeParams() != nil { - instances = instancesByObj[o] - } else { - instances = []typeparams.Instance{{Object: o}} - } + // Finally, anonymous types are translated the last, to make sure we've + // discovered all of them referenced in functions, variable and type + // declarations. + typeDecls = append(typeDecls, rootCtx.anonTypeDecls(rootCtx.pkgCtx.anonTypes)...) - typeName := funcCtx.objectName(o) + // Combine all decls in a single list in the order they must appear in the + // final program. + allDecls := append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) - varDecl := Decl{Vars: []string{typeName}} - if typ.TypeParams() != nil { - varDecl.DeclCode = funcCtx.CatchOutput(0, func() { - funcCtx.Printf("%s = {};", funcCtx.objectName(o)) - }) + if minify { + for _, d := range allDecls { + *d = d.minify() } - if isPkgLevel(o) { - varDecl.TypeInitCode = funcCtx.CatchOutput(0, func() { - funcCtx.Printf("$pkg.%s = %s;", encodeIdent(o.Name()), funcCtx.objectName(o)) - }) - } - typeDecls = append(typeDecls, &varDecl) - - for _, inst := range instances { - funcCtx.typeResolver = typeparams.NewResolver(funcCtx.pkgCtx.typesCtx, typeparams.ToSlice(typ.TypeParams()), inst.TArgs) - - named := typ - if !inst.IsTrivial() { - instantiated, err := types.Instantiate(funcCtx.pkgCtx.typesCtx, typ, inst.TArgs, true) - if err != nil { - return nil, fmt.Errorf("failed to instantiate type %v with args %v: %w", typ, inst.TArgs, err) - } - named = instantiated.(*types.Named) - } - underlying := named.Underlying() - d := Decl{ - DceObjectFilter: o.Name(), - } - d.DceDeps = collectDependencies(func() { - d.DeclCode = funcCtx.CatchOutput(0, func() { - size := int64(0) - constructor := "null" - switch t := underlying.(type) { - case *types.Struct: - params := make([]string, t.NumFields()) - for i := 0; i < t.NumFields(); i++ { - params[i] = fieldName(t, i) + "_" - } - constructor = fmt.Sprintf("function(%s) {\n\t\tthis.$val = this;\n\t\tif (arguments.length === 0) {\n", strings.Join(params, ", ")) - for i := 0; i < t.NumFields(); i++ { - constructor += fmt.Sprintf("\t\t\tthis.%s = %s;\n", fieldName(t, i), funcCtx.translateExpr(funcCtx.zeroValue(t.Field(i).Type())).String()) - } - constructor += "\t\t\treturn;\n\t\t}\n" - for i := 0; i < t.NumFields(); i++ { - constructor += fmt.Sprintf("\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) - } - constructor += "\t}" - case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map: - size = sizes32.Sizeof(t) - } - if tPointer, ok := underlying.(*types.Pointer); ok { - if _, ok := tPointer.Elem().Underlying().(*types.Array); ok { - // Array pointers have non-default constructors to support wrapping - // of the native objects. - constructor = "$arrayPtrCtor()" - } - } - funcCtx.Printf(`%s = $newType(%d, %s, %q, %t, "%s", %t, %s);`, funcCtx.instName(inst), size, typeKind(typ), inst.TypeString(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor) - }) - d.MethodListCode = funcCtx.CatchOutput(0, func() { - if _, ok := underlying.(*types.Interface); ok { - return - } - var methods []string - var ptrMethods []string - for i := 0; i < named.NumMethods(); i++ { - method := named.Method(i) - name := method.Name() - if reservedKeywords[name] { - name += "$" - } - pkgPath := "" - if !method.Exported() { - pkgPath = method.Pkg().Path() - } - t := method.Type().(*types.Signature) - entry := fmt.Sprintf(`{prop: "%s", name: %s, pkg: "%s", typ: $funcType(%s)}`, name, encodeString(method.Name()), pkgPath, funcCtx.initArgs(t)) - if _, isPtr := t.Recv().Type().(*types.Pointer); isPtr { - ptrMethods = append(ptrMethods, entry) - continue - } - methods = append(methods, entry) - } - if len(methods) > 0 { - funcCtx.Printf("%s.methods = [%s];", funcCtx.instName(inst), strings.Join(methods, ", ")) - } - if len(ptrMethods) > 0 { - funcCtx.Printf("%s.methods = [%s];", funcCtx.typeName(types.NewPointer(named)), strings.Join(ptrMethods, ", ")) - } - }) - switch t := underlying.(type) { - case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: - d.TypeInitCode = funcCtx.CatchOutput(0, func() { - funcCtx.Printf("%s.init(%s);", funcCtx.instName(inst), funcCtx.initArgs(t)) - }) - } - }) - typeDecls = append(typeDecls, &d) - } - funcCtx.typeResolver = nil - } - - // anonymous types - for _, t := range funcCtx.pkgCtx.anonTypes { - d := Decl{ - Vars: []string{t.Name()}, - DceObjectFilter: t.Name(), - } - d.DceDeps = collectDependencies(func() { - d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), funcCtx.initArgs(t.Type()))) - }) - typeDecls = append(typeDecls, &d) - } - - var allDecls []*Decl - for _, d := range append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) { - d.DeclCode = removeWhitespace(d.DeclCode, minify) - d.MethodListCode = removeWhitespace(d.MethodListCode, minify) - d.TypeInitCode = removeWhitespace(d.TypeInitCode, minify) - d.InitCode = removeWhitespace(d.InitCode, minify) - allDecls = append(allDecls, d) } - if len(funcCtx.pkgCtx.errList) != 0 { - return nil, funcCtx.pkgCtx.errList + if len(rootCtx.pkgCtx.errList) != 0 { + return nil, rootCtx.pkgCtx.errList } exportData := new(bytes.Buffer) @@ -669,8 +342,9 @@ func (fc *funcContext) initArgs(ty types.Type) string { } } -func (fc *funcContext) translateToplevelFunction(fun *ast.FuncDecl, info *analysis.FuncInfo, inst typeparams.Instance) []byte { +func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte { o := inst.Object.(*types.Func) + info := fc.pkgCtx.FuncDeclInfos[o] sig := o.Type().(*types.Signature) var recv *ast.Ident if fun.Recv != nil && fun.Recv.List[0].Names != nil { diff --git a/compiler/statements.go b/compiler/statements.go index 3f228963e..3d7210e47 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -445,7 +445,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { for _, spec := range decl.Specs { o := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) fc.pkgCtx.typeNames.Add(o) - fc.pkgCtx.dependencies[o] = true + fc.DeclareDCEDep(o) } case token.CONST: // skip, constants are inlined diff --git a/compiler/typesutil/typesutil.go b/compiler/typesutil/typesutil.go index 1434c23c5..bce656f3b 100644 --- a/compiler/typesutil/typesutil.go +++ b/compiler/typesutil/typesutil.go @@ -107,3 +107,9 @@ func OffsetOf(sizes types.Sizes, sel Selection) int64 { return o } + +// IsMethod returns true if the passed object is a method. +func IsMethod(o types.Object) bool { + f, ok := o.(*types.Func) + return ok && f.Type().(*types.Signature).Recv() != nil +} diff --git a/compiler/utils.go b/compiler/utils.go index c1c5941f5..7fec5b223 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -25,12 +25,17 @@ import ( // root returns the topmost function context corresponding to the package scope. func (fc *funcContext) root() *funcContext { - if fc.parent == nil { + if fc.isRoot() { return fc } return fc.parent.root() } +// isRoot returns true for the package-level context. +func (fc *funcContext) isRoot() bool { + return fc.parent == nil +} + func (fc *funcContext) Write(b []byte) (int, error) { fc.writePos() fc.output = append(fc.output, b...) @@ -97,6 +102,43 @@ func (fc *funcContext) Delayed(f func()) { fc.delayedOutput = fc.CatchOutput(0, f) } +// CollectDCEDeps captures a list of Go objects (types, functions, etc.) +// the code translated inside f() depends on. The returned list of identifiers +// can be used in dead-code elimination. +// +// Note that calling CollectDCEDeps() inside another CollectDCEDeps() call is +// not allowed. +func (fc *funcContext) CollectDCEDeps(f func()) []string { + if fc.pkgCtx.dependencies != nil { + panic(bailout(fmt.Errorf("called funcContext.CollectDependencies() inside another funcContext.CollectDependencies() call"))) + } + + fc.pkgCtx.dependencies = make(map[types.Object]bool) + defer func() { fc.pkgCtx.dependencies = nil }() + + f() + + var deps []string + for o := range fc.pkgCtx.dependencies { + qualifiedName := o.Pkg().Path() + "." + o.Name() + if typesutil.IsMethod(o) { + qualifiedName += "~" + } + deps = append(deps, qualifiedName) + } + sort.Strings(deps) + return deps +} + +// DeclareDCEDep records that the code that is currently being transpiled +// depends on a given Go object. +func (fc *funcContext) DeclareDCEDep(o types.Object) { + if fc.pkgCtx.dependencies == nil { + return // Dependencies are not being collected. + } + fc.pkgCtx.dependencies[o] = true +} + // expandTupleArgs converts a function call which argument is a tuple returned // by another function into a set of individual call arguments corresponding to // tuple elements. @@ -311,12 +353,20 @@ func (fc *funcContext) newVariable(name string, pkgLevel bool) string { return varName } +// newIdent declares a new Go variable with the given name and type and returns +// an *ast.Ident referring to that object. func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident { - ident := ast.NewIdent(name) - fc.setType(ident, t) obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t) - fc.pkgCtx.Uses[ident] = obj fc.objectNames[obj] = name + return fc.newIdentFor(obj) +} + +// newIdentFor creates a new *ast.Ident referring to the given Go object. +func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident { + ident := ast.NewIdent(obj.Name()) + ident.NamePos = obj.Pos() + fc.pkgCtx.Uses[ident] = obj + fc.setType(ident, obj.Type()) return ident } @@ -378,7 +428,7 @@ func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bo // allocated as needed. func (fc *funcContext) objectName(o types.Object) string { if isPkgLevel(o) { - fc.pkgCtx.dependencies[o] = true + fc.DeclareDCEDep(o) if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { return fc.pkgVar(o.Pkg()) + "." + o.Name() @@ -402,6 +452,17 @@ func (fc *funcContext) objectName(o types.Object) string { return name } +// knownInstances returns a list of known instantiations of the object. +// +// For objects without type params always returns a single trivial instance. +func (fc *funcContext) knownInstances(o types.Object) []typeparams.Instance { + if !typeparams.HasTypeParams(o.Type()) { + return []typeparams.Instance{{Object: o}} + } + + return fc.pkgCtx.instanceSet.Pkg(o.Pkg()).ByObj()[o] +} + // instName returns a JS expression that refers to the provided instance of a // function or type. Non-generic objects may be represented as an instance with // zero type arguments. @@ -462,10 +523,29 @@ func (fc *funcContext) typeName(ty types.Type) string { fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType) fc.pkgCtx.anonTypeMap.Set(ty, anonType) } - fc.pkgCtx.dependencies[anonType] = true + fc.DeclareDCEDep(anonType) return anonType.Name() } +// importedPkgVar returns a package-level variable name for accessing an imported +// package. +// +// Allocates a new variable if this is the first call, or returns the existing +// one. The variable is based on the package name (implicitly derived from the +// `package` declaration in the imported package, or explicitly assigned by the +// import decl in the importing source file). +// +// Returns the allocated variable name. +func (fc *funcContext) importedPkgVar(pkg *types.Package) string { + if pkgVar, ok := fc.pkgCtx.pkgVars[pkg.Path()]; ok { + return pkgVar // Already registered. + } + + pkgVar := fc.newVariable(pkg.Name(), true) + fc.pkgCtx.pkgVars[pkg.Path()] = pkgVar + return pkgVar +} + // instanceOf constructs an instance description of the object the ident is // referring to. For non-generic objects, it will return a trivial instance with // no type arguments.