github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/cmd/go/internal/work/gc.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package work 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "log" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 19 "cmd/go/internal/base" 20 "cmd/go/internal/cfg" 21 "cmd/go/internal/load" 22 "cmd/go/internal/str" 23 "crypto/sha1" 24 ) 25 26 // The Go toolchain. 27 28 type gcToolchain struct{} 29 30 func (gcToolchain) compiler() string { 31 return base.Tool("compile") 32 } 33 34 func (gcToolchain) linker() string { 35 return base.Tool("link") 36 } 37 38 func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) { 39 p := a.Package 40 objdir := a.Objdir 41 if archive != "" { 42 ofile = archive 43 } else { 44 out := "_go_.o" 45 ofile = objdir + out 46 } 47 48 pkgpath := p.ImportPath 49 if cfg.BuildBuildmode == "plugin" { 50 pkgpath = pluginPath(a) 51 } else if p.Name == "main" { 52 pkgpath = "main" 53 } 54 gcargs := []string{"-p", pkgpath} 55 if p.Standard { 56 gcargs = append(gcargs, "-std") 57 } 58 compilingRuntime := p.Standard && (p.ImportPath == "runtime" || strings.HasPrefix(p.ImportPath, "runtime/internal")) 59 if compilingRuntime { 60 // runtime compiles with a special gc flag to emit 61 // additional reflect type data. 62 gcargs = append(gcargs, "-+") 63 } 64 65 // If we're giving the compiler the entire package (no C etc files), tell it that, 66 // so that it can give good error messages about forward declarations. 67 // Exceptions: a few standard packages have forward declarations for 68 // pieces supplied behind-the-scenes by package runtime. 69 extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.FFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles) 70 if p.Standard { 71 switch p.ImportPath { 72 case "bytes", "internal/poll", "net", "os", "runtime/pprof", "sync", "syscall", "time": 73 extFiles++ 74 } 75 } 76 if extFiles == 0 { 77 gcargs = append(gcargs, "-complete") 78 } 79 if cfg.BuildContext.InstallSuffix != "" { 80 gcargs = append(gcargs, "-installsuffix", cfg.BuildContext.InstallSuffix) 81 } 82 if a.buildID != "" { 83 gcargs = append(gcargs, "-buildid", a.buildID) 84 } 85 platform := cfg.Goos + "/" + cfg.Goarch 86 if p.Internal.OmitDebug || platform == "nacl/amd64p32" || platform == "darwin/arm" || platform == "darwin/arm64" || cfg.Goos == "plan9" { 87 gcargs = append(gcargs, "-dwarf=false") 88 } 89 if strings.HasPrefix(runtimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") { 90 gcargs = append(gcargs, "-goversion", runtimeVersion) 91 } 92 93 gcflags := str.StringList(forcedGcflags, p.Internal.Gcflags) 94 if compilingRuntime { 95 // Remove -N, if present. 96 // It is not possible to build the runtime with no optimizations, 97 // because the compiler cannot eliminate enough write barriers. 98 for i := 0; i < len(gcflags); i++ { 99 if gcflags[i] == "-N" { 100 copy(gcflags[i:], gcflags[i+1:]) 101 gcflags = gcflags[:len(gcflags)-1] 102 i-- 103 } 104 } 105 } 106 107 args := []interface{}{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", trimDir(a.Objdir), gcflags, gcargs, "-D", p.Internal.LocalPrefix} 108 if importcfg != nil { 109 if err := b.writeFile(objdir+"importcfg", importcfg); err != nil { 110 return "", nil, err 111 } 112 args = append(args, "-importcfg", objdir+"importcfg") 113 } 114 if ofile == archive { 115 args = append(args, "-pack") 116 } 117 if asmhdr { 118 args = append(args, "-asmhdr", objdir+"go_asm.h") 119 } 120 121 // Add -c=N to use concurrent backend compilation, if possible. 122 if c := gcBackendConcurrency(gcflags); c > 1 { 123 args = append(args, fmt.Sprintf("-c=%d", c)) 124 } 125 126 for _, f := range gofiles { 127 args = append(args, mkAbs(p.Dir, f)) 128 } 129 130 output, err = b.runOut(p.Dir, p.ImportPath, nil, args...) 131 return ofile, output, err 132 } 133 134 // gcBackendConcurrency returns the backend compiler concurrency level for a package compilation. 135 func gcBackendConcurrency(gcflags []string) int { 136 // First, check whether we can use -c at all for this compilation. 137 canDashC := concurrentGCBackendCompilationEnabledByDefault 138 139 switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e { 140 case "0": 141 canDashC = false 142 case "1": 143 canDashC = true 144 case "": 145 // Not set. Use default. 146 default: 147 log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e) 148 } 149 150 if os.Getenv("GOEXPERIMENT") != "" { 151 // Concurrent compilation is presumed incompatible with GOEXPERIMENTs. 152 canDashC = false 153 } 154 155 CheckFlags: 156 for _, flag := range gcflags { 157 // Concurrent compilation is presumed incompatible with any gcflags, 158 // except for a small whitelist of commonly used flags. 159 // If the user knows better, they can manually add their own -c to the gcflags. 160 switch flag { 161 case "-N", "-l", "-S", "-B", "-C", "-I": 162 // OK 163 default: 164 canDashC = false 165 break CheckFlags 166 } 167 } 168 169 if !canDashC { 170 return 1 171 } 172 173 // Decide how many concurrent backend compilations to allow. 174 // 175 // If we allow too many, in theory we might end up with p concurrent processes, 176 // each with c concurrent backend compiles, all fighting over the same resources. 177 // However, in practice, that seems not to happen too much. 178 // Most build graphs are surprisingly serial, so p==1 for much of the build. 179 // Furthermore, concurrent backend compilation is only enabled for a part 180 // of the overall compiler execution, so c==1 for much of the build. 181 // So don't worry too much about that interaction for now. 182 // 183 // However, in practice, setting c above 4 tends not to help very much. 184 // See the analysis in CL 41192. 185 // 186 // TODO(josharian): attempt to detect whether this particular compilation 187 // is likely to be a bottleneck, e.g. when: 188 // - it has no successor packages to compile (usually package main) 189 // - all paths through the build graph pass through it 190 // - critical path scheduling says it is high priority 191 // and in such a case, set c to runtime.NumCPU. 192 // We do this now when p==1. 193 if cfg.BuildP == 1 { 194 // No process parallelism. Max out c. 195 return runtime.NumCPU() 196 } 197 // Some process parallelism. Set c to min(4, numcpu). 198 c := 4 199 if ncpu := runtime.NumCPU(); ncpu < c { 200 c = ncpu 201 } 202 return c 203 } 204 205 func trimDir(dir string) string { 206 if len(dir) > 1 && dir[len(dir)-1] == filepath.Separator { 207 dir = dir[:len(dir)-1] 208 } 209 return dir 210 } 211 212 func (gcToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) { 213 p := a.Package 214 // Add -I pkg/GOOS_GOARCH so #include "textflag.h" works in .s files. 215 inc := filepath.Join(cfg.GOROOT, "pkg", "include") 216 args := []interface{}{cfg.BuildToolexec, base.Tool("asm"), "-trimpath", trimDir(a.Objdir), "-I", a.Objdir, "-I", inc, "-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch, forcedAsmflags, p.Internal.Asmflags} 217 if p.ImportPath == "runtime" && cfg.Goarch == "386" { 218 for _, arg := range forcedAsmflags { 219 if arg == "-dynlink" { 220 args = append(args, "-D=GOBUILDMODE_shared=1") 221 } 222 } 223 } 224 var ofiles []string 225 for _, sfile := range sfiles { 226 ofile := a.Objdir + sfile[:len(sfile)-len(".s")] + ".o" 227 ofiles = append(ofiles, ofile) 228 a := append(args, "-o", ofile, mkAbs(p.Dir, sfile)) 229 if err := b.run(p.Dir, p.ImportPath, nil, a...); err != nil { 230 return nil, err 231 } 232 } 233 return ofiles, nil 234 } 235 236 // toolVerify checks that the command line args writes the same output file 237 // if run using newTool instead. 238 // Unused now but kept around for future use. 239 func toolVerify(b *Builder, p *load.Package, newTool string, ofile string, args []interface{}) error { 240 newArgs := make([]interface{}, len(args)) 241 copy(newArgs, args) 242 newArgs[1] = base.Tool(newTool) 243 newArgs[3] = ofile + ".new" // x.6 becomes x.6.new 244 if err := b.run(p.Dir, p.ImportPath, nil, newArgs...); err != nil { 245 return err 246 } 247 data1, err := ioutil.ReadFile(ofile) 248 if err != nil { 249 return err 250 } 251 data2, err := ioutil.ReadFile(ofile + ".new") 252 if err != nil { 253 return err 254 } 255 if !bytes.Equal(data1, data2) { 256 return fmt.Errorf("%s and %s produced different output files:\n%s\n%s", filepath.Base(args[1].(string)), newTool, strings.Join(str.StringList(args...), " "), strings.Join(str.StringList(newArgs...), " ")) 257 } 258 os.Remove(ofile + ".new") 259 return nil 260 } 261 262 func (gcToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error { 263 var absOfiles []string 264 for _, f := range ofiles { 265 absOfiles = append(absOfiles, mkAbs(a.Objdir, f)) 266 } 267 absAfile := mkAbs(a.Objdir, afile) 268 269 // The archive file should have been created by the compiler. 270 // Since it used to not work that way, verify. 271 if !cfg.BuildN { 272 if _, err := os.Stat(absAfile); err != nil { 273 base.Fatalf("os.Stat of archive file failed: %v", err) 274 } 275 } 276 277 p := a.Package 278 if cfg.BuildN || cfg.BuildX { 279 cmdline := str.StringList(base.Tool("pack"), "r", absAfile, absOfiles) 280 b.Showcmd(p.Dir, "%s # internal", joinUnambiguously(cmdline)) 281 } 282 if cfg.BuildN { 283 return nil 284 } 285 if err := packInternal(b, absAfile, absOfiles); err != nil { 286 b.showOutput(p.Dir, p.ImportPath, err.Error()+"\n") 287 return errPrintedOutput 288 } 289 return nil 290 } 291 292 func packInternal(b *Builder, afile string, ofiles []string) error { 293 dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0) 294 if err != nil { 295 return err 296 } 297 defer dst.Close() // only for error returns or panics 298 w := bufio.NewWriter(dst) 299 300 for _, ofile := range ofiles { 301 src, err := os.Open(ofile) 302 if err != nil { 303 return err 304 } 305 fi, err := src.Stat() 306 if err != nil { 307 src.Close() 308 return err 309 } 310 // Note: Not using %-16.16s format because we care 311 // about bytes, not runes. 312 name := fi.Name() 313 if len(name) > 16 { 314 name = name[:16] 315 } else { 316 name += strings.Repeat(" ", 16-len(name)) 317 } 318 size := fi.Size() 319 fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\n", 320 name, 0, 0, 0, 0644, size) 321 n, err := io.Copy(w, src) 322 src.Close() 323 if err == nil && n < size { 324 err = io.ErrUnexpectedEOF 325 } else if err == nil && n > size { 326 err = fmt.Errorf("file larger than size reported by stat") 327 } 328 if err != nil { 329 return fmt.Errorf("copying %s to %s: %v", ofile, afile, err) 330 } 331 if size&1 != 0 { 332 w.WriteByte(0) 333 } 334 } 335 336 if err := w.Flush(); err != nil { 337 return err 338 } 339 return dst.Close() 340 } 341 342 // setextld sets the appropriate linker flags for the specified compiler. 343 func setextld(ldflags []string, compiler []string) []string { 344 for _, f := range ldflags { 345 if f == "-extld" || strings.HasPrefix(f, "-extld=") { 346 // don't override -extld if supplied 347 return ldflags 348 } 349 } 350 ldflags = append(ldflags, "-extld="+compiler[0]) 351 if len(compiler) > 1 { 352 extldflags := false 353 add := strings.Join(compiler[1:], " ") 354 for i, f := range ldflags { 355 if f == "-extldflags" && i+1 < len(ldflags) { 356 ldflags[i+1] = add + " " + ldflags[i+1] 357 extldflags = true 358 break 359 } else if strings.HasPrefix(f, "-extldflags=") { 360 ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):] 361 extldflags = true 362 break 363 } 364 } 365 if !extldflags { 366 ldflags = append(ldflags, "-extldflags="+add) 367 } 368 } 369 return ldflags 370 } 371 372 // pluginPath computes the package path for a plugin main package. 373 // 374 // This is typically the import path of the main package p, unless the 375 // plugin is being built directly from source files. In that case we 376 // combine the package build ID with the contents of the main package 377 // source files. This allows us to identify two different plugins 378 // built from two source files with the same name. 379 func pluginPath(a *Action) string { 380 p := a.Package 381 if p.ImportPath != "command-line-arguments" { 382 return p.ImportPath 383 } 384 h := sha1.New() 385 fmt.Fprintf(h, "build ID: %s\n", a.buildID) 386 for _, file := range str.StringList(p.GoFiles, p.CgoFiles, p.SFiles) { 387 data, err := ioutil.ReadFile(filepath.Join(p.Dir, file)) 388 if err != nil { 389 base.Fatalf("go: %s", err) 390 } 391 h.Write(data) 392 } 393 return fmt.Sprintf("plugin/unnamed-%x", h.Sum(nil)) 394 } 395 396 func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error { 397 cxx := len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0 398 for _, a := range root.Deps { 399 if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) { 400 cxx = true 401 } 402 } 403 var ldflags []string 404 if cfg.BuildContext.InstallSuffix != "" { 405 ldflags = append(ldflags, "-installsuffix", cfg.BuildContext.InstallSuffix) 406 } 407 if root.Package.Internal.OmitDebug { 408 ldflags = append(ldflags, "-s", "-w") 409 } 410 if cfg.BuildBuildmode == "plugin" { 411 ldflags = append(ldflags, "-pluginpath", pluginPath(root)) 412 } 413 414 // TODO(rsc): This is probably wrong - see golang.org/issue/22155. 415 if cfg.GOROOT != runtime.GOROOT() { 416 ldflags = append(ldflags, "-X=runtime/internal/sys.DefaultGoroot="+cfg.GOROOT) 417 } 418 419 // Store BuildID inside toolchain binaries as a unique identifier of the 420 // tool being run, for use by content-based staleness determination. 421 if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") { 422 ldflags = append(ldflags, "-X=cmd/internal/objabi.buildID="+root.buildID) 423 } 424 425 // If the user has not specified the -extld option, then specify the 426 // appropriate linker. In case of C++ code, use the compiler named 427 // by the CXX environment variable or defaultCXX if CXX is not set. 428 // Else, use the CC environment variable and defaultCC as fallback. 429 var compiler []string 430 if cxx { 431 compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch)) 432 } else { 433 compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch)) 434 } 435 ldflags = append(ldflags, "-buildmode="+ldBuildmode) 436 if root.buildID != "" { 437 ldflags = append(ldflags, "-buildid="+root.buildID) 438 } 439 ldflags = append(ldflags, forcedLdflags...) 440 ldflags = append(ldflags, root.Package.Internal.Ldflags...) 441 ldflags = setextld(ldflags, compiler) 442 443 // On OS X when using external linking to build a shared library, 444 // the argument passed here to -o ends up recorded in the final 445 // shared library in the LC_ID_DYLIB load command. 446 // To avoid putting the temporary output directory name there 447 // (and making the resulting shared library useless), 448 // run the link in the output directory so that -o can name 449 // just the final path element. 450 // On Windows, DLL file name is recorded in PE file 451 // export section, so do like on OS X. 452 dir := "." 453 if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" { 454 dir, out = filepath.Split(out) 455 } 456 457 return b.run(dir, root.Package.ImportPath, nil, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags, mainpkg) 458 } 459 460 func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error { 461 ldflags := []string{"-installsuffix", cfg.BuildContext.InstallSuffix} 462 ldflags = append(ldflags, "-buildmode=shared") 463 ldflags = append(ldflags, forcedLdflags...) 464 ldflags = append(ldflags, root.Package.Internal.Ldflags...) 465 cxx := false 466 for _, a := range allactions { 467 if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) { 468 cxx = true 469 } 470 } 471 // If the user has not specified the -extld option, then specify the 472 // appropriate linker. In case of C++ code, use the compiler named 473 // by the CXX environment variable or defaultCXX if CXX is not set. 474 // Else, use the CC environment variable and defaultCC as fallback. 475 var compiler []string 476 if cxx { 477 compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch)) 478 } else { 479 compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch)) 480 } 481 ldflags = setextld(ldflags, compiler) 482 for _, d := range toplevelactions { 483 if !strings.HasSuffix(d.Target, ".a") { // omit unsafe etc and actions for other shared libraries 484 continue 485 } 486 ldflags = append(ldflags, d.Package.ImportPath+"="+d.Target) 487 } 488 return b.run(".", out, nil, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags) 489 } 490 491 func (gcToolchain) cc(b *Builder, a *Action, ofile, cfile string) error { 492 return fmt.Errorf("%s: C source files not supported without cgo", mkAbs(a.Package.Dir, cfile)) 493 }