diff --git a/compileopts/config.go b/compileopts/config.go index 657dbd09..6eaf5d62 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -200,11 +200,6 @@ func (c *Config) UseThinLTO() bool { // wasm-ld doesn't seem to support ThinLTO yet. 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. return true } diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index 34274688..a79ecf9c 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -196,21 +196,31 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv return newBlock } -// 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") +// Append the given values to a global array like llvm.used. The global might +// not exist yet. The values can be any pointer type, they will be cast to i8*. +func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { + // Read the existing values in the llvm.used array (if it exists). + var usedValues []llvm.Value + if used := mod.NamedGlobal(globalName); !used.IsNil() { + 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) - var castValues []llvm.Value 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.SetLinkage(llvm.AppendingLinkage) } diff --git a/compiler/symbol.go b/compiler/symbol.go index 61ca4473..87e7b1ed 100644 --- a/compiler/symbol.go +++ b/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. llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 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. diff --git a/src/runtime/arch_avr.go b/src/runtime/arch_avr.go index 9df90fcc..61c08ea2 100644 --- a/src/runtime/arch_avr.go +++ b/src/runtime/arch_avr.go @@ -40,6 +40,8 @@ func procUnpin() { // The following functions are workarounds for things missing in compiler-rt. // 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 func __mulsi3(a, b uint32) uint32 { diff --git a/targets/avr.ld b/targets/avr.ld index a3460c13..293a06d7 100644 --- a/targets/avr.ld +++ b/targets/avr.ld @@ -5,7 +5,7 @@ MEMORY RAM (xrw) : ORIGIN = 0x800000 + __ram_start, LENGTH = __ram_size } -ENTRY(__vector_RESET) +ENTRY(main) SECTIONS { diff --git a/transform/stacksize.go b/transform/stacksize.go index 44409c5f..3e46a579 100644 --- a/transform/stacksize.go +++ b/transform/stacksize.go @@ -53,7 +53,7 @@ func CreateStackSizeLoads(mod llvm.Module, config *compileopts.Config) []string stackSizesGlobal.SetInitializer(llvm.ConstArray(functions[0].Type(), defaultStackSizes)) // 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. irbuilder := ctx.NewBuilder()