diff options
author | Ayke van Laethem <[email protected]> | 2021-11-03 01:24:25 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-11-06 09:40:15 +0100 |
commit | edcece33caeb0453c105efad799a94239c128dff (patch) | |
tree | 5bc4d51da363f5b8a5ebb84bed2879eb2534bddf | |
parent | 30bbdd5aeb6a78388db533da72ebace10545935e (diff) | |
download | tinygo-edcece33caeb0453c105efad799a94239c128dff.tar.gz tinygo-edcece33caeb0453c105efad799a94239c128dff.zip |
transform: refactor interrupt lowering
Instead of doing everything in the interrupt lowering pass, generate
some more code in gen-device to declare interrupt handler functions and
do some work in the compiler so that interrupt lowering becomes a lot
simpler.
This has several benefits:
- Overall code is smaller, in particular the interrupt lowering pass.
- The code should be a bit less "magical" and instead a bit easier to
read. In particular, instead of having a magic
runtime.callInterruptHandler (that is fully written by the interrupt
lowering pass), the runtime calls a generated function like
device/sifive.InterruptHandler where this switch already exists in
code.
- Debug information is improved. This can be helpful during actual
debugging but is also useful for other uses of DWARF debug
information.
For an example on debug information improvement, this is what a
backtrace might look like before this commit:
Breakpoint 1, 0x00000b46 in UART0_IRQHandler ()
(gdb) bt
#0 0x00000b46 in UART0_IRQHandler ()
#1 <signal handler called>
[..etc]
Notice that the debugger doesn't see the source code location where it
has stopped.
After this commit, breaking at the same line might look like this:
Breakpoint 1, (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
200 uart.Receive(byte(nrf.UART0.RXD.Get()))
(gdb) bt
#0 (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
#1 UART0_IRQHandler () at /home/ayke/src/github.com/tinygo-org/tinygo/src/device/nrf/nrf51.go:176
#2 <signal handler called>
[..etc]
By now, the debugger sees an actual source location for UART0_IRQHandler
(in the generated file) and an inlined function.
-rw-r--r-- | compiler/compiler.go | 11 | ||||
-rw-r--r-- | compiler/func.go | 17 | ||||
-rw-r--r-- | compiler/interrupt.go | 7 | ||||
-rw-r--r-- | compiler/symbol.go | 5 | ||||
-rw-r--r-- | src/machine/machine_gameboyadvance.go | 29 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt.go | 5 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt_esp32c3.go | 7 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt_gameboyadvance.go | 59 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt_hwvector.go | 8 | ||||
-rw-r--r-- | src/runtime/runtime_fe310.go | 6 | ||||
-rw-r--r-- | src/runtime/runtime_k210.go | 6 | ||||
-rwxr-xr-x | tools/gen-device-avr/gen-device-avr.go | 18 | ||||
-rwxr-xr-x | tools/gen-device-svd/gen-device-svd.go | 46 | ||||
-rw-r--r-- | transform/interrupt.go | 305 | ||||
-rw-r--r-- | transform/interrupt_test.go | 23 | ||||
-rw-r--r-- | transform/optimizer.go | 4 | ||||
-rw-r--r-- | transform/testdata/interrupt-avr.ll | 33 | ||||
-rw-r--r-- | transform/testdata/interrupt-avr.out.ll | 35 | ||||
-rw-r--r-- | transform/testdata/interrupt.ll (renamed from transform/testdata/interrupt-cortexm.ll) | 42 | ||||
-rw-r--r-- | transform/testdata/interrupt.out.ll (renamed from transform/testdata/interrupt-cortexm.out.ll) | 36 |
20 files changed, 303 insertions, 399 deletions
diff --git a/compiler/compiler.go b/compiler/compiler.go index 5caf932d9..fabfa54b3 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 a82d1f1b8..fa940570d 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 2437ad452..bcd407cd1 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 469066af0..902277a58 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 100c2bce4..9387749e4 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 498389b6a..a8cf6f4e9 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 20b055f8d..a37814888 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 658a2b9c7..b0055070b 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 f210da589..000000000 --- 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 5cfa7a698..33d8e4834 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 c94153ba9..dc8ee0445 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 9b9c627ae..c553ec30d 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 752286f41..e50ff299f 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 11fab18ce..10548ef84 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) - - // 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] - - // 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) - } - } 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)) + // 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 } - // 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 + num := call.Operand(0) + if num.IsAConstantInt().IsNil() { + errs = append(errs, errorAt(call, "non-constant interrupt number?")) + call.InstructionParent().Parent().Dump() + continue } - // 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 + 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), + }, "") } - 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 + 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 } - 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 4cf7e6de1..b80c9b8d8 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 ec48a1192..bd6d21687 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 80570c0bb..000000000 --- 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 } } [email protected] = 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 3f3c881f1..000000000 --- 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}{}" } [email protected] = 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 index 2c156b5b6..e46fbe6fa 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 index 2e35f2633..78f52ff66 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 -} |