diff --git a/compiler/compiler.go b/compiler/compiler.go index 5caf932d..fabfa54b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -846,6 +846,17 @@ func (b *builder) createFunction() { b.llvmFn.AddFunctionAttr(noinline) } + if b.info.interrupt { + // Mark this function as an interrupt. + // This is necessary on MCUs that don't push caller saved registers when + // entering an interrupt, such as on AVR. + if strings.HasPrefix(b.Triple, "avr") { + b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("signal", "")) + } else { + b.addError(b.fn.Pos(), "//go:interrupt not supported on this architecture") + } + } + // Add debug info, if needed. if b.Debug { if b.fn.Synthetic == "package initializer" { diff --git a/compiler/func.go b/compiler/func.go index a82d1f1b..fa940570 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -78,11 +78,24 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value { // value. This may be an expensive operation. func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { context = b.CreateExtractValue(funcValue, 0, "") - llvmSig := b.getRawFuncType(sig) switch b.FuncImplementation { case "doubleword": - funcPtr = b.CreateBitCast(b.CreateExtractValue(funcValue, 1, ""), llvmSig, "") + bitcast := b.CreateExtractValue(funcValue, 1, "") + if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast { + funcPtr = bitcast.Operand(0) + return + } + llvmSig := b.getRawFuncType(sig) + funcPtr = b.CreateBitCast(bitcast, llvmSig, "") case "switch": + if !funcValue.IsAConstant().IsNil() { + // If this is a constant func value, the underlying function is + // known and can be returned directly. + funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0) + funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0) + return + } + llvmSig := b.getRawFuncType(sig) sigGlobal := b.getFuncSignatureID(sig) funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "") diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 2437ad45..bcd407cd 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -36,6 +36,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Fall back to a generic error. return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") } + funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil) + funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType) // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. @@ -47,8 +49,9 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro global.SetGlobalConstant(true) global.SetUnnamedAddr(true) initializer := llvm.ConstNull(globalLLVMType) - initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0}) - initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0}) + initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0}) + initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1}) + initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0}) global.SetInitializer(initializer) // Add debug info to the interrupt global. diff --git a/compiler/symbol.go b/compiler/symbol.go index 469066af..902277a5 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -26,6 +26,7 @@ type functionInfo struct { linkName string // go:linkname, go:export - The name that we map for the particular module -> importName section string // go:section - object file section name exported bool // go:export, CGo + interrupt bool // go:interrupt nobounds bool // go:nobounds variadic bool // go:variadic (CGo only) inline inlineType // go:inline @@ -251,6 +252,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { importName = parts[1] info.exported = true + case "//go:interrupt": + if hasUnsafeImport(f.Pkg.Pkg) { + info.interrupt = true + } case "//go:wasm-module": // Alternative comment for setting the import module. if len(parts) != 2 { diff --git a/src/machine/machine_gameboyadvance.go b/src/machine/machine_gameboyadvance.go index 100c2bce..9387749e 100644 --- a/src/machine/machine_gameboyadvance.go +++ b/src/machine/machine_gameboyadvance.go @@ -4,6 +4,7 @@ package machine import ( "image/color" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -11,20 +12,20 @@ import ( // Interrupt numbers as used on the GameBoy Advance. Register them with // runtime/interrupt.New. const ( - IRQ_VBLANK = 0 - IRQ_HBLANK = 1 - IRQ_VCOUNT = 2 - IRQ_TIMER0 = 3 - IRQ_TIMER1 = 4 - IRQ_TIMER2 = 5 - IRQ_TIMER3 = 6 - IRQ_COM = 7 - IRQ_DMA0 = 8 - IRQ_DMA1 = 9 - IRQ_DMA2 = 10 - IRQ_DMA3 = 11 - IRQ_KEYPAD = 12 - IRQ_GAMEPAK = 13 + IRQ_VBLANK = interrupt.IRQ_VBLANK + IRQ_HBLANK = interrupt.IRQ_HBLANK + IRQ_VCOUNT = interrupt.IRQ_VCOUNT + IRQ_TIMER0 = interrupt.IRQ_TIMER0 + IRQ_TIMER1 = interrupt.IRQ_TIMER1 + IRQ_TIMER2 = interrupt.IRQ_TIMER2 + IRQ_TIMER3 = interrupt.IRQ_TIMER3 + IRQ_COM = interrupt.IRQ_COM + IRQ_DMA0 = interrupt.IRQ_DMA0 + IRQ_DMA1 = interrupt.IRQ_DMA1 + IRQ_DMA2 = interrupt.IRQ_DMA2 + IRQ_DMA3 = interrupt.IRQ_DMA3 + IRQ_KEYPAD = interrupt.IRQ_KEYPAD + IRQ_GAMEPAK = interrupt.IRQ_GAMEPAK ) // Make it easier to directly write to I/O RAM. diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go index 498389b6..a8cf6f4e 100644 --- a/src/runtime/interrupt/interrupt.go +++ b/src/runtime/interrupt/interrupt.go @@ -2,6 +2,8 @@ // to define interrupts and to enable/disable them. package interrupt +import "unsafe" + // Interrupt provides direct access to hardware interrupts. You can configure // this interrupt through this interface. // @@ -28,6 +30,7 @@ func New(id int, handler func(Interrupt)) Interrupt // individually be enabled/disabled, the compiler should create a pseudo-call // (like runtime/interrupt.use()) that keeps the interrupt alive. type handle struct { - handler func(Interrupt) + context unsafe.Pointer + funcPtr uintptr Interrupt } diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index 20b055f8..a3781488 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -74,7 +74,7 @@ func handleInterrupt() { riscv.MSTATUS.SetBits(0x8) // Call registered interrupt handler(s) - callInterruptHandler(int(interruptNumber)) + esp.HandleInterrupt(int(interruptNumber)) // disable CPU interrupts riscv.MSTATUS.ClearBits(0x8) @@ -107,8 +107,3 @@ func handleException(mcause uintptr) { riscv.Asm("wfi") } } - -// callInterruptHandler is a compiler-generated function that calls the -// appropriate interrupt handler for the given interrupt ID. -//go:linkname callInterruptHandler runtime.callInterruptHandler -func callInterruptHandler(id int) diff --git a/src/runtime/interrupt/interrupt_gameboyadvance.go b/src/runtime/interrupt/interrupt_gameboyadvance.go index 658a2b9c..b0055070 100644 --- a/src/runtime/interrupt/interrupt_gameboyadvance.go +++ b/src/runtime/interrupt/interrupt_gameboyadvance.go @@ -7,6 +7,23 @@ import ( "unsafe" ) +const ( + IRQ_VBLANK = 0 + IRQ_HBLANK = 1 + IRQ_VCOUNT = 2 + IRQ_TIMER0 = 3 + IRQ_TIMER1 = 4 + IRQ_TIMER2 = 5 + IRQ_TIMER3 = 6 + IRQ_COM = 7 + IRQ_DMA0 = 8 + IRQ_DMA1 = 9 + IRQ_DMA2 = 10 + IRQ_DMA3 = 11 + IRQ_KEYPAD = 12 + IRQ_GAMEPAK = 13 +) + var ( regInterruptEnable = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000200))) regInterruptRequestFlags = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000202))) @@ -30,10 +47,44 @@ func handleInterrupt() { } } -// callInterruptHandler is a compiler-generated function that calls the -// appropriate interrupt handler for the given interrupt ID. -//go:linkname callInterruptHandler runtime.callInterruptHandler -func callInterruptHandler(id int) +// Pseudo function call that is replaced by the compiler with the actual +// functions registered through interrupt.New. If there are none, calls will be +// replaced with 'unreachablecalls will be replaced with 'unreachable'. +//go:linkname callHandlers runtime/interrupt.callHandlers +func callHandlers(num int) + +func callInterruptHandler(id int) { + switch id { + case IRQ_VBLANK: + callHandlers(IRQ_VBLANK) + case IRQ_HBLANK: + callHandlers(IRQ_HBLANK) + case IRQ_VCOUNT: + callHandlers(IRQ_VCOUNT) + case IRQ_TIMER0: + callHandlers(IRQ_TIMER0) + case IRQ_TIMER1: + callHandlers(IRQ_TIMER1) + case IRQ_TIMER2: + callHandlers(IRQ_TIMER2) + case IRQ_TIMER3: + callHandlers(IRQ_TIMER3) + case IRQ_COM: + callHandlers(IRQ_COM) + case IRQ_DMA0: + callHandlers(IRQ_DMA0) + case IRQ_DMA1: + callHandlers(IRQ_DMA1) + case IRQ_DMA2: + callHandlers(IRQ_DMA2) + case IRQ_DMA3: + callHandlers(IRQ_DMA3) + case IRQ_KEYPAD: + callHandlers(IRQ_KEYPAD) + case IRQ_GAMEPAK: + callHandlers(IRQ_GAMEPAK) + } +} // State represents the previous global interrupt state. type State uint8 diff --git a/src/runtime/interrupt/interrupt_hwvector.go b/src/runtime/interrupt/interrupt_hwvector.go deleted file mode 100644 index f210da58..00000000 --- a/src/runtime/interrupt/interrupt_hwvector.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build avr cortexm - -package interrupt - -// Register is used to declare an interrupt. You should not normally call this -// function: it is only for telling the compiler about the mapping between an -// interrupt number and the interrupt handler name. -func Register(id int, handlerName string) int diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index 5cfa7a69..33d8e483 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -65,7 +65,7 @@ func handleInterrupt() { // Claim this interrupt. id := sifive.PLIC.CLAIM.Get() // Call the interrupt handler, if any is registered for this ID. - callInterruptHandler(int(id)) + sifive.HandleInterrupt(int(id)) // Complete this interrupt. sifive.PLIC.CLAIM.Set(id) } @@ -147,7 +147,3 @@ func handleException(code uint) { println() abort() } - -// callInterruptHandler is a compiler-generated function that calls the -// appropriate interrupt handler for the given interrupt ID. -func callInterruptHandler(id int) diff --git a/src/runtime/runtime_k210.go b/src/runtime/runtime_k210.go index c94153ba..dc8ee044 100644 --- a/src/runtime/runtime_k210.go +++ b/src/runtime/runtime_k210.go @@ -85,7 +85,7 @@ func handleInterrupt() { // Claim this interrupt. id := kendryte.PLIC.TARGETS[hartId].CLAIM.Get() // Call the interrupt handler, if any is registered for this ID. - callInterruptHandler(int(id)) + kendryte.HandleInterrupt(int(id)) // Complete this interrupt. kendryte.PLIC.TARGETS[hartId].CLAIM.Set(id) } @@ -153,7 +153,3 @@ func handleException(code uint64) { println() abort() } - -// callInterruptHandler is a compiler-generated function that calls the -// appropriate interrupt handler for the given interrupt ID. -func callInterruptHandler(id int) diff --git a/tools/gen-device-avr/gen-device-avr.go b/tools/gen-device-avr/gen-device-avr.go index 9b9c627a..c553ec30 100755 --- a/tools/gen-device-avr/gen-device-avr.go +++ b/tools/gen-device-avr/gen-device-avr.go @@ -288,7 +288,6 @@ func writeGo(outdir string, device *Device) error { package {{.pkgName}} import ( - "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -306,11 +305,18 @@ const ({{range .interrupts}} IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. ) -// Map interrupt numbers to function names. -// These aren't real calls, they're removed by the compiler. -var ({{range .interrupts}} - _ = interrupt.Register(IRQ_{{.Name}}, "__vector_{{.Name}}"){{end}} -) +// Pseudo function call that is replaced by the compiler with the actual +// functions registered through interrupt.New. +//go:linkname callHandlers runtime/interrupt.callHandlers +func callHandlers(num int) + +{{- range .interrupts}} +//export __vector_{{.Name}} +//go:interrupt +func interrupt{{.Name}}() { + callHandlers(IRQ_{{.Name}}) +} +{{- end}} // Peripherals. var ({{range .peripherals}} diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 752286f4..e50ff299 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -831,6 +831,15 @@ func writeGo(outdir string, device *Device, interruptSystem string) error { } } + interruptHandlerMap := make(map[string]*Interrupt) + var interruptHandlers []*Interrupt + for _, intr := range device.Interrupts { + if _, ok := interruptHandlerMap[intr.HandlerName]; !ok { + interruptHandlerMap[intr.HandlerName] = intr + interruptHandlers = append(interruptHandlers, intr) + } + } + t := template.Must(template.New("go").Funcs(template.FuncMap{ "bytesNeeded": func(i, j uint64) uint64 { return j - i }, "isMultiline": isMultiline, @@ -846,7 +855,6 @@ func writeGo(outdir string, device *Device, interruptSystem string) error { package {{.pkgName}} import ( -{{if eq .interruptSystem "hardware"}}"runtime/interrupt"{{end}} "runtime/volatile" "unsafe" ) @@ -876,14 +884,29 @@ const ( IRQ_max = {{.interruptMax}} ) +// Pseudo function call that is replaced by the compiler with the actual +// functions registered through interrupt.New. +//go:linkname callHandlers runtime/interrupt.callHandlers +func callHandlers(num int) + {{- if eq .interruptSystem "hardware"}} -// Map interrupt numbers to function names. -// These aren't real calls, they're removed by the compiler. -var ( -{{- range .device.Interrupts}} - _ = interrupt.Register(IRQ_{{.Name}}, "{{.HandlerName}}") +{{- range .interruptHandlers}} +//export {{.HandlerName}} +func interrupt{{.Name}}() { + callHandlers(IRQ_{{.Name}}) +} {{- end}} -) +{{- end}} + +{{- if eq .interruptSystem "software"}} +func HandleInterrupt(num int) { + switch num { + {{- range .interruptHandlers}} + case IRQ_{{.Name}}: + callHandlers(IRQ_{{.Name}}) + {{- end}} + } +} {{- end}} // Peripherals. @@ -901,10 +924,11 @@ var ( `)) err = t.Execute(w, map[string]interface{}{ - "device": device, - "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), - "interruptMax": maxInterruptValue, - "interruptSystem": interruptSystem, + "device": device, + "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "interruptMax": maxInterruptValue, + "interruptSystem": interruptSystem, + "interruptHandlers": interruptHandlers, }) if err != nil { return err diff --git a/transform/interrupt.go b/transform/interrupt.go index 11fab18c..10548ef8 100644 --- a/transform/interrupt.go +++ b/transform/interrupt.go @@ -2,11 +2,8 @@ package transform import ( "fmt" - "sort" - "strconv" "strings" - "github.com/tinygo-org/tinygo/compileopts" "tinygo.org/x/go-llvm" ) @@ -15,70 +12,29 @@ import ( // // 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. +// * 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 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, config *compileopts.Config) []error { +// 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 - // 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() - } - - hasSoftwareVectoring := hasUses(mod.NamedFunction("runtime.callInterruptHandler")) - softwareVector := make(map[int64]llvm.Value) - ctx := mod.Context() i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) - nullptr := llvm.ConstNull(i8ptrType) 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 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[int][]llvm.Value{} + handleMap := map[int64][]llvm.Value{} handleType := mod.GetTypeByName("runtime/interrupt.handle") if !handleType.IsNil() { handlePtrType := llvm.PointerType(handleType, 0) @@ -89,7 +45,7 @@ func LowerInterrupts(mod llvm.Module, config *compileopts.Config) []error { // Get the interrupt number from the initializer initializer := global.Initializer() - num := int(llvm.ConstExtractValue(initializer, []uint32{1, 0}).SExtValue()) + num := llvm.ConstExtractValue(initializer, []uint32{2, 0}).SExtValue() pkg := packageFromInterruptHandle(global) handles, exists := handleMap[num] @@ -101,8 +57,8 @@ func LowerInterrupts(mod llvm.Module, config *compileopts.Config) []error { // 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])))) + fmt.Sprintf("handlers for interrupt %d in multiple packages: %s and %s", + num, pkg, packageFromInterruptHandle(handles[0])))) continue } @@ -110,146 +66,77 @@ func LowerInterrupts(mod llvm.Module, config *compileopts.Config) []error { } } - // 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) + // 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 + } - // Iterate over all handle objects, replacing their ptrtoint uses with a - // real interrupt ID and creating an interrupt handler for them. - for _, interrupt := range interrupts { - handles := handleMap[interrupt] + num := call.Operand(0) + if num.IsAConstantInt().IsNil() { + errs = append(errs, errorAt(call, "non-constant interrupt number?")) + call.InstructionParent().Parent().Dump() + continue + } - // 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}) - name := handlerNames[num.SExtValue()] - - isSoftwareVectored := false - if name == "" { - // No function name was defined for this interrupt number, which - // probably means one of two things: - // * runtime/interrupt.Register wasn't called to give the interrupt - // number a function name (such as on Cortex-M). - // * We're using software vectoring instead of hardware vectoring, - // which means the name of the handler doesn't matter (it will - // probably be inlined anyway). - if hasSoftwareVectoring { - isSoftwareVectored = true - if name == "" { - // Name doesn't matter, so pick something unique. - name = "runtime/interrupt.interruptHandler" + strconv.FormatInt(num.SExtValue(), 10) - } + 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, + llvm.Undef(i8ptrType), + }, "") + } + 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 { - errs = append(errs, errorAt(first, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) - 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(first, name+" redeclared with a different signature")) - continue - } else if !fn.IsDeclaration() { - errValue := name + " redeclared in this program" - fnPos := getPosition(fn) - if fnPos.IsValid() { - errValue += "\n\tprevious declaration at " + fnPos.String() - } - errs = append(errs, errorAt(first, errValue)) - continue - } - - // Create the wrapper function which is the actual interrupt handler - // that is inserted in the interrupt vector. - fn.SetUnnamedAddr(true) - AddStandardAttributes(fn, config) - fn.SetSection(".text." + name) - if isSoftwareVectored { - fn.SetLinkage(llvm.InternalLinkage) - softwareVector[num.SExtValue()] = fn - } - 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 - } - - // 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 + // 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 } - 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. - // In practice, the called function will often be inlined which avoids - // the extra indirection. - handlerFuncPtrType := llvm.PointerType(llvm.FunctionType(ctx.VoidType(), []llvm.Type{num.Type(), i8ptrType, i8ptrType}, false), handlerFuncPtr.Type().PointerAddressSpace()) - handlerFuncPtr = llvm.ConstBitCast(handlerFuncPtr, handlerFuncPtrType) - builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "") - - // 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. + // 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(num) + user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true)) } // The runtime/interrput.handle struct can finally be removed. @@ -257,58 +144,6 @@ func LowerInterrupts(mod llvm.Module, config *compileopts.Config) []error { // better to do it now to be sure. handler.EraseFromParentAsGlobal() } - - // The wrapper function has no return value - builder.CreateRetVoid() - } - // Create a dispatcher function that calls the appropriate interrupt handler - // for each interrupt ID. This is used in the case of software vectoring. - // The function looks like this: - // func callInterruptHandler(id int) { - // switch id { - // case IRQ_UART: - // interrupt.interruptHandler3() - // case IRQ_FOO: - // interrupt.interruptHandler7() - // default: - // // do nothing - // } - if hasSoftwareVectoring { - // Create a sorted list of interrupt vector IDs. - ids := make([]int64, 0, len(softwareVector)) - for id := range softwareVector { - ids = append(ids, id) - } - sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) - - // Start creating the function body with the big switch. - dispatcher := mod.NamedFunction("runtime.callInterruptHandler") - entryBlock := ctx.AddBasicBlock(dispatcher, "entry") - defaultBlock := ctx.AddBasicBlock(dispatcher, "default") - builder.SetInsertPointAtEnd(entryBlock) - interruptID := dispatcher.Param(0) - sw := builder.CreateSwitch(interruptID, defaultBlock, len(ids)) - - // Create a switch case for each interrupt ID that calls the appropriate - // handler. - for _, id := range ids { - block := ctx.AddBasicBlock(dispatcher, "interrupt"+strconv.FormatInt(id, 10)) - builder.SetInsertPointAtEnd(block) - builder.CreateCall(softwareVector[id], nil, "") - builder.CreateRetVoid() - sw.AddCase(llvm.ConstInt(interruptID.Type(), uint64(id), true), block) - } - - // Create a default case that just returns. - // Perhaps it is better to call some default interrupt handler here that - // logs an error? - builder.SetInsertPointAtEnd(defaultBlock) - builder.CreateRetVoid() - - // Make sure the dispatcher is optimized. - // Without this, it will probably not get inlined. - dispatcher.SetLinkage(llvm.InternalLinkage) - dispatcher.SetUnnamedAddr(true) } // Remove now-useless runtime/interrupt.use calls. These are used for some diff --git a/transform/interrupt_test.go b/transform/interrupt_test.go index 4cf7e6de..b80c9b8d 100644 --- a/transform/interrupt_test.go +++ b/transform/interrupt_test.go @@ -3,24 +3,19 @@ package transform_test import ( "testing" - "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/transform" "tinygo.org/x/go-llvm" ) func TestInterruptLowering(t *testing.T) { t.Parallel() - for _, subtest := range []string{"avr", "cortexm"} { - t.Run(subtest, func(t *testing.T) { - testTransform(t, "testdata/interrupt-"+subtest, func(mod llvm.Module) { - errs := transform.LowerInterrupts(mod, &compileopts.Config{Options: &compileopts.Options{Opt: "2"}}) - if len(errs) != 0 { - t.Fail() - for _, err := range errs { - t.Error(err) - } - } - }) - }) - } + testTransform(t, "testdata/interrupt", func(mod llvm.Module) { + errs := transform.LowerInterrupts(mod) + if len(errs) != 0 { + t.Fail() + for _, err := range errs { + t.Error(err) + } + } + }) } diff --git a/transform/optimizer.go b/transform/optimizer.go index ec48a119..bd6d2168 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -73,7 +73,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i return []error{err} } - errs := LowerInterrupts(mod, config) + errs := LowerInterrupts(mod) if len(errs) > 0 { return errs } @@ -105,7 +105,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i if config.FuncImplementation() == "switch" { LowerFuncValues(mod) } - errs := LowerInterrupts(mod, config) + errs := LowerInterrupts(mod) if len(errs) > 0 { return errs } diff --git a/transform/testdata/interrupt-avr.ll b/transform/testdata/interrupt-avr.ll deleted file mode 100644 index 80570c0b..00000000 --- a/transform/testdata/interrupt-avr.ll +++ /dev/null @@ -1,33 +0,0 @@ -target datalayout = "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8" -target triple = "avr-unknown-unknown" - -%"runtime/interrupt.handle" = type { %runtime.funcValue, %"runtime/interrupt.Interrupt" } %runtime.funcValue = type { i8*, i16 } -%runtime.typecodeID = type { %runtime.typecodeID*, i16 } -%runtime.funcValueWithSignature = type { i16, %runtime.typecodeID* } -%machine.UART = type { %machine.RingBuffer* } -%machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" } -%"runtime/volatile.Register8" = type { i8 } -%"runtime/interrupt.Interrupt" = type { i32 } - -@"reflect/types.type:func:{named:runtime/interrupt.Interrupt}{}" = external constant %runtime.typecodeID -@"(machine.UART).Configure$1$withSignature" = internal constant %runtime.funcValueWithSignature { i16 ptrtoint (void (i32, i8*, i8*) addrspace(1)* @"(machine.UART).Configure$1" to i16), %runtime.typecodeID* @"reflect/types.type:func:{named:runtime/interrupt.Interrupt}{}" } -@"runtime/interrupt.$interrupt18" = private unnamed_addr constant %"runtime/interrupt.handle" { %runtime.funcValue { i8* undef, i16 ptrtoint (%runtime.funcValueWithSignature* @"(machine.UART).Configure$1$withSignature" to i16) }, %"runtime/interrupt.Interrupt" { i32 18 } } -@machine.UART0 = internal global %machine.UART zeroinitializer -@"device/avr.init$string.18" = internal unnamed_addr constant [17 x i8] c"__vector_USART_RX" - -declare void @"(machine.UART).Configure$1"(i32, i8*, i8*) unnamed_addr addrspace(1) - -declare i32 @"runtime/interrupt.Register"(i32, i8*, i16, i8*, i8*) addrspace(1) - -declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") addrspace(1) - -define void @"(machine.UART).Configure"(%machine.RingBuffer*, i32, i8, i8, i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) { - call addrspace(1) void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt" { i32 ptrtoint (%"runtime/interrupt.handle"* @"runtime/interrupt.$interrupt18" to i32) }) - ret void -} - -define void @"device/avr.init"(i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) { -entry: - %0 = call addrspace(1) i32 @"runtime/interrupt.Register"(i32 18, i8* getelementptr inbounds ([17 x i8], [17 x i8]* @"device/avr.init$string.18", i32 0, i32 0), i16 17, i8* undef, i8* undef) - ret void -} diff --git a/transform/testdata/interrupt-avr.out.ll b/transform/testdata/interrupt-avr.out.ll deleted file mode 100644 index 3f3c881f..00000000 --- a/transform/testdata/interrupt-avr.out.ll +++ /dev/null @@ -1,35 +0,0 @@ -target datalayout = "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8" -target triple = "avr-unknown-unknown" - -%runtime.typecodeID = type { %runtime.typecodeID*, i16 } -%runtime.funcValueWithSignature = type { i16, %runtime.typecodeID* } -%machine.UART = type { %machine.RingBuffer* } -%machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" } -%"runtime/volatile.Register8" = type { i8 } -%"runtime/interrupt.Interrupt" = type { i32 } - -@"reflect/types.type:func:{named:runtime/interrupt.Interrupt}{}" = external constant %runtime.typecodeID -@"(machine.UART).Configure$1$withSignature" = internal constant %runtime.funcValueWithSignature { i16 ptrtoint (void (i32, i8*, i8*) addrspace(1)* @"(machine.UART).Configure$1" to i16), %runtime.typecodeID* @"reflect/types.type:func:{named:runtime/interrupt.Interrupt}{}" } -@machine.UART0 = internal global %machine.UART zeroinitializer -@"device/avr.init$string.18" = internal unnamed_addr constant [17 x i8] c"__vector_USART_RX" - -declare void @"(machine.UART).Configure$1"(i32, i8*, i8*) unnamed_addr addrspace(1) - -declare i32 @"runtime/interrupt.Register"(i32, i8*, i16, i8*, i8*) addrspace(1) - -declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") addrspace(1) - -define void @"(machine.UART).Configure"(%machine.RingBuffer* %0, i32 %1, i8 %2, i8 %3, i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) { - ret void -} - -define void @"device/avr.init"(i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) { -entry: - ret void -} - -define avr_signalcc void @__vector_USART_RX() unnamed_addr addrspace(1) section ".text.__vector_USART_RX" { -entry: - call addrspace(1) void @"(machine.UART).Configure$1"(i32 18, i8* undef, i8* null) - ret void -} diff --git a/transform/testdata/interrupt-cortexm.ll b/transform/testdata/interrupt.ll similarity index 56% rename from transform/testdata/interrupt-cortexm.ll rename to transform/testdata/interrupt.ll index 2c156b5b..e46fbe6f 100644 --- a/transform/testdata/interrupt-cortexm.ll +++ b/transform/testdata/interrupt.ll @@ -4,27 +4,55 @@ target triple = "armv7em-none-eabi" %machine.UART = type { %machine.RingBuffer* } %machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" } %"runtime/volatile.Register8" = type { i8 } -%"runtime/interrupt.handle" = type { { i8*, void (i32, i8*, i8*)* }, %"runtime/interrupt.Interrupt" } +%"runtime/interrupt.handle" = type { i8*, i32, %"runtime/interrupt.Interrupt" } %"runtime/interrupt.Interrupt" = type { i32 } -@"runtime/interrupt.$interrupt2" = private unnamed_addr constant %"runtime/interrupt.handle" { { i8*, void (i32, i8*, i8*)* } { i8* bitcast (%machine.UART* @machine.UART0 to i8*), void (i32, i8*, i8*)* @"(*machine.UART).handleInterrupt$bound" }, %"runtime/interrupt.Interrupt" { i32 2 } } +@"runtime/interrupt.$interrupt2" = private unnamed_addr constant %"runtime/interrupt.handle" { i8* bitcast (%machine.UART* @machine.UART0 to i8*), i32 ptrtoint (void (i32, i8*, i8*)* @"(*machine.UART).handleInterrupt$bound" to i32), %"runtime/interrupt.Interrupt" { i32 2 } } @machine.UART0 = internal global %machine.UART { %machine.RingBuffer* @"machine$alloc.335" } @"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer -@"device/nrf.init$string.2" = internal unnamed_addr constant [23 x i8] c"UARTE0_UART0_IRQHandler" -@"device/nrf.init$string.3" = internal unnamed_addr constant [44 x i8] c"SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler" -declare i32 @"runtime/interrupt.Register"(i32, i8*, i32, i8*, i8*) local_unnamed_addr +declare void @"runtime/interrupt.callHandlers"(i32, i8*, i8*) local_unnamed_addr declare void @"device/arm.EnableIRQ"(i32, i8* nocapture readnone, i8* nocapture readnone) declare void @"device/arm.SetPriority"(i32, i32, i8* nocapture readnone, i8* nocapture readnone) +declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") + define void @runtime.initAll(i8* nocapture readnone, i8* nocapture readnone) unnamed_addr { entry: - %2 = call i32 @"runtime/interrupt.Register"(i32 2, i8* getelementptr inbounds ([23 x i8], [23 x i8]* @"device/nrf.init$string.2", i32 0, i32 0), i32 23, i8* undef, i8* undef) - %3 = call i32 @"runtime/interrupt.Register"(i32 3, i8* getelementptr inbounds ([44 x i8], [44 x i8]* @"device/nrf.init$string.3", i32 0, i32 0), i32 44, i8* undef, i8* undef) call void @"device/arm.SetPriority"(i32 ptrtoint (%"runtime/interrupt.handle"* @"runtime/interrupt.$interrupt2" to i32), i32 192, i8* undef, i8* undef) call void @"device/arm.EnableIRQ"(i32 ptrtoint (%"runtime/interrupt.handle"* @"runtime/interrupt.$interrupt2" to i32), i8* undef, i8* undef) + call void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt" { i32 ptrtoint (%"runtime/interrupt.handle"* @"runtime/interrupt.$interrupt2" to i32) }) + ret void +} + +define void @UARTE0_UART0_IRQHandler() { + call void @"runtime/interrupt.callHandlers"(i32 2, i8* undef, i8* undef) + ret void +} + +define void @NFCT_IRQHandler() { + call void @"runtime/interrupt.callHandlers"(i32 5, i8* undef, i8* undef) + ret void +} + +define internal void @interruptSWVector(i32 %num) { +entry: + switch i32 %num, label %switch.done [ + i32 2, label %switch.body2 + i32 5, label %switch.body5 + ] + +switch.body2: + call void @"runtime/interrupt.callHandlers"(i32 2, i8* undef, i8* undef) + ret void + +switch.body5: + call void @"runtime/interrupt.callHandlers"(i32 5, i8* undef, i8* undef) + ret void + +switch.done: ret void } diff --git a/transform/testdata/interrupt-cortexm.out.ll b/transform/testdata/interrupt.out.ll similarity index 64% rename from transform/testdata/interrupt-cortexm.out.ll rename to transform/testdata/interrupt.out.ll index 2e35f263..78f52ff6 100644 --- a/transform/testdata/interrupt-cortexm.out.ll +++ b/transform/testdata/interrupt.out.ll @@ -4,18 +4,19 @@ target triple = "armv7em-none-eabi" %machine.UART = type { %machine.RingBuffer* } %machine.RingBuffer = type { [128 x %"runtime/volatile.Register8"], %"runtime/volatile.Register8", %"runtime/volatile.Register8" } %"runtime/volatile.Register8" = type { i8 } +%"runtime/interrupt.Interrupt" = type { i32 } @machine.UART0 = internal global %machine.UART { %machine.RingBuffer* @"machine$alloc.335" } @"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer -@"device/nrf.init$string.2" = internal unnamed_addr constant [23 x i8] c"UARTE0_UART0_IRQHandler" -@"device/nrf.init$string.3" = internal unnamed_addr constant [44 x i8] c"SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler" -declare i32 @"runtime/interrupt.Register"(i32, i8*, i32, i8*, i8*) local_unnamed_addr +declare void @"runtime/interrupt.callHandlers"(i32, i8*, i8*) local_unnamed_addr declare void @"device/arm.EnableIRQ"(i32, i8* nocapture readnone, i8* nocapture readnone) declare void @"device/arm.SetPriority"(i32, i32, i8* nocapture readnone, i8* nocapture readnone) +declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") + define void @runtime.initAll(i8* nocapture readnone %0, i8* nocapture readnone %1) unnamed_addr { entry: call void @"device/arm.SetPriority"(i32 2, i32 192, i8* undef, i8* undef) @@ -23,6 +24,29 @@ entry: ret void } +define void @UARTE0_UART0_IRQHandler() { + call void @"(*machine.UART).handleInterrupt$bound"(i32 2, i8* bitcast (%machine.UART* @machine.UART0 to i8*), i8* undef) + ret void +} + +define internal void @interruptSWVector(i32 %num) { +entry: + switch i32 %num, label %switch.done [ + i32 2, label %switch.body2 + i32 5, label %switch.body5 + ] + +switch.body2: ; preds = %entry + call void @"(*machine.UART).handleInterrupt$bound"(i32 2, i8* bitcast (%machine.UART* @machine.UART0 to i8*), i8* undef) + ret void + +switch.body5: ; preds = %entry + unreachable + +switch.done: ; preds = %entry + ret void +} + define internal void @"(*machine.UART).handleInterrupt$bound"(i32 %0, i8* nocapture %context, i8* nocapture readnone %parentHandle) { entry: %unpack.ptr = bitcast i8* %context to %machine.UART* @@ -31,9 +55,3 @@ entry: } declare void @"(*machine.UART).handleInterrupt"(%machine.UART* nocapture, i32, i8* nocapture readnone, i8* nocapture readnone) - -define void @UARTE0_UART0_IRQHandler() unnamed_addr section ".text.UARTE0_UART0_IRQHandler" { -entry: - call void @"(*machine.UART).handleInterrupt$bound"(i32 2, i8* bitcast (%machine.UART* @machine.UART0 to i8*), i8* null) - ret void -}