Support chained interrupt handlers

Multiple calls to interrupt.New are permitted with handlers called sequentially in undefined order.
Этот коммит содержится в:
Kenneth Bell 2021-04-29 00:46:52 -07:00 коммит произвёл Ron Evans
родитель e312cb0fe7
коммит e3b98dabfd
2 изменённых файлов: 107 добавлений и 75 удалений

Просмотреть файл

@ -41,10 +41,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// type are lowered in the interrupt lowering pass. // type are lowered in the interrupt lowering pass.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType) globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility) global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)

Просмотреть файл

@ -73,10 +73,10 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
// Create a function type with the signature of an interrupt handler. // Create a function type with the signature of an interrupt handler.
fnType := llvm.FunctionType(ctx.VoidType(), nil, false) fnType := llvm.FunctionType(ctx.VoidType(), nil, false)
// Collect a slice of interrupt handle objects. The fact that they still // Collect a map of interrupt handle objects. The fact that they still
// exist in the IR indicates that they could not be optimized away, // exist in the IR indicates that they could not be optimized away,
// therefore we need to make real interrupt handlers for them. // therefore we need to make real interrupt handlers for them.
var handlers []llvm.Value handleMap := map[int][]llvm.Value{}
handleType := mod.GetTypeByName("runtime/interrupt.handle") handleType := mod.GetTypeByName("runtime/interrupt.handle")
if !handleType.IsNil() { if !handleType.IsNil() {
handlePtrType := llvm.PointerType(handleType, 0) handlePtrType := llvm.PointerType(handleType, 0)
@ -84,14 +84,48 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
if global.Type() != handlePtrType { if global.Type() != handlePtrType {
continue continue
} }
handlers = append(handlers, global)
// Get the interrupt number from the initializer
initializer := global.Initializer()
num := int(llvm.ConstExtractValue(initializer, []uint32{1, 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 (%s) in multiple packages: %s and %s",
num, handlerNames[int64(num)], pkg, packageFromInterruptHandle(handles[0]))))
continue
}
handleMap[num] = append(handles, global)
} }
} }
// Iterate over all handler objects, replacing their ptrtoint uses with a // Output interrupts in numerical order for reproducible builds (Go map
// intentionally randomizes iteration order of maps).
interrupts := make([]int, 0, len(handleMap))
for k := range handleMap {
interrupts = append(interrupts, k)
}
sort.Ints(interrupts)
// Iterate over all handle objects, replacing their ptrtoint uses with a
// real interrupt ID and creating an interrupt handler for them. // real interrupt ID and creating an interrupt handler for them.
for _, global := range handlers { for _, interrupt := range interrupts {
initializer := global.Initializer() handles := handleMap[interrupt]
// There is always at least one handler for each interrupt number. We
// arbitrarily take the first handler to attach any errors to.
first := handles[0]
initializer := first.Initializer()
num := llvm.ConstExtractValue(initializer, []uint32{1, 0}) num := llvm.ConstExtractValue(initializer, []uint32{1, 0})
name := handlerNames[num.SExtValue()] name := handlerNames[num.SExtValue()]
@ -111,44 +145,11 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
name = "runtime/interrupt.interruptHandler" + strconv.FormatInt(num.SExtValue(), 10) name = "runtime/interrupt.interruptHandler" + strconv.FormatInt(num.SExtValue(), 10)
} }
} else { } else {
errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) errs = append(errs, errorAt(first, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
continue 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
}
if !strings.HasSuffix(global.Name(), "$withSignature") {
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected name: "+global.Name()))
continue
}
initializer := global.Initializer()
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 // Check for an existing interrupt handler, and report it as an error if
// there is one. // there is one.
fn := mod.NamedFunction(name) fn := mod.NamedFunction(name)
@ -157,25 +158,15 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
} else if fn.Type().ElementType() != fnType { } else if fn.Type().ElementType() != fnType {
// Don't bother with a precise error message (listing the previsous // Don't bother with a precise error message (listing the previsous
// location) because this should not normally happen anyway. // location) because this should not normally happen anyway.
errs = append(errs, errorAt(global, name+" redeclared with a different signature")) errs = append(errs, errorAt(first, name+" redeclared with a different signature"))
continue continue
} else if !fn.IsDeclaration() { } 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" errValue := name + " redeclared in this program"
fnPos := getPosition(fn) fnPos := getPosition(fn)
if fnPos.IsValid() { if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String() errValue += "\n\tprevious declaration at " + fnPos.String()
} }
errs = append(errs, errorAt(global, errValue)) errs = append(errs, errorAt(first, errValue))
continue continue
} }
@ -204,19 +195,56 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
} }
// For each handle (i.e. each call to interrupt.New), check the usage,
// output a call to the actual handler function and clean-up the handle
// that is no longer needed.
for _, handler := range handles {
// Extract the func value.
initializer := handler.Initializer()
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(handler, "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
}
if !strings.HasSuffix(global.Name(), "$withSignature") {
errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected name: "+global.Name()))
continue
}
initializer := global.Initializer()
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(handler, "internal error: unexpected LLVM types in func value"))
continue
}
// Fill the function declaration with the forwarding call. // Fill the function declaration with the forwarding call.
// In practice, the called function will often be inlined which avoids // In practice, the called function will often be inlined which avoids
// the extra indirection. // the extra indirection.
builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "") builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "")
builder.CreateRetVoid()
// Replace all ptrtoint uses of the global with the interrupt constant. // Replace all ptrtoint uses of the global with the interrupt constant.
// That can only now be safely done after the interrupt handler has been // That can only now be safely done after the interrupt handler has been
// created, doing it before the interrupt handler is created might // created, doing it before the interrupt handler is created might
// result in this interrupt handler being optimized away entirely. // result in this interrupt handler being optimized away entirely.
for _, user := range getUses(global) { for _, user := range getUses(handler) {
if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt { if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
errs = append(errs, errorAt(global, "internal error: expected a ptrtoint")) errs = append(errs, errorAt(handler, "internal error: expected a ptrtoint"))
continue continue
} }
user.ReplaceAllUsesWith(num) user.ReplaceAllUsesWith(num)
@ -225,9 +253,12 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
// The runtime/interrput.handle struct can finally be removed. // The runtime/interrput.handle struct can finally be removed.
// It would probably be eliminated anyway by a globaldce pass but it's // It would probably be eliminated anyway by a globaldce pass but it's
// better to do it now to be sure. // better to do it now to be sure.
global.EraseFromParentAsGlobal() handler.EraseFromParentAsGlobal()
} }
// The wrapper function has no return value
builder.CreateRetVoid()
}
// Create a dispatcher function that calls the appropriate interrupt handler // Create a dispatcher function that calls the appropriate interrupt handler
// for each interrupt ID. This is used in the case of software vectoring. // for each interrupt ID. This is used in the case of software vectoring.
// The function looks like this: // The function looks like this:
@ -294,3 +325,7 @@ func LowerInterrupts(mod llvm.Module, sizeLevel int) []error {
return errs return errs
} }
func packageFromInterruptHandle(handle llvm.Value) string {
return strings.Split(handle.Name(), "$")[0]
}