tinygo/transform/interrupt.go
Nia Waldvogel c6ae1c58fc compiler: remove parentHandle from calling convention
This removes the parentHandle argument from the internal calling convention.
It was formerly used to implment coroutines.
Now that coroutines have been removed, it is no longer necessary.
2022-01-19 14:42:02 -05:00

166 строки
6 КиБ
Go

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]
}