mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
6.0 KiB
166 lines
6.0 KiB
package transform
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// LowerInterrupts creates interrupt handlers for the interrupts created by
|
|
// runtime/interrupt.New.
|
|
//
|
|
// The operation is as follows. The compiler creates the following during IR
|
|
// generation:
|
|
// - calls to runtime/interrupt.callHandlers with an interrupt number.
|
|
// - runtime/interrupt.handle objects that store the (constant) interrupt ID and
|
|
// interrupt handler func value.
|
|
//
|
|
// This pass then replaces those callHandlers calls with calls to the actual
|
|
// interrupt handlers. If there are no interrupt handlers for the given call,
|
|
// the interrupt handler is removed. For hardware vectoring, that means that the
|
|
// entire function is removed. For software vectoring, that means that the call
|
|
// is replaced with an 'unreachable' instruction.
|
|
// This might seem like it causes extra overhead, but in fact inlining and const
|
|
// propagation will eliminate most if not all of that.
|
|
func LowerInterrupts(mod llvm.Module) []error {
|
|
var errs []error
|
|
|
|
ctx := mod.Context()
|
|
builder := ctx.NewBuilder()
|
|
defer builder.Dispose()
|
|
|
|
// Collect a map of interrupt handle objects. The fact that they still
|
|
// exist in the IR indicates that they could not be optimized away,
|
|
// therefore we need to make real interrupt handlers for them.
|
|
handleMap := map[int64][]llvm.Value{}
|
|
handleType := mod.GetTypeByName("runtime/interrupt.handle")
|
|
if !handleType.IsNil() {
|
|
handlePtrType := llvm.PointerType(handleType, 0)
|
|
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
|
if global.Type() != handlePtrType {
|
|
continue
|
|
}
|
|
|
|
// Get the interrupt number from the initializer
|
|
initializer := global.Initializer()
|
|
num := llvm.ConstExtractValue(initializer, []uint32{2, 0}).SExtValue()
|
|
pkg := packageFromInterruptHandle(global)
|
|
|
|
handles, exists := handleMap[num]
|
|
|
|
// If there is an existing interrupt handler, ensure it is in the same package
|
|
// as the new one. This is to prevent any assumptions in code that a single
|
|
// compiler pass can see all packages to chain interrupt handlers. When packages are
|
|
// compiled to separate object files, the linker should spot the duplicate symbols
|
|
// for the wrapper function, failing the build.
|
|
if exists && packageFromInterruptHandle(handles[0]) != pkg {
|
|
errs = append(errs, errorAt(global,
|
|
fmt.Sprintf("handlers for interrupt %d in multiple packages: %s and %s",
|
|
num, pkg, packageFromInterruptHandle(handles[0]))))
|
|
continue
|
|
}
|
|
|
|
handleMap[num] = append(handles, global)
|
|
}
|
|
}
|
|
|
|
// Discover interrupts. The runtime/interrupt.callHandlers call is a
|
|
// compiler intrinsic that is replaced with the handlers for the given
|
|
// function.
|
|
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.callHandlers")) {
|
|
if call.IsACallInst().IsNil() {
|
|
errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.callHandlers?"))
|
|
continue
|
|
}
|
|
|
|
num := call.Operand(0)
|
|
if num.IsAConstantInt().IsNil() {
|
|
errs = append(errs, errorAt(call, "non-constant interrupt number?"))
|
|
call.InstructionParent().Parent().Dump()
|
|
continue
|
|
}
|
|
|
|
handlers := handleMap[num.SExtValue()]
|
|
if len(handlers) != 0 {
|
|
// This interrupt has at least one handler.
|
|
// Replace the callHandlers call with (possibly multiple) calls to
|
|
// these handlers.
|
|
builder.SetInsertPointBefore(call)
|
|
for _, handler := range handlers {
|
|
initializer := handler.Initializer()
|
|
context := llvm.ConstExtractValue(initializer, []uint32{0})
|
|
funcPtr := llvm.ConstExtractValue(initializer, []uint32{1}).Operand(0)
|
|
builder.CreateCall(funcPtr, []llvm.Value{
|
|
num,
|
|
context,
|
|
}, "")
|
|
}
|
|
call.EraseFromParentAsInstruction()
|
|
} else {
|
|
// No handlers. Remove the call.
|
|
fn := call.InstructionParent().Parent()
|
|
if fn.Linkage() == llvm.ExternalLinkage {
|
|
// Hardware vectoring. Remove the function entirely (redirecting
|
|
// it to the default handler).
|
|
fn.ReplaceAllUsesWith(llvm.Undef(fn.Type()))
|
|
fn.EraseFromParentAsFunction()
|
|
} else {
|
|
// Software vectoring. Erase the instruction and replace it with
|
|
// 'unreachable'.
|
|
builder.SetInsertPointBefore(call)
|
|
builder.CreateUnreachable()
|
|
// Erase all instructions that follow the unreachable
|
|
// instruction (which is a block terminator).
|
|
inst := call
|
|
for !inst.IsNil() {
|
|
next := llvm.NextInstruction(inst)
|
|
inst.EraseFromParentAsInstruction()
|
|
inst = next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace all ptrtoint uses of the interrupt handler globals with the real
|
|
// interrupt ID.
|
|
// This can now be safely done after interrupts have been lowered, doing it
|
|
// earlier may result in this interrupt handler being optimized away
|
|
// entirely (which is not what we want).
|
|
for num, handlers := range handleMap {
|
|
for _, handler := range handlers {
|
|
for _, user := range getUses(handler) {
|
|
if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
|
|
errs = append(errs, errorAt(handler, "internal error: expected a ptrtoint"))
|
|
continue
|
|
}
|
|
user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true))
|
|
}
|
|
|
|
// The runtime/interrput.handle struct can finally be removed.
|
|
// It would probably be eliminated anyway by a globaldce pass but it's
|
|
// better to do it now to be sure.
|
|
handler.EraseFromParentAsGlobal()
|
|
}
|
|
}
|
|
|
|
// Remove now-useless runtime/interrupt.use calls. These are used for some
|
|
// platforms like AVR that do not need to enable interrupts to use them, so
|
|
// need another way to keep them alive.
|
|
// After interrupts have been lowered, this call is useless and would cause
|
|
// a linker error so must be removed.
|
|
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.use")) {
|
|
if call.IsACallInst().IsNil() {
|
|
errs = append(errs, errorAt(call, "internal error: expected call to runtime/interrupt.use"))
|
|
continue
|
|
}
|
|
|
|
call.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
func packageFromInterruptHandle(handle llvm.Value) string {
|
|
return strings.Split(handle.Name(), "$")[0]
|
|
}
|
|
|