tinygo/transform/stacksize.go
Ayke van Laethem 603fff78d4 all: add support for ThinLTO
ThinLTO optimizes across LLVM modules at link time. This means that
optimizations (such as inlining and const-propagation) are possible
between C and Go. This makes this change especially useful for CGo, but
not just for CGo. By doing some optimizations at link time, the linker
can discard some unused functions and this leads to a size reduction on
average. It does increase code size in some cases, but that's true for
most optimizations.

I've excluded a number of targets for now (wasm, avr, xtensa, windows,
macos). They can probably be supported with some more work, but that
should be done in separate PRs.

Overall, this change results in an average 3.24% size reduction over all
the tinygo.org/x/drivers smoke tests.

TODO: this commit runs part of the pass pipeline twice. We should set
the PrepareForThinLTO flag in the PassManagerBuilder for even further
reduced code size (0.7%) and improved compilation speed.
2022-03-12 12:55:38 +01:00

88 строки
3,4 КиБ
Go

package transform
import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm"
)
// CreateStackSizeLoads replaces internal/task.getGoroutineStackSize calls with
// loads from internal/task.stackSizes that will be updated after linking. This
// way the stack sizes are loaded from a separate section and can easily be
// modified after linking.
func CreateStackSizeLoads(mod llvm.Module, config *compileopts.Config) []string {
functionMap := map[llvm.Value][]llvm.Value{}
var functions []llvm.Value // ptrtoint values of functions
var functionNames []string
var functionValues []llvm.Value // direct references to functions
for _, use := range getUses(mod.NamedFunction("internal/task.getGoroutineStackSize")) {
if use.FirstUse().IsNil() {
// Apparently this stack size isn't used.
use.EraseFromParentAsInstruction()
continue
}
ptrtoint := use.Operand(0)
if _, ok := functionMap[ptrtoint]; !ok {
functions = append(functions, ptrtoint)
functionNames = append(functionNames, ptrtoint.Operand(0).Name())
functionValues = append(functionValues, ptrtoint.Operand(0))
}
functionMap[ptrtoint] = append(functionMap[ptrtoint], use)
}
if len(functions) == 0 {
// Nothing to do.
return nil
}
// Create the new global with stack sizes, that will be put in a new section
// just for itself.
stackSizesGlobalType := llvm.ArrayType(functions[0].Type(), len(functions))
stackSizesGlobal := llvm.AddGlobal(mod, stackSizesGlobalType, "internal/task.stackSizes")
stackSizesGlobal.SetSection(".tinygo_stacksizes")
defaultStackSizes := make([]llvm.Value, len(functions))
defaultStackSize := llvm.ConstInt(functions[0].Type(), config.Target.DefaultStackSize, false)
for i := range defaultStackSizes {
defaultStackSizes[i] = defaultStackSize
}
stackSizesGlobal.SetInitializer(llvm.ConstArray(functions[0].Type(), defaultStackSizes))
// Add all relevant values to llvm.used (for LTO).
appendToUsedGlobals(mod, append([]llvm.Value{stackSizesGlobal}, functionValues...)...)
// Replace the calls with loads from the new global with stack sizes.
irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose()
for i, function := range functions {
for _, use := range functionMap[function] {
ptr := llvm.ConstGEP(stackSizesGlobal, []llvm.Value{
llvm.ConstInt(mod.Context().Int32Type(), 0, false),
llvm.ConstInt(mod.Context().Int32Type(), uint64(i), false),
})
irbuilder.SetInsertPointBefore(use)
stacksize := irbuilder.CreateLoad(ptr, "stacksize")
use.ReplaceAllUsesWith(stacksize)
use.EraseFromParentAsInstruction()
}
}
return functionNames
}
// Append the given values to the llvm.used array. The values can be any pointer
// type, they will be bitcast to i8*.
func appendToUsedGlobals(mod llvm.Module, values ...llvm.Value) {
if !mod.NamedGlobal("llvm.used").IsNil() {
// Sanity check. TODO: we don't emit such a global at the moment, but
// when we do we should append to it instead.
panic("todo: append to existing llvm.used")
}
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
var castValues []llvm.Value
for _, value := range values {
castValues = append(castValues, llvm.ConstBitCast(value, i8ptrType))
}
usedInitializer := llvm.ConstArray(i8ptrType, castValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), "llvm.used")
used.SetInitializer(usedInitializer)
used.SetLinkage(llvm.AppendingLinkage)
}