mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 12:33:42 +00:00
builder: add real ThinLTO mode
We use ThinLTO for linking, but we use it in a way that doesn't give most of its benefits: we merge all the bitcode files into a single LLVM module and run some optimizations on it before linking. Therefore, this works more like a traditional "full" LTO link rather than a true thin link. This commit adds a new experimental -lto=thin option to do a true ThinLTO link. The main benefit is that linking will be a lot faster, especially for large programs consisting of many packages. At the moment, it only works for programs that don't do interface type asserts and don't call interface methods. It also probably won't work on WebAssembly and baremetal systems. But it's part of a larger goal towards a truly incremental build system: https://github.com/tinygo-org/tinygo/issues/2870 Once interface type asserts and method calls are converted to a vtable-like implementation, most programs should just work on linux/darwin/windows.
This commit is contained in:
+47
-10
@@ -176,6 +176,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
DefaultStackSize: config.StackSize(),
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
Debug: true,
|
||||
LTO: config.LTO() != "legacy",
|
||||
}
|
||||
|
||||
// Load the target machine, which is the LLVM object that contains all
|
||||
@@ -452,18 +453,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// Work around a problem on Windows.
|
||||
// For some reason, WriteBitcodeToFile causes TinyGo to
|
||||
// exit with the following message:
|
||||
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
||||
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||
if compilerConfig.LTO {
|
||||
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
||||
defer buf.Dispose()
|
||||
_, err = f.Write(buf.Bytes())
|
||||
} else {
|
||||
// Otherwise, write bitcode directly to the file (probably
|
||||
// faster).
|
||||
err = llvm.WriteBitcodeToFile(mod, f)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Work around a problem on Windows.
|
||||
// For some reason, WriteBitcodeToFile causes TinyGo to
|
||||
// exit with the following message:
|
||||
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
||||
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||
defer buf.Dispose()
|
||||
_, err = f.Write(buf.Bytes())
|
||||
} else {
|
||||
// Otherwise, write bitcode directly to the file (probably
|
||||
// faster).
|
||||
err = llvm.WriteBitcodeToFile(mod, f)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// WriteBitcodeToFile doesn't produce a useful error on its
|
||||
@@ -609,8 +616,38 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
},
|
||||
}
|
||||
|
||||
// Add job to create the runtime.initAll function in a new module.
|
||||
initAllJob := &compileJob{
|
||||
description: "create runtime.initAll",
|
||||
result: filepath.Join(tmpdir, "runtime-initAll.bc"),
|
||||
run: func(job *compileJob) (err error) {
|
||||
// Create module with runtime.initAll.
|
||||
ctx := llvm.NewContext()
|
||||
defer ctx.Dispose()
|
||||
initAllMod := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
|
||||
defer initAllMod.Dispose()
|
||||
|
||||
// Write module to bitcode file.
|
||||
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(initAllMod)
|
||||
defer llvmBuf.Dispose()
|
||||
return os.WriteFile(job.result, llvmBuf.Bytes(), 0666)
|
||||
},
|
||||
}
|
||||
|
||||
// Prepare link command.
|
||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||
var linkerDependencies []*compileJob
|
||||
switch config.LTO() {
|
||||
case "legacy":
|
||||
// Link all Go bitcode files together into a single large module and
|
||||
// then do a ThinLTO link with the resulting large module + extra C
|
||||
// files (from CGo etc).
|
||||
linkerDependencies = append(linkerDependencies, outputObjectFileJob)
|
||||
case "thin":
|
||||
// Do a real thin link, with each Go package in a separate translation
|
||||
// unit. This is faster than merging them into one big LTO module.
|
||||
linkerDependencies = append(linkerDependencies, packageJobs...)
|
||||
linkerDependencies = append(linkerDependencies, initAllJob)
|
||||
}
|
||||
result.Executable = filepath.Join(tmpdir, "main")
|
||||
if config.GOOS() == "windows" {
|
||||
result.Executable += ".exe"
|
||||
|
||||
Reference in New Issue
Block a user