builder: refactor link file inputs

Add a 'result' member to the compileJob struct which is used by the link
job to get all the paths that should be linked together. This is not yet
necessary (the paths are fixed), but soon the paths are only known after
a linker dependency has run.
Этот коммит содержится в:
Ayke van Laethem 2021-03-11 22:23:32 +01:00 коммит произвёл Ron Evans
родитель 99a41bec4e
коммит 83a949647f
3 изменённых файлов: 58 добавлений и 47 удалений

Просмотреть файл

@ -181,7 +181,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// The package has not yet been compiled, so create a job to do so. // The package has not yet been compiled, so create a job to do so.
job := &compileJob{ job := &compileJob{
description: "compile package " + pkg.ImportPath, description: "compile package " + pkg.ImportPath,
run: func() error { run: func(*compileJob) error {
// Compile AST to IR. The compiler.CompilePackage function will // Compile AST to IR. The compiler.CompilePackage function will
// build the SSA as needed. // build the SSA as needed.
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
@ -250,7 +250,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
programJob := &compileJob{ programJob := &compileJob{
description: "link+optimize packages (LTO)", description: "link+optimize packages (LTO)",
dependencies: packageJobs, dependencies: packageJobs,
run: func() error { run: func(*compileJob) error {
// Load and link all the bitcode files. This does not yet optimize // Load and link all the bitcode files. This does not yet optimize
// anything, it only links the bitcode files together. // anything, it only links the bitcode files together.
ctx := llvm.NewContext() ctx := llvm.NewContext()
@ -370,7 +370,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
outputObjectFileJob := &compileJob{ outputObjectFileJob := &compileJob{
description: "generate output file", description: "generate output file",
dependencies: []*compileJob{programJob}, dependencies: []*compileJob{programJob},
run: func() error { result: objfile,
run: func(*compileJob) error {
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil { if err != nil {
return err return err
@ -384,40 +385,32 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
linkerDependencies := []*compileJob{outputObjectFileJob} linkerDependencies := []*compileJob{outputObjectFileJob}
executable := filepath.Join(dir, "main") executable := filepath.Join(dir, "main")
tmppath := executable // final file tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile) ldflags := append(config.LDFlags(), "-o", executable)
// Add compiler-rt dependency if needed. Usually this is a simple load from // Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache. // a cache.
if config.Target.RTLib == "compiler-rt" { if config.Target.RTLib == "compiler-rt" {
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir) job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
if err != nil { if err != nil {
return err return err
} }
if job != nil {
// The library was not loaded from cache so needs to be compiled
// (and then stored in the cache).
jobs = append(jobs, job.dependencies...) jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job) jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
ldflags = append(ldflags, path)
}
// Add libc dependency if needed. // Add libc dependency if needed.
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
switch config.Target.Libc { switch config.Target.Libc {
case "picolibc": case "picolibc":
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir) job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil { if err != nil {
return err return err
} }
if job != nil {
// The library needs to be compiled (cache miss). // The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...) jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job) jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
}
ldflags = append(ldflags, path)
case "wasi-libc": case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
@ -438,7 +431,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o") outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func() error { result: outpath,
run: func(*compileJob) error {
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...) err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
if err != nil { if err != nil {
return &commandError{"failed to build", path, err} return &commandError{"failed to build", path, err}
@ -448,7 +442,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
jobs = append(jobs, job) jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
ldflags = append(ldflags, outpath)
} }
// Add jobs to compile C files in all packages. This is part of CGo. // Add jobs to compile C files in all packages. This is part of CGo.
@ -460,7 +453,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o") outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o")
job := &compileJob{ job := &compileJob{
description: "compile CGo file " + file, description: "compile CGo file " + file,
run: func() error { result: outpath,
run: func(*compileJob) error {
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil { if err != nil {
return &commandError{"failed to build", file, err} return &commandError{"failed to build", file, err}
@ -470,7 +464,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
jobs = append(jobs, job) jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
ldflags = append(ldflags, outpath)
} }
} }
@ -485,7 +478,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
jobs = append(jobs, &compileJob{ jobs = append(jobs, &compileJob{
description: "link", description: "link",
dependencies: linkerDependencies, dependencies: linkerDependencies,
run: func() error { run: func(job *compileJob) error {
for _, dependency := range job.dependencies {
if dependency.result == "" {
return errors.New("dependency without result: " + dependency.description)
}
ldflags = append(ldflags, dependency.result)
}
err = link(config.Target.Linker, ldflags...) err = link(config.Target.Linker, ldflags...)
if err != nil { if err != nil {
return &commandError{"failed to link", executable, err} return &commandError{"failed to link", executable, err}

Просмотреть файл

@ -27,12 +27,23 @@ const (
type compileJob struct { type compileJob struct {
description string // description, only used for logging description string // description, only used for logging
dependencies []*compileJob dependencies []*compileJob
run func() error result string // result (path)
run func(*compileJob) (err error)
state jobState state jobState
err error // error if finished err error // error if finished
duration time.Duration // how long it took to run this job (only set after finishing) duration time.Duration // how long it took to run this job (only set after finishing)
} }
// dummyCompileJob returns a new *compileJob that produces an output without
// doing anything. This can be useful where a *compileJob producing an output is
// expected but nothing needs to be done, for example for a load from a cache.
func dummyCompileJob(result string) *compileJob {
return &compileJob{
description: "<dummy>",
result: result,
}
}
// readyToRun returns whether this job is ready to run: it is itself not yet // readyToRun returns whether this job is ready to run: it is itself not yet
// started and all dependencies are finished. // started and all dependencies are finished.
func (job *compileJob) readyToRun() bool { func (job *compileJob) readyToRun() bool {
@ -150,10 +161,12 @@ func nextJob(jobs []*compileJob) *compileJob {
func jobWorker(workerChan, doneChan chan *compileJob) { func jobWorker(workerChan, doneChan chan *compileJob) {
for job := range workerChan { for job := range workerChan {
start := time.Now() start := time.Now()
err := job.run() if job.run != nil {
err := job.run(job)
if err != nil { if err != nil {
job.err = err job.err = err
} }
}
job.duration = time.Since(start) job.duration = time.Since(start)
doneChan <- job doneChan <- job
} }

Просмотреть файл

@ -42,30 +42,28 @@ func (l *Library) sourcePaths(target string) []string {
// The resulting file is stored in the provided tmpdir, which is expected to be // The resulting file is stored in the provided tmpdir, which is expected to be
// removed after the Load call. // removed after the Load call.
func (l *Library) Load(target, tmpdir string) (path string, err error) { func (l *Library) Load(target, tmpdir string) (path string, err error) {
path, job, err := l.load(target, "", tmpdir) job, err := l.load(target, "", tmpdir)
if err != nil { if err != nil {
return "", err return "", err
} }
if job != nil {
jobs := append([]*compileJob{job}, job.dependencies...) jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs) err = runJobs(jobs)
} return job.result, err
return path, err
} }
// load returns a path to the library file for the given target, loading it from // load returns a compile job to build this library file for the given target
// cache if possible. It will return a non-zero compiler job if the library // and CPU. It may return a dummy compileJob if the library build is already
// wasn't cached, this job (and its dependencies) must be run before the library // cached. The path is stored as job.result but is only valid if the job and
// path is valid. // job.dependencies have been run.
// The provided tmpdir will be used to store intermediary files and possibly the // The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use. // output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob, err error) { func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
// Try to load a precompiled library. // Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a") precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil { if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled library for this OS/architecture. Return the path // Found a precompiled library for this OS/architecture. Return the path
// directly. // directly.
return precompiledPath, nil, nil return dummyCompileJob(precompiledPath), nil
} }
var outfile string var outfile string
@ -78,7 +76,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
// Try to fetch this library from the cache. // Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil { if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
// Cache hit. // Cache hit.
return path, nil, err return dummyCompileJob(path), nil
} }
// Cache miss, build it now. // Cache miss, build it now.
@ -86,7 +84,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
dir := filepath.Join(tmpdir, "build-lib-"+l.name) dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777) err = os.Mkdir(dir, 0777)
if err != nil { if err != nil {
return "", nil, err return nil, err
} }
// Precalculate the flags to the compiler invocation. // Precalculate the flags to the compiler invocation.
@ -113,7 +111,8 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
arpath := filepath.Join(dir, l.name+".a") arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{ job = &compileJob{
description: "ar " + l.name + ".a", description: "ar " + l.name + ".a",
run: func() error { result: arpath,
run: func(*compileJob) error {
// Create an archive of all object files. // Create an archive of all object files.
err := makeArchive(arpath, objs) err := makeArchive(arpath, objs)
if err != nil { if err != nil {
@ -133,7 +132,7 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
objs = append(objs, objpath) objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{ job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath, description: "compile " + srcpath,
run: func() error { run: func(*compileJob) error {
var compileArgs []string var compileArgs []string
compileArgs = append(compileArgs, args...) compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath) compileArgs = append(compileArgs, "-o", objpath, srcpath)
@ -146,5 +145,5 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
}) })
} }
return arpath, job, nil return job, nil
} }