mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-09 05:23:40 +00:00
all: add compiler support for interrupts
This commit lets the compiler know about interrupts and allows optimizations to be performed based on that: interrupts are eliminated when they appear to be unused in a program. This is done with a new pseudo-call (runtime/interrupt.New) that is treated specially by the compiler.
This commit is contained in:
committed by
Ron Evans
parent
3729fcfa9e
commit
a5ed993f8d
@@ -0,0 +1,218 @@
|
||||
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.Register that map interrupt IDs to ISR names.
|
||||
// * runtime/interrupt.handle objects that store the (constant) interrupt ID and
|
||||
// interrupt handler func value.
|
||||
//
|
||||
// This pass then creates the specially named interrupt handler names that
|
||||
// simply call the registered handlers. 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
|
||||
|
||||
// Discover interrupts. The runtime/interrupt.Register call is a compiler
|
||||
// intrinsic that maps interrupt numbers to handler names.
|
||||
handlerNames := map[int64]string{}
|
||||
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.Register")) {
|
||||
if call.IsACallInst().IsNil() {
|
||||
errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.Register?"))
|
||||
continue
|
||||
}
|
||||
|
||||
num := call.Operand(0)
|
||||
if num.IsAConstant().IsNil() {
|
||||
errs = append(errs, errorAt(call, "non-constant interrupt number?"))
|
||||
continue
|
||||
}
|
||||
|
||||
// extract the interrupt name
|
||||
nameStrGEP := call.Operand(1)
|
||||
if nameStrGEP.IsAConstantExpr().IsNil() || nameStrGEP.Opcode() != llvm.GetElementPtr {
|
||||
errs = append(errs, errorAt(call, "expected a string operand?"))
|
||||
continue
|
||||
}
|
||||
nameStrPtr := nameStrGEP.Operand(0) // note: assuming it's a GEP to the first byte
|
||||
nameStrLen := call.Operand(2)
|
||||
if nameStrPtr.IsAGlobalValue().IsNil() || !nameStrPtr.IsGlobalConstant() || nameStrLen.IsAConstant().IsNil() {
|
||||
errs = append(errs, errorAt(call, "non-constant interrupt name?"))
|
||||
continue
|
||||
}
|
||||
|
||||
// keep track of this name
|
||||
name := string(getGlobalBytes(nameStrPtr)[:nameStrLen.SExtValue()])
|
||||
handlerNames[num.SExtValue()] = name
|
||||
|
||||
// remove this pseudo-call
|
||||
call.ReplaceAllUsesWith(llvm.ConstNull(call.Type()))
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
ctx := mod.Context()
|
||||
nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0))
|
||||
builder := ctx.NewBuilder()
|
||||
defer builder.Dispose()
|
||||
|
||||
// Create a function type with the signature of an interrupt handler.
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), nil, false)
|
||||
|
||||
// Collect a slice 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.
|
||||
var handlers []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
|
||||
}
|
||||
handlers = append(handlers, global)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all handler objects, replacing their ptrtoint uses with a
|
||||
// real interrupt ID and creating an interrupt handler for them.
|
||||
for _, global := range handlers {
|
||||
initializer := global.Initializer()
|
||||
num := llvm.ConstExtractValue(initializer, []uint32{1, 0})
|
||||
name := handlerNames[num.SExtValue()]
|
||||
|
||||
if name == "" {
|
||||
errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the func value.
|
||||
handlerContext := llvm.ConstExtractValue(initializer, []uint32{0, 0})
|
||||
handlerFuncPtr := llvm.ConstExtractValue(initializer, []uint32{0, 1})
|
||||
if !handlerContext.IsConstant() || !handlerFuncPtr.IsConstant() {
|
||||
// This should have been checked already in the compiler.
|
||||
errs = append(errs, errorAt(global, "func value must be constant"))
|
||||
continue
|
||||
}
|
||||
if !handlerFuncPtr.IsAConstantExpr().IsNil() && handlerFuncPtr.Opcode() == llvm.PtrToInt {
|
||||
// This is a ptrtoint: the IR was created for func lowering using a
|
||||
// switch statement.
|
||||
global := handlerFuncPtr.Operand(0)
|
||||
if global.IsAGlobalValue().IsNil() {
|
||||
errs = append(errs, errorAt(global, "internal error: expected a global for func lowering"))
|
||||
continue
|
||||
}
|
||||
initializer := global.Initializer()
|
||||
if initializer.Type() != mod.GetTypeByName("runtime.funcValueWithSignature") {
|
||||
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected type"))
|
||||
continue
|
||||
}
|
||||
ptrtoint := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected func ptr type"))
|
||||
continue
|
||||
}
|
||||
handlerFuncPtr = ptrtoint.Operand(0)
|
||||
}
|
||||
if handlerFuncPtr.Type().TypeKind() != llvm.PointerTypeKind || handlerFuncPtr.Type().ElementType().TypeKind() != llvm.FunctionTypeKind {
|
||||
errs = append(errs, errorAt(global, "internal error: unexpected LLVM types in func value"))
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for an existing interrupt handler, and report it as an error if
|
||||
// there is one.
|
||||
fn := mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, name, fnType)
|
||||
} else if fn.Type().ElementType() != fnType {
|
||||
// Don't bother with a precise error message (listing the previsous
|
||||
// location) because this should not normally happen anyway.
|
||||
errs = append(errs, errorAt(global, name+" redeclared with a different signature"))
|
||||
continue
|
||||
} else if !fn.IsDeclaration() {
|
||||
// Interrupt handler was already defined. Check the first
|
||||
// instruction (which should be a call) whether this handler would
|
||||
// be identical anyway.
|
||||
firstInst := fn.FirstBasicBlock().FirstInstruction()
|
||||
if !firstInst.IsACallInst().IsNil() && firstInst.OperandsCount() == 4 && firstInst.CalledValue() == handlerFuncPtr && firstInst.Operand(0) == num && firstInst.Operand(1) == handlerContext {
|
||||
// Already defined and apparently identical, so assume this is
|
||||
// fine.
|
||||
continue
|
||||
}
|
||||
|
||||
errValue := name + " redeclared in this program"
|
||||
fnPos := getPosition(fn)
|
||||
if fnPos.IsValid() {
|
||||
errValue += "\n\tprevious declaration at " + fnPos.String()
|
||||
}
|
||||
errs = append(errs, errorAt(global, errValue))
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the wrapper function which is the actual interrupt handler
|
||||
// that is inserted in the interrupt vector.
|
||||
fn.SetUnnamedAddr(true)
|
||||
fn.SetSection(".text." + name)
|
||||
entryBlock := ctx.AddBasicBlock(fn, "entry")
|
||||
builder.SetInsertPointAtEnd(entryBlock)
|
||||
|
||||
// Set the 'interrupt' flag if needed on this platform.
|
||||
if strings.HasPrefix(mod.Target(), "avr") {
|
||||
// This special calling convention is needed on AVR to save and
|
||||
// restore all clobbered registers, instead of just the ones that
|
||||
// would need to be saved/restored in a normal function call.
|
||||
// Note that the AVR_INTERRUPT calling convention would enable
|
||||
// interrupts right at the beginning of the handler, potentially
|
||||
// leading to lots of nested interrupts and a stack overflow.
|
||||
fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
|
||||
}
|
||||
|
||||
// Fill the function declaration with the forwarding call.
|
||||
// In practice, the called function will often be inlined which avoids
|
||||
// the extra indirection.
|
||||
builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "")
|
||||
builder.CreateRetVoid()
|
||||
|
||||
// Replace all ptrtoint uses of the global with the interrupt constant.
|
||||
// That can only now be safely done after the interrupt handler has been
|
||||
// created, doing it before the interrupt handler is created might
|
||||
// result in this interrupt handler being optimized away entirely.
|
||||
for _, user := range getUses(global) {
|
||||
if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
|
||||
errs = append(errs, errorAt(global, "internal error: expected a ptrtoint"))
|
||||
continue
|
||||
}
|
||||
user.ReplaceAllUsesWith(num)
|
||||
}
|
||||
|
||||
// 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.
|
||||
global.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
|
||||
}
|
||||
Reference in New Issue
Block a user