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 }