Browse Source

avr: support ThinLTO

ThinLTO results in a small code size reduction, which is nice
(especially on these very small chips). It also brings us one step
closer to using ThinLTO everywhere.
pull/3298/head
Ayke van Laethem 2 years ago
committed by Ron Evans
parent
commit
4d14d3cd54
  1. 5
      compileopts/config.go
  2. 32
      compiler/llvmutil/llvm.go
  3. 13
      compiler/symbol.go
  4. 2
      src/runtime/arch_avr.go
  5. 2
      targets/avr.ld
  6. 2
      transform/stacksize.go

5
compileopts/config.go

@ -200,11 +200,6 @@ func (c *Config) UseThinLTO() bool {
// wasm-ld doesn't seem to support ThinLTO yet. // wasm-ld doesn't seem to support ThinLTO yet.
return false return false
} }
if parts[0] == "avr" {
// These use external (GNU) linkers which might perhaps support ThinLTO
// through a plugin, but it's too much hassle to set up.
return false
}
// Other architectures support ThinLTO. // Other architectures support ThinLTO.
return true return true
} }

32
compiler/llvmutil/llvm.go

@ -196,21 +196,31 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv
return newBlock return newBlock
} }
// Append the given values to the llvm.used array. The values can be any pointer // Append the given values to a global array like llvm.used. The global might
// type, they will be bitcast to i8*. // not exist yet. The values can be any pointer type, they will be cast to i8*.
func AppendToUsedGlobals(mod llvm.Module, values ...llvm.Value) { func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
if !mod.NamedGlobal("llvm.used").IsNil() { // Read the existing values in the llvm.used array (if it exists).
// Sanity check. TODO: we don't emit such a global at the moment, but var usedValues []llvm.Value
// when we do we should append to it instead. if used := mod.NamedGlobal(globalName); !used.IsNil() {
panic("todo: append to existing llvm.used") builder := mod.Context().NewBuilder()
defer builder.Dispose()
usedInitializer := used.Initializer()
num := usedInitializer.Type().ArrayLength()
for i := 0; i < num; i++ {
usedValues = append(usedValues, builder.CreateExtractValue(usedInitializer, i, ""))
}
used.EraseFromParentAsGlobal()
} }
// Add the new values.
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
var castValues []llvm.Value
for _, value := range values { for _, value := range values {
castValues = append(castValues, llvm.ConstBitCast(value, i8ptrType)) usedValues = append(usedValues, llvm.ConstPointerCast(value, i8ptrType))
} }
usedInitializer := llvm.ConstArray(i8ptrType, castValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), "llvm.used") // Create a new array (with the old and new values).
usedInitializer := llvm.ConstArray(i8ptrType, usedValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName)
used.SetInitializer(usedInitializer) used.SetInitializer(usedInitializer)
used.SetLinkage(llvm.AppendingLinkage) used.SetLinkage(llvm.AppendingLinkage)
} }

13
compiler/symbol.go

@ -150,6 +150,19 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// that the only thing we'll do is read the pointer. // that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "__mulsi3", "__divmodsi4", "__udivmodsi4":
if strings.Split(c.Triple, "-")[0] == "avr" {
// These functions are compiler-rt/libgcc functions that are
// currently implemented in Go. Assembly versions should appear in
// LLVM 16 hopefully. Until then, they need to be made available to
// the linker and the best way to do that is llvm.compiler.used.
// I considered adding a pragma for this, but the LLVM language
// reference explicitly says that this feature should not be exposed
// to source languages:
// > This is a rare construct that should only be used in rare
// > circumstances, and should not be exposed to source languages.
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
}
} }
// External/exported functions may not retain pointer values. // External/exported functions may not retain pointer values.

2
src/runtime/arch_avr.go

@ -40,6 +40,8 @@ func procUnpin() {
// The following functions are workarounds for things missing in compiler-rt. // The following functions are workarounds for things missing in compiler-rt.
// They will likely need special assembly implementations. // They will likely need special assembly implementations.
// They are treated specially: they're added to @llvm.compiler.used so that the
// linker won't eliminate them.
//export __mulsi3 //export __mulsi3
func __mulsi3(a, b uint32) uint32 { func __mulsi3(a, b uint32) uint32 {

2
targets/avr.ld

@ -5,7 +5,7 @@ MEMORY
RAM (xrw) : ORIGIN = 0x800000 + __ram_start, LENGTH = __ram_size RAM (xrw) : ORIGIN = 0x800000 + __ram_start, LENGTH = __ram_size
} }
ENTRY(__vector_RESET) ENTRY(main)
SECTIONS SECTIONS
{ {

2
transform/stacksize.go

@ -53,7 +53,7 @@ func CreateStackSizeLoads(mod llvm.Module, config *compileopts.Config) []string
stackSizesGlobal.SetInitializer(llvm.ConstArray(functions[0].Type(), defaultStackSizes)) stackSizesGlobal.SetInitializer(llvm.ConstArray(functions[0].Type(), defaultStackSizes))
// Add all relevant values to llvm.used (for LTO). // Add all relevant values to llvm.used (for LTO).
llvmutil.AppendToUsedGlobals(mod, append([]llvm.Value{stackSizesGlobal}, functionValues...)...) llvmutil.AppendToGlobal(mod, "llvm.used", append([]llvm.Value{stackSizesGlobal}, functionValues...)...)
// Replace the calls with loads from the new global with stack sizes. // Replace the calls with loads from the new global with stack sizes.
irbuilder := ctx.NewBuilder() irbuilder := ctx.NewBuilder()

Loading…
Cancel
Save