github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/compiler.go (about)

     1  // Package compiler implements GopherJS compiler logic.
     2  //
     3  // WARNING: This package's API is treated as internal and currently doesn't
     4  // provide any API stability guarantee, use it at your own risk. If you need a
     5  // stable interface, prefer invoking the gopherjs CLI tool as a subprocess.
     6  package compiler
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/binary"
    11  	"encoding/gob"
    12  	"encoding/json"
    13  	"fmt"
    14  	"go/token"
    15  	"go/types"
    16  	"io"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/gopherjs/gopherjs/compiler/internal/symbol"
    21  	"github.com/gopherjs/gopherjs/compiler/prelude"
    22  	"golang.org/x/tools/go/gcexportdata"
    23  )
    24  
    25  var (
    26  	sizes32          = &types.StdSizes{WordSize: 4, MaxAlign: 8}
    27  	reservedKeywords = make(map[string]bool)
    28  )
    29  
    30  func init() {
    31  	for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} {
    32  		reservedKeywords[keyword] = true
    33  	}
    34  }
    35  
    36  // Archive contains intermediate build outputs of a single package.
    37  //
    38  // This is a logical equivalent of an object file in traditional compilers.
    39  type Archive struct {
    40  	// Package's full import path, e.g. "some/package/name".
    41  	ImportPath string
    42  	// Package's name as per "package" statement at the top of a source file.
    43  	// Usually matches the last component of import path, but may differ in
    44  	// certain cases (e.g. main or test packages).
    45  	Name string
    46  	// A list of full package import paths that the current package imports across
    47  	// all source files. See go/types.Package.Imports().
    48  	Imports []string
    49  	// Serialized contents of go/types.Package in a binary format. This information
    50  	// is used by the compiler to type-check packages that import this one. See
    51  	// gcexportdata.Write().
    52  	//
    53  	// TODO(nevkontakte): It would be more convenient to store go/types.Package
    54  	// itself and only serialize it when writing the archive onto disk.
    55  	ExportData []byte
    56  	// Compiled package-level symbols.
    57  	Declarations []*Decl
    58  	// Concatenated contents of all raw .inc.js of the package.
    59  	IncJSCode []byte
    60  	// JSON-serialized contents of go/token.FileSet. This is used to obtain source
    61  	// code locations for various symbols (e.g. for sourcemap generation). See
    62  	// token.FileSet.Write().
    63  	//
    64  	// TODO(nevkontakte): This is also more convenient to store as the original
    65  	// object and only serialize before writing onto disk.
    66  	FileSet []byte
    67  	// Whether or not the package was compiled with minification enabled.
    68  	Minified bool
    69  	// A list of go:linkname directives encountered in the package.
    70  	GoLinknames []GoLinkname
    71  	// Time when this archive was built.
    72  	BuildTime time.Time
    73  }
    74  
    75  func (a Archive) String() string {
    76  	return fmt.Sprintf("compiler.Archive{%s}", a.ImportPath)
    77  }
    78  
    79  // RegisterTypes adds package type information from the archive into the provided map.
    80  func (a *Archive) RegisterTypes(packages map[string]*types.Package) error {
    81  	var err error
    82  	// TODO(nevkontakte): Should this be shared throughout the build?
    83  	fset := token.NewFileSet()
    84  	packages[a.ImportPath], err = gcexportdata.Read(bytes.NewReader(a.ExportData), fset, packages, a.ImportPath)
    85  	return err
    86  }
    87  
    88  // Decl represents a package-level symbol (e.g. a function, variable or type).
    89  //
    90  // It contains code generated by the compiler for this specific symbol, which is
    91  // grouped by the execution stage it belongs to in the JavaScript runtime.
    92  type Decl struct {
    93  	// The package- or receiver-type-qualified name of function or method obj.
    94  	// See go/types.Func.FullName().
    95  	FullName string
    96  	// A logical equivalent of a symbol name in an object file in the traditional
    97  	// Go compiler/linker toolchain. Used by GopherJS to support go:linkname
    98  	// directives. Must be set for decls that are supported by go:linkname
    99  	// implementation.
   100  	LinkingName symbol.Name
   101  	// A list of package-level JavaScript variable names this symbol needs to declare.
   102  	Vars []string
   103  	// A JS expression by which the object represented by this decl may be
   104  	// referenced within the package context. Empty if the decl represents no such
   105  	// object.
   106  	RefExpr string
   107  	// NamedRecvType is method named recv declare.
   108  	NamedRecvType string
   109  	// JavaScript code that declares basic information about a symbol. For a type
   110  	// it configures basic information about the type and its identity. For a function
   111  	// or method it contains its compiled body.
   112  	DeclCode []byte
   113  	// JavaScript code that initializes reflection metadata about type's method list.
   114  	MethodListCode []byte
   115  	// JavaScript code that initializes the rest of reflection metadata about a type
   116  	// (e.g. struct fields, array type sizes, element types, etc.).
   117  	TypeInitCode []byte
   118  	// JavaScript code that needs to be executed during the package init phase to
   119  	// set the symbol up (e.g. initialize package-level variable value).
   120  	InitCode []byte
   121  	// Symbol's identifier used by the dead-code elimination logic, not including
   122  	// package path. If empty, the symbol is assumed to be alive and will not be
   123  	// eliminated. For methods it is the same as its receiver type identifier.
   124  	DceObjectFilter string
   125  	// The second part of the identified used by dead-code elimination for methods.
   126  	// Empty for other types of symbols.
   127  	DceMethodFilter string
   128  	// List of fully qualified (including package path) DCE symbol identifiers the
   129  	// symbol depends on for dead code elimination purposes.
   130  	DceDeps []string
   131  	// Set to true if a function performs a blocking operation (I/O or
   132  	// synchronization). The compiler will have to generate function code such
   133  	// that it can be resumed after a blocking operation completes without
   134  	// blocking the main thread in the meantime.
   135  	Blocking bool
   136  }
   137  
   138  type Dependency struct {
   139  	Pkg    string
   140  	Type   string
   141  	Method string
   142  }
   143  
   144  func ImportDependencies(archive *Archive, importPkg func(string) (*Archive, error)) ([]*Archive, error) {
   145  	var deps []*Archive
   146  	paths := make(map[string]bool)
   147  	var collectDependencies func(path string) error
   148  	collectDependencies = func(path string) error {
   149  		if paths[path] {
   150  			return nil
   151  		}
   152  		dep, err := importPkg(path)
   153  		if err != nil {
   154  			return err
   155  		}
   156  		for _, imp := range dep.Imports {
   157  			if err := collectDependencies(imp); err != nil {
   158  				return err
   159  			}
   160  		}
   161  		deps = append(deps, dep)
   162  		paths[dep.ImportPath] = true
   163  		return nil
   164  	}
   165  
   166  	if err := collectDependencies("runtime"); err != nil {
   167  		return nil, err
   168  	}
   169  	for _, imp := range archive.Imports {
   170  		if err := collectDependencies(imp); err != nil {
   171  			return nil, err
   172  		}
   173  	}
   174  
   175  	deps = append(deps, archive)
   176  	return deps, nil
   177  }
   178  
   179  type dceInfo struct {
   180  	decl         *Decl
   181  	objectFilter string
   182  	methodFilter string
   183  }
   184  
   185  func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) error {
   186  	mainPkg := pkgs[len(pkgs)-1]
   187  	minify := mainPkg.Minified
   188  
   189  	// Aggregate all go:linkname directives in the program together.
   190  	gls := goLinknameSet{}
   191  	for _, pkg := range pkgs {
   192  		gls.Add(pkg.GoLinknames)
   193  	}
   194  
   195  	byFilter := make(map[string][]*dceInfo)
   196  	var pendingDecls []*Decl // A queue of live decls to find other live decls.
   197  	for _, pkg := range pkgs {
   198  		for _, d := range pkg.Declarations {
   199  			if d.DceObjectFilter == "" && d.DceMethodFilter == "" {
   200  				// This is an entry point (like main() or init() functions) or a variable
   201  				// initializer which has a side effect, consider it live.
   202  				pendingDecls = append(pendingDecls, d)
   203  				continue
   204  			}
   205  			if gls.IsImplementation(d.LinkingName) {
   206  				// If a decl is referenced by a go:linkname directive, we just assume
   207  				// it's not dead.
   208  				// TODO(nevkontakte): This is a safe, but imprecise assumption. We should
   209  				// try and trace whether the referencing functions are actually live.
   210  				pendingDecls = append(pendingDecls, d)
   211  			}
   212  			info := &dceInfo{decl: d}
   213  			if d.DceObjectFilter != "" {
   214  				info.objectFilter = pkg.ImportPath + "." + d.DceObjectFilter
   215  				byFilter[info.objectFilter] = append(byFilter[info.objectFilter], info)
   216  			}
   217  			if d.DceMethodFilter != "" {
   218  				info.methodFilter = pkg.ImportPath + "." + d.DceMethodFilter
   219  				byFilter[info.methodFilter] = append(byFilter[info.methodFilter], info)
   220  			}
   221  		}
   222  	}
   223  
   224  	dceSelection := make(map[*Decl]struct{}) // Known live decls.
   225  	for len(pendingDecls) != 0 {
   226  		d := pendingDecls[len(pendingDecls)-1]
   227  		pendingDecls = pendingDecls[:len(pendingDecls)-1]
   228  
   229  		dceSelection[d] = struct{}{} // Mark the decl as live.
   230  
   231  		// Consider all decls the current one is known to depend on and possible add
   232  		// them to the live queue.
   233  		for _, dep := range d.DceDeps {
   234  			if infos, ok := byFilter[dep]; ok {
   235  				delete(byFilter, dep)
   236  				for _, info := range infos {
   237  					if info.objectFilter == dep {
   238  						info.objectFilter = ""
   239  					}
   240  					if info.methodFilter == dep {
   241  						info.methodFilter = ""
   242  					}
   243  					if info.objectFilter == "" && info.methodFilter == "" {
   244  						pendingDecls = append(pendingDecls, info.decl)
   245  					}
   246  				}
   247  			}
   248  		}
   249  	}
   250  
   251  	if _, err := w.Write([]byte("\"use strict\";\n(function() {\n\n")); err != nil {
   252  		return err
   253  	}
   254  	if _, err := w.Write([]byte(fmt.Sprintf("var $goVersion = %q;\n", goVersion))); err != nil {
   255  		return err
   256  	}
   257  
   258  	preludeJS := prelude.Prelude
   259  	if minify {
   260  		preludeJS = prelude.Minified()
   261  	}
   262  	if _, err := io.WriteString(w, preludeJS); err != nil {
   263  		return err
   264  	}
   265  	if _, err := w.Write([]byte("\n")); err != nil {
   266  		return err
   267  	}
   268  
   269  	// write packages
   270  	for _, pkg := range pkgs {
   271  		if err := WritePkgCode(pkg, dceSelection, gls, minify, w); err != nil {
   272  			return err
   273  		}
   274  	}
   275  
   276  	if _, err := w.Write([]byte("$synthesizeMethods();\n$initAllLinknames();\nvar $mainPkg = $packages[\"" + string(mainPkg.ImportPath) + "\"];\n$packages[\"runtime\"].$init();\n$go($mainPkg.$init, []);\n$flushConsole();\n\n}).call(this);\n")); err != nil {
   277  		return err
   278  	}
   279  	return nil
   280  }
   281  
   282  func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, gls goLinknameSet, minify bool, w *SourceMapFilter) error {
   283  	if w.MappingCallback != nil && pkg.FileSet != nil {
   284  		w.fileSet = token.NewFileSet()
   285  		if err := w.fileSet.Read(json.NewDecoder(bytes.NewReader(pkg.FileSet)).Decode); err != nil {
   286  			panic(err)
   287  		}
   288  	}
   289  	if _, err := w.Write(pkg.IncJSCode); err != nil {
   290  		return err
   291  	}
   292  	if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("$packages[\"%s\"] = (function() {\n", pkg.ImportPath)), minify)); err != nil {
   293  		return err
   294  	}
   295  	vars := []string{"$pkg = {}", "$init"}
   296  	var filteredDecls []*Decl
   297  	for _, d := range pkg.Declarations {
   298  		if _, ok := dceSelection[d]; ok {
   299  			vars = append(vars, d.Vars...)
   300  			filteredDecls = append(filteredDecls, d)
   301  		}
   302  	}
   303  	if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("\tvar %s;\n", strings.Join(vars, ", "))), minify)); err != nil {
   304  		return err
   305  	}
   306  	for _, d := range filteredDecls {
   307  		if _, err := w.Write(d.DeclCode); err != nil {
   308  			return err
   309  		}
   310  		if gls.IsImplementation(d.LinkingName) {
   311  			// This decl is referenced by a go:linkname directive, expose it to external
   312  			// callers via $linkname object (declared in prelude). We are not using
   313  			// $pkg to avoid clashes with exported symbols.
   314  			var code string
   315  			if recv, method, ok := d.LinkingName.IsMethod(); ok {
   316  				code = fmt.Sprintf("\t$linknames[%q] = $unsafeMethodToFunction(%v,%q,%t);\n", d.LinkingName.String(), d.NamedRecvType, method, strings.HasPrefix(recv, "*"))
   317  			} else {
   318  				code = fmt.Sprintf("\t$linknames[%q] = %s;\n", d.LinkingName.String(), d.RefExpr)
   319  			}
   320  			if _, err := w.Write(removeWhitespace([]byte(code), minify)); err != nil {
   321  				return err
   322  			}
   323  		}
   324  	}
   325  	for _, d := range filteredDecls {
   326  		if _, err := w.Write(d.MethodListCode); err != nil {
   327  			return err
   328  		}
   329  	}
   330  	for _, d := range filteredDecls {
   331  		if _, err := w.Write(d.TypeInitCode); err != nil {
   332  			return err
   333  		}
   334  	}
   335  
   336  	{
   337  		// Set up all functions which package declares, but which implementation
   338  		// comes from elsewhere via a go:linkname compiler directive. This code
   339  		// needs to be executed after all $packages entries were defined, since such
   340  		// reference may go in a direction opposite of the import graph. It also
   341  		// needs to run before any initializer code runs, since that code may invoke
   342  		// linknamed function.
   343  		lines := []string{}
   344  		for _, d := range filteredDecls {
   345  			impl, found := gls.FindImplementation(d.LinkingName)
   346  			if !found {
   347  				continue // The symbol is not affected by a go:linkname directive.
   348  			}
   349  			lines = append(lines, fmt.Sprintf("\t\t%s = $linknames[%q];\n", d.RefExpr, impl.String()))
   350  		}
   351  		if len(lines) > 0 {
   352  			code := fmt.Sprintf("\t$pkg.$initLinknames = function() {\n%s};\n", strings.Join(lines, ""))
   353  			if _, err := w.Write(removeWhitespace([]byte(code), minify)); err != nil {
   354  				return err
   355  			}
   356  		}
   357  	}
   358  
   359  	if _, err := w.Write(removeWhitespace([]byte("\t$init = function() {\n\t\t$pkg.$init = function() {};\n\t\t/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:\n"), minify)); err != nil {
   360  		return err
   361  	}
   362  	for _, d := range filteredDecls {
   363  		if _, err := w.Write(d.InitCode); err != nil {
   364  			return err
   365  		}
   366  	}
   367  	if _, err := w.Write(removeWhitespace([]byte("\t\t/* */ } return; } if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;\n\t};\n\t$pkg.$init = $init;\n\treturn $pkg;\n})();"), minify)); err != nil {
   368  		return err
   369  	}
   370  	if _, err := w.Write([]byte("\n")); err != nil { // keep this \n even when minified
   371  		return err
   372  	}
   373  	return nil
   374  }
   375  
   376  // ReadArchive reads serialized compiled archive of the importPath package.
   377  func ReadArchive(path string, r io.Reader) (*Archive, error) {
   378  	var a Archive
   379  	if err := gob.NewDecoder(r).Decode(&a); err != nil {
   380  		return nil, err
   381  	}
   382  
   383  	return &a, nil
   384  }
   385  
   386  // WriteArchive writes compiled package archive on disk for later reuse.
   387  func WriteArchive(a *Archive, w io.Writer) error {
   388  	return gob.NewEncoder(w).Encode(a)
   389  }
   390  
   391  type SourceMapFilter struct {
   392  	Writer          io.Writer
   393  	MappingCallback func(generatedLine, generatedColumn int, originalPos token.Position)
   394  	line            int
   395  	column          int
   396  	fileSet         *token.FileSet
   397  }
   398  
   399  func (f *SourceMapFilter) Write(p []byte) (n int, err error) {
   400  	var n2 int
   401  	for {
   402  		i := bytes.IndexByte(p, '\b')
   403  		w := p
   404  		if i != -1 {
   405  			w = p[:i]
   406  		}
   407  
   408  		n2, err = f.Writer.Write(w)
   409  		n += n2
   410  		for {
   411  			i := bytes.IndexByte(w, '\n')
   412  			if i == -1 {
   413  				f.column += len(w)
   414  				break
   415  			}
   416  			f.line++
   417  			f.column = 0
   418  			w = w[i+1:]
   419  		}
   420  
   421  		if err != nil || i == -1 {
   422  			return
   423  		}
   424  		if f.MappingCallback != nil {
   425  			f.MappingCallback(f.line+1, f.column, f.fileSet.Position(token.Pos(binary.BigEndian.Uint32(p[i+1:i+5]))))
   426  		}
   427  		p = p[i+5:]
   428  		n += 5
   429  	}
   430  }