diff options
author | Ayke van Laethem <[email protected]> | 2019-12-15 14:16:22 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-01-20 21:19:12 +0100 |
commit | a5ed993f8dda8cfc71de549e60323236feb85c05 (patch) | |
tree | abd0d3577cf13a6e3a2d61379b689e744bda7ef6 | |
parent | 3729fcfa9edba2230aba65bcd607621d3285704f (diff) | |
download | tinygo-a5ed993f8dda8cfc71de549e60323236feb85c05.tar.gz tinygo-a5ed993f8dda8cfc71de549e60323236feb85c05.zip |
all: add compiler support for interrupts
This commit lets the compiler know about interrupts and allows
optimizations to be performed based on that: interrupts are eliminated
when they appear to be unused in a program. This is done with a new
pseudo-call (runtime/interrupt.New) that is treated specially by the
compiler.
36 files changed, 766 insertions, 199 deletions
diff --git a/compiler/compiler.go b/compiler/compiler.go index 1217d3d33..b2f803393 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error { path = path[len(tinygoPath+"/src/"):] } switch path { - case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite": + case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite": return path default: if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { @@ -828,9 +828,6 @@ func (c *Compiler) parseFunc(frame *Frame) { frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) frame.fn.LLVMFn.SetUnnamedAddr(true) } - if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple(), "avr") { - frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL - } // Some functions have a pragma controlling the inlining level. switch frame.fn.Inline() { @@ -1314,6 +1311,8 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e return c.emitVolatileLoad(frame, instr) case strings.HasPrefix(name, "runtime/volatile.Store"): return c.emitVolatileStore(frame, instr) + case name == "runtime/interrupt.New": + return c.emitInterruptGlobal(frame, instr) } targetFunc := c.ir.GetFunction(fn) diff --git a/compiler/interrupt.go b/compiler/interrupt.go new file mode 100644 index 000000000..67cb8eca6 --- /dev/null +++ b/compiler/interrupt.go @@ -0,0 +1,92 @@ +package compiler + +import ( + "strconv" + "strings" + + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that +// will be lowered to a real interrupt during interrupt lowering. +// +// This two-stage approach allows unused interrupts to be optimized away if +// necessary. +func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) { + // Get the interrupt number, which must be a compile-time constant. + id, ok := instr.Args[0].(*ssa.Const) + if !ok { + return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant") + } + + // Get the func value, which also must be a compile time constant. + // Note that bound functions are allowed if the function has a pointer + // receiver and is a global. This is rather strict but still allows for + // idiomatic Go code. + funcValue := c.getValue(frame, instr.Args[1]) + if funcValue.IsAConstant().IsNil() { + // Try to determine the cause of the non-constantness for a nice error + // message. + switch instr.Args[1].(type) { + case *ssa.MakeClosure: + // This may also be a bound method. + return llvm.Value{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New") + } + // Fall back to a generic error. + return llvm.Value{}, c.makeError(instr.Pos(), "interrupt function must be constant") + } + + // Create a new global of type runtime/interrupt.handle. Globals of this + // type are lowered in the interrupt lowering pass. + globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() + globalLLVMType := c.getLLVMType(globalType) + globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) + if global := c.mod.NamedGlobal(globalName); !global.IsNil() { + return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program") + } + global := llvm.AddGlobal(c.mod, globalLLVMType, globalName) + global.SetLinkage(llvm.PrivateLinkage) + global.SetGlobalConstant(true) + global.SetUnnamedAddr(true) + initializer := llvm.ConstNull(globalLLVMType) + initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0}) + initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0}) + global.SetInitializer(initializer) + + // Add debug info to the interrupt global. + if c.Debug() { + pos := c.ir.Program.Fset.Position(instr.Pos()) + diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ + Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), + LinkageName: globalName, + File: c.getDIFile(pos.Filename), + Line: pos.Line, + Type: c.getDIType(globalType), + Expr: c.dibuilder.CreateExpression(nil), + LocalToUnit: false, + }) + global.AddMetadata(0, diglobal) + } + + // Create the runtime/interrupt.Interrupt type. It is a struct with a single + // member of type int. + num := llvm.ConstPtrToInt(global, c.intType) + interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) + + // Add dummy "use" call for AVR, because interrupts may be used even though + // they are never referenced again. This is unlike Cortex-M or the RISC-V + // PLIC where each interrupt must be enabled using the interrupt number, and + // thus keeps the Interrupt object alive. + // This call is removed during interrupt lowering. + if strings.HasPrefix(c.Triple(), "avr") { + useFn := c.mod.NamedFunction("runtime/interrupt.use") + if useFn.IsNil() { + useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) + useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType) + } + c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "") + } + + return interrupt, nil +} diff --git a/compiler/optimizer.go b/compiler/optimizer.go index 17b782e83..60395edcb 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -57,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er transform.OptimizeStringToBytes(c.mod) transform.OptimizeAllocs(c.mod) transform.LowerInterfaces(c.mod) + + errs := transform.LowerInterrupts(c.mod) + if len(errs) > 0 { + return errs + } + if c.funcImplementation() == funcValueSwitch { transform.LowerFuncValues(c.mod) } @@ -100,6 +106,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er if err != nil { return []error{err} } + errs := transform.LowerInterrupts(c.mod) + if len(errs) > 0 { + return errs + } } if c.VerifyIR() { if errs := c.checkModule(); errs != nil { @@ -27,14 +27,13 @@ type Program struct { // Function or method. type Function struct { *ssa.Function - LLVMFn llvm.Value - module string // go:wasm-module - linkName string // go:linkname, go:export, go:interrupt - exported bool // go:export - nobounds bool // go:nobounds - flag bool // used by dead code elimination - interrupt bool // go:interrupt - inline InlineType // go:inline + LLVMFn llvm.Value + module string // go:wasm-module + linkName string // go:linkname, go:export + exported bool // go:export + nobounds bool // go:nobounds + flag bool // used by dead code elimination + inline InlineType // go:inline } // Interface type that is at some point used in a type assert (to check whether @@ -243,18 +242,6 @@ func (f *Function) parsePragmas() { f.inline = InlineHint case "//go:noinline": f.inline = InlineNone - case "//go:interrupt": - if len(parts) != 2 { - continue - } - name := parts[1] - if strings.HasSuffix(name, "_vect") { - // AVR vector naming - name = "__vector_" + name[:len(name)-5] - } - f.linkName = name - f.exported = true - f.interrupt = true case "//go:linkname": if len(parts) != 3 || parts[1] != f.Name() { continue @@ -288,14 +275,6 @@ func (f *Function) IsExported() bool { return f.exported || f.CName() != "" } -// Return true for functions annotated with //go:interrupt. The function name is -// already customized in LinkName() to hook up in the interrupt vector. -// -// On some platforms (like AVR), interrupts need a special compiler flag. -func (f *Function) IsInterrupt() bool { - return f.interrupt -} - // Return the inline directive of this function. func (f *Function) Inline() InlineType { return f.inline diff --git a/src/machine/board_arduino_nano33_baremetal.go b/src/machine/board_arduino_nano33_baremetal.go index 829989a16..cb024cd42 100644 --- a/src/machine/board_arduino_nano33_baremetal.go +++ b/src/machine/board_arduino_nano33_baremetal.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. var ( @@ -13,11 +16,6 @@ var ( } ) -//go:export SERCOM3_IRQHandler -func handleUART1() { - defaultUART1Handler() -} - // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins. var ( UART2 = UART{ @@ -27,11 +25,9 @@ var ( } ) -//go:export SERCOM5_IRQHandler -func handleUART2() { - // should reset IRQ - UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF))) - UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM5, UART2.handleInterrupt) } // I2C on the Arduino Nano 33. diff --git a/src/machine/board_bluepill.go b/src/machine/board_bluepill.go index 39c7d421f..e6ba49b40 100644 --- a/src/machine/board_bluepill.go +++ b/src/machine/board_bluepill.go @@ -2,7 +2,10 @@ package machine -import "device/stm32" +import ( + "device/stm32" + "runtime/interrupt" +) // https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif const ( @@ -61,14 +64,12 @@ var ( UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, - IRQVal: stm32.IRQ_USART1, } UART1 = &UART0 ) -//go:export USART1_IRQHandler -func handleUART1() { - UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF))) +func init() { + UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt) } // SPI pins diff --git a/src/machine/board_circuitplay_express_baremetal.go b/src/machine/board_circuitplay_express_baremetal.go index 446b1790f..271082e95 100644 --- a/src/machine/board_circuitplay_express_baremetal.go +++ b/src/machine/board_circuitplay_express_baremetal.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // UART1 on the Circuit Playground Express. var ( @@ -13,9 +16,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM4, UART1.handleInterrupt) } // I2C on the Circuit Playground Express. diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index 97af7f901..f5fa76a18 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -60,9 +63,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt) } // I2C pins diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index 3238d2037..ed4f9fa7b 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -4,6 +4,7 @@ package machine import ( "device/sam" + "runtime/interrupt" ) // used to reset into bootloader @@ -62,9 +63,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt) } // I2C pins diff --git a/src/machine/board_nucleof103rb.go b/src/machine/board_nucleof103rb.go index 1186ce2a6..5e2850f7b 100644 --- a/src/machine/board_nucleof103rb.go +++ b/src/machine/board_nucleof103rb.go @@ -2,7 +2,10 @@ package machine -import "device/stm32" +import ( + "device/stm32" + "runtime/interrupt" +) const ( PA0 = portA + 0 @@ -100,14 +103,12 @@ var ( UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, - IRQVal: stm32.IRQ_USART2, } UART2 = &UART0 ) -//go:export USART2_IRQHandler -func handleUART2() { - UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF))) +func init() { + UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt) } // SPI pins diff --git a/src/machine/board_trinket.go b/src/machine/board_trinket.go index f2c62160e..b9f2d6864 100644 --- a/src/machine/board_trinket.go +++ b/src/machine/board_trinket.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -51,9 +54,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM0, UART1.handleInterrupt) } // SPI pins diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 83c501e52..16395fe42 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -4,6 +4,7 @@ package machine import ( "device/avr" + "runtime/interrupt" "runtime/volatile" ) @@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) { config.BaudRate = 9600 } + // Register the UART interrupt. + interrupt.New(avr.IRQ_USART_RX, func(intr interrupt.Interrupt) { + // Read register to clear it. + data := avr.UDR0.Get() + + // Ensure no error. + if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { + // Put data from UDR register into buffer. + UART0.Receive(byte(data)) + } + }) + // Set baud rate based on prescale formula from // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html // ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) @@ -254,15 +267,3 @@ func (uart UART) WriteByte(c byte) error { avr.UDR0.Set(c) // send char return nil } - -//go:interrupt USART_RX_vect -func handleUSART_RX() { - // Read register to clear it. - data := avr.UDR0.Get() - - // Ensure no error. - if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { - // Put data from UDR register into buffer. - UART0.Receive(byte(data)) - } -} diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index c1bb9e632..856ef136b 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -11,6 +11,7 @@ import ( "device/arm" "device/sam" "errors" + "runtime/interrupt" "unsafe" ) @@ -292,9 +293,10 @@ func waitADCSync() { // UART on the SAMD21. type UART struct { - Buffer *RingBuffer - Bus *sam.SERCOM_USART_Type - SERCOM uint8 + Buffer *RingBuffer + Bus *sam.SERCOM_USART_Type + SERCOM uint8 + Interrupt interrupt.Interrupt } var ( @@ -401,10 +403,7 @@ func (uart UART) Configure(config UARTConfig) error { uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC) // Enable RX IRQ. - // IRQ lines are in the same order as SERCOM instance numbers on SAMD21 - // chips, so the IRQ number can be trivially determined from the SERCOM - // number. - arm.EnableIRQ(sam.IRQ_SERCOM0 + uint32(uart.SERCOM)) + uart.Interrupt.Enable() return nil } @@ -432,11 +431,12 @@ func (uart UART) WriteByte(c byte) error { return nil } -// defaultUART1Handler handles the UART1 IRQ. -func defaultUART1Handler() { +// handleInterrupt should be called from the appropriate interrupt handler for +// this UART instance. +func (uart *UART) handleInterrupt(interrupt.Interrupt) { // should reset IRQ - UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF))) - UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) + uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) + uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) } // I2C on the SAMD21. @@ -1368,7 +1368,8 @@ func (usbcdc USBCDC) Configure(config UARTConfig) { sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) // enable IRQ - arm.EnableIRQ(sam.IRQ_USB) + intr := interrupt.New(sam.IRQ_USB, handleUSB) + intr.Enable() } func handlePadCalibration() { @@ -1414,8 +1415,7 @@ func handlePadCalibration() { sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } -//go:export USB_IRQHandler -func handleUSB() { +func handleUSB(intr interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index e23ebf170..e35cb41bc 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -11,6 +11,7 @@ import ( "device/arm" "device/sam" "errors" + "runtime/interrupt" "unsafe" ) @@ -1547,11 +1548,11 @@ func (usbcdc USBCDC) Configure(config UARTConfig) { // enable USB sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) - // enable IRQ - arm.EnableIRQ(sam.IRQ_USB_OTHER) - arm.EnableIRQ(sam.IRQ_USB_SOF_HSOF) - arm.EnableIRQ(sam.IRQ_USB_TRCPT0) - arm.EnableIRQ(sam.IRQ_USB_TRCPT1) + // enable IRQ at highest priority + interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable() } func handlePadCalibration() { @@ -1597,27 +1598,7 @@ func handlePadCalibration() { sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } -//go:export USB_OTHER_IRQHandler -func handleUSBOther() { - handleUSBIRQ() -} - -//go:export USB_SOF_HSOF_IRQHandler -func handleUSBSOFHSOF() { - handleUSBIRQ() -} - -//go:export USB_TRCPT0_IRQHandler -func handleUSBTRCPT0() { - handleUSBIRQ() -} - -//go:export USB_TRCPT1_IRQHandler -func handleUSBTRCPT1() { - handleUSBIRQ() -} - -func handleUSBIRQ() { +func handleUSBIRQ(interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 7e12c8a84..0946356b5 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,9 +3,9 @@ package machine import ( - "device/arm" "device/nrf" "errors" + "runtime/interrupt" ) var ( @@ -88,8 +88,9 @@ func (uart UART) Configure(config UARTConfig) { nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk) // Enable RX IRQ. - arm.SetPriority(nrf.IRQ_UART0, 0xc0) // low priority - arm.EnableIRQ(nrf.IRQ_UART0) + intr := interrupt.New(nrf.IRQ_UART0, UART0.handleInterrupt) + intr.SetPriority(0xc0) // low priority + intr.Enable() } // SetBaudRate sets the communication speed for the UART. @@ -116,7 +117,7 @@ func (uart UART) WriteByte(c byte) error { return nil } -func (uart UART) handleInterrupt() { +func (uart *UART) handleInterrupt(interrupt.Interrupt) { if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { uart.Receive(byte(nrf.UART0.RXD.Get())) nrf.UART0.EVENTS_RXDRDY.Set(0x0) diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index 6425b2ae2..2c9af2405 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSELRXD.Set(uint32(rx)) } -//go:export UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go index 392e237c3..b2181770a 100644 --- a/src/machine/machine_nrf52.go +++ b/src/machine/machine_nrf52.go @@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSELRXD.Set(uint32(rx)) } -//go:export UARTE0_UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go index a9dc60cc3..2a76ac27b 100644 --- a/src/machine/machine_nrf52840.go +++ b/src/machine/machine_nrf52840.go @@ -77,11 +77,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSEL.RXD.Set(uint32(rx)) } -//go:export UARTE0_UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SDA.Set(uint32(sda)) diff --git a/src/machine/machine_stm32f103xx.go b/src/machine/machine_stm32f103xx.go index 5e77712f0..6aafb264f 100644 --- a/src/machine/machine_stm32f103xx.go +++ b/src/machine/machine_stm32f103xx.go @@ -5,9 +5,9 @@ package machine // Peripheral abstraction layer for the stm32. import ( - "device/arm" "device/stm32" "errors" + "runtime/interrupt" ) func CPUFrequency() uint32 { @@ -111,9 +111,9 @@ func (p Pin) Get() bool { // UART type UART struct { - Buffer *RingBuffer - Bus *stm32.USART_Type - IRQVal uint32 + Buffer *RingBuffer + Bus *stm32.USART_Type + Interrupt interrupt.Interrupt } // Configure the UART. @@ -155,8 +155,8 @@ func (uart UART) Configure(config UARTConfig) { uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) // Enable RX IRQ - arm.SetPriority(uart.IRQVal, 0xc0) - arm.EnableIRQ(uart.IRQVal) + uart.Interrupt.SetPriority(0xc0) + uart.Interrupt.Enable() } // SetBaudRate sets the communication speed for the UART. @@ -182,6 +182,12 @@ func (uart UART) WriteByte(c byte) error { return nil } +// handleInterrupt should be called from the appropriate interrupt handler for +// this UART instance. +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + uart.Receive(byte((uart.Bus.DR.Get() & 0xFF))) +} + // SPI on the STM32. type SPI struct { Bus *stm32.SPI_Type diff --git a/src/machine/machine_stm32f407.go b/src/machine/machine_stm32f407.go index 54c0190d8..c75e38771 100644 --- a/src/machine/machine_stm32f407.go +++ b/src/machine/machine_stm32f407.go @@ -5,8 +5,8 @@ package machine // Peripheral abstraction layer for the stm32. import ( - "device/arm" "device/stm32" + "runtime/interrupt" ) func CPUFrequency() uint32 { @@ -203,8 +203,11 @@ func (uart UART) Configure(config UARTConfig) { stm32.USART2.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) // Enable RX IRQ. - arm.SetPriority(stm32.IRQ_USART2, 0xc0) - arm.EnableIRQ(stm32.IRQ_USART2) + intr := interrupt.New(stm32.IRQ_USART2, func(interrupt.Interrupt) { + UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF))) + }) + intr.SetPriority(0xc0) + intr.Enable() } // WriteByte writes a byte of data to the UART. @@ -215,8 +218,3 @@ func (uart UART) WriteByte(c byte) error { } return nil } - -//go:export USART2_IRQHandler -func handleUSART2() { - UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF))) -} diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go new file mode 100644 index 000000000..c37b2c11e --- /dev/null +++ b/src/runtime/interrupt/interrupt.go @@ -0,0 +1,38 @@ +// Package interrupt provides access to hardware interrupts. It provides a way +// to define interrupts and to enable/disable them. +package interrupt + +// Interrupt provides direct access to hardware interrupts. You can configure +// this interrupt through this interface. +// +// Do not use the zero value of an Interrupt object. Instead, call New to obtain +// an interrupt handle. +type Interrupt struct { + // Make this number unexported so it cannot be set directly. This provides + // some encapsulation. + num int +} + +// New is a compiler intrinsic that creates a new Interrupt object. You may call +// it only once, and must pass constant parameters to it. That means that the +// interrupt ID must be a Go constant and that the handler must be a simple +// function: closures are not supported. +func New(id int, handler func(Interrupt)) 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 + +// handle is used internally, between IR generation and interrupt lowering. The +// frontend will create runtime/interrupt.handle objects, cast them to an int, +// and use that in an Interrupt object. That way the compiler will be able to +// optimize away all interrupt handles that are never used in a program. +// This system only works when interrupts need to be enabled before use and this +// is done only through calling Enable() on this object. If interrups cannot +// 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) + Interrupt +} diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go new file mode 100644 index 000000000..5354be9e0 --- /dev/null +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -0,0 +1,23 @@ +// +build cortexm + +package interrupt + +import ( + "device/arm" +) + +// Enable enables this interrupt. Right after calling this function, the +// interrupt may be invoked if it was already pending. +func (irq Interrupt) Enable() { + arm.EnableIRQ(uint32(irq.num)) +} + +// SetPriority sets the interrupt priority for this interrupt. A lower number +// means a higher priority. Additionally, most hardware doesn't implement all +// priority bits (only the uppoer bits). +// +// Examples: 0xff (lowest priority), 0xc0 (low priority), 0x00 (highest possible +// priority). +func (irq Interrupt) SetPriority(priority uint8) { + arm.SetPriority(uint32(irq.num), uint32(priority)) +} diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index dbbf8f142..440cd148e 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/sam" "machine" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -214,8 +215,14 @@ func initRTC() { sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE) waitForSync() - arm.SetPriority(sam.IRQ_RTC, 0xc0) - arm.EnableIRQ(sam.IRQ_RTC) + intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) { + // disable IRQ for CMP0 compare + sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0) + + timerWakeup.Set(1) + }) + intr.SetPriority(0xc0) + intr.Enable() } func waitForSync() { @@ -286,14 +293,6 @@ func timerSleep(ticks uint32) { } } -//go:export RTC_IRQHandler -func handleRTC() { - // disable IRQ for CMP0 compare - sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0) - - timerWakeup.Set(1) -} - func initUSBClock() { // Turn on clock for USB sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_) diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go index 0b97b2783..7cbcad044 100644 --- a/src/runtime/runtime_atsamd51.go +++ b/src/runtime/runtime_atsamd51.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/sam" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -202,8 +203,14 @@ func initRTC() { for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) { } - arm.SetPriority(sam.IRQ_RTC, 0xc0) - arm.EnableIRQ(sam.IRQ_RTC) + irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) { + // disable IRQ for CMP0 compare + sam.RTC_MODE0.INTFLAG.SetBits(sam.RTC_MODE0_INTENSET_CMP0) + + timerWakeup.Set(1) + }) + irq.SetPriority(0xc0) + irq.Enable() } func waitForSync() { @@ -271,14 +278,6 @@ func timerSleep(ticks uint32) { } } -//go:export RTC_IRQHandler -func handleRTC() { - // disable IRQ for CMP0 compare - sam.RTC_MODE0.INTFLAG.SetBits(sam.RTC_MODE0_INTENSET_CMP0) - - timerWakeup.Set(1) -} - func initUSBClock() { // Turn on clock(s) for USB //MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index c289437d2..50d3e5f2b 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/nrf" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -43,8 +44,13 @@ func initLFCLK() { func initRTC() { nrf.RTC1.TASKS_START.Set(1) - arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority - arm.EnableIRQ(nrf.IRQ_RTC1) + intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) { + nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0) + nrf.RTC1.EVENTS_COMPARE[0].Set(0) + rtc_wakeup.Set(1) + }) + intr.SetPriority(0xc0) // low priority + intr.Enable() } func putchar(c byte) { @@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) { arm.Asm("wfi") } } - -//go:export RTC1_IRQHandler -func handleRTC1() { - nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0) - nrf.RTC1.EVENTS_COMPARE[0].Set(0) - rtc_wakeup.Set(1) -} diff --git a/src/runtime/runtime_stm32f103xx.go b/src/runtime/runtime_stm32f103xx.go index 397e3c92f..8c29c264b 100644 --- a/src/runtime/runtime_stm32f103xx.go +++ b/src/runtime/runtime_stm32f103xx.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/stm32" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -101,8 +102,9 @@ func initRTC() { func initTIM() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) - arm.SetPriority(stm32.IRQ_TIM3, 0xc3) - arm.EnableIRQ(stm32.IRQ_TIM3) + intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3) + intr.SetPriority(0xc3) + intr.Enable() } const asyncScheduler = false @@ -186,8 +188,7 @@ func timerSleep(ticks uint32) { } } -//go:export TIM3_IRQHandler -func handleTIM3() { +func handleTIM3(interrupt.Interrupt) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { // Disable the timer. stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go index 270e1b8a8..fd1be5b43 100644 --- a/src/runtime/runtime_stm32f407.go +++ b/src/runtime/runtime_stm32f407.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/stm32" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -121,8 +122,9 @@ var timerWakeup volatile.Register8 func initTIM3() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) - arm.SetPriority(stm32.IRQ_TIM3, 0xc3) - arm.EnableIRQ(stm32.IRQ_TIM3) + intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3) + intr.SetPriority(0xc3) + intr.Enable() } // Enable the TIM7 clock.(tick count) @@ -139,8 +141,9 @@ func initTIM7() { // Enable the timer. stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN) - arm.SetPriority(stm32.IRQ_TIM7, 0xc1) - arm.EnableIRQ(stm32.IRQ_TIM7) + intr := interrupt.New(stm32.IRQ_TIM7, handleTIM7) + intr.SetPriority(0xc1) + intr.Enable() } const asyncScheduler = false @@ -183,8 +186,7 @@ func timerSleep(ticks uint32) { } } -//go:export TIM3_IRQHandler -func handleTIM3() { +func handleTIM3(interrupt.Interrupt) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { // Disable the timer. stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) @@ -197,8 +199,7 @@ func handleTIM3() { } } -//go:export TIM7_IRQHandler -func handleTIM7() { +func handleTIM7(interrupt.Interrupt) { if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) { // clear the update flag stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF) diff --git a/tools/gen-device-avr/gen-device-avr.go b/tools/gen-device-avr/gen-device-avr.go index ac4c8b996..8f9298a21 100755 --- a/tools/gen-device-avr/gen-device-avr.go +++ b/tools/gen-device-avr/gen-device-avr.go @@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error { package {{.pkgName}} import ( + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -277,6 +278,12 @@ 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}} +) + // Peripherals. var ({{range .peripherals}} // {{.Caption}} diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index cef0365d5..f5aebcfe8 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -82,6 +82,7 @@ type Device struct { type interrupt struct { Name string + HandlerName string peripheralIndex int Value int // interrupt number Description string @@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) { groupName := cleanName(periphEl.GroupName) for _, interrupt := range periphEl.Interrupts { - addInterrupt(interrupts, interrupt.Name, interrupt.Index, description) + addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description) // As a convenience, also use the peripheral name as the interrupt // name. Only do that for the nrf for now, as the stm32 .svd files // don't always put interrupts in the correct peripheral... if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") { - addInterrupt(interrupts, periphEl.Name, interrupt.Index, description) + addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description) } } @@ -388,7 +389,7 @@ func readSVD(path, sourceURL string) (*Device, error) { }, nil } -func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) { +func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { // Note: some SVD files like the one for STM32H7x7 contain mistakes. @@ -409,6 +410,7 @@ func addInterrupt(interrupts map[string]*interrupt, name string, index int, desc } else { interrupts[name] = &interrupt{ Name: name, + HandlerName: interruptName + "_IRQHandler", peripheralIndex: len(interrupts), Value: index, Description: description, @@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error { package {{.pkgName}} import ( + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -628,12 +631,18 @@ const ( DEVICE = "{{.metadata.name}}" ) -// Interrupt numbers +// Interrupt numbers. const ({{range .interrupts}} IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}} 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}}, "{{.HandlerName}}"){{end}} +) + // Peripherals. var ( {{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}} @@ -879,7 +888,7 @@ Default_Handler: num++ } num++ - fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name) + fmt.Fprintf(w, " .long %s\n", intr.HandlerName) } w.WriteString(` diff --git a/transform/errors.go b/transform/errors.go new file mode 100644 index 000000000..226ead28c --- /dev/null +++ b/transform/errors.go @@ -0,0 +1,48 @@ +package transform + +import ( + "go/scanner" + "go/token" + "path/filepath" + + "tinygo.org/x/go-llvm" +) + +// errorAt returns an error value at the location of the value. +// The location information may not be complete as it depends on debug +// information in the IR. +func errorAt(val llvm.Value, msg string) scanner.Error { + return scanner.Error{ + Pos: getPosition(val), + Msg: msg, + } +} + +// getPosition returns the position information for the given value, as far as +// it is available. +func getPosition(val llvm.Value) token.Position { + if !val.IsAInstruction().IsNil() { + loc := val.InstructionDebugLoc() + if loc.IsNil() { + return token.Position{} + } + file := loc.LocationScope().ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.LocationLine()), + Column: int(loc.LocationColumn()), + } + } else if !val.IsAFunction().IsNil() { + loc := val.Subprogram() + if loc.IsNil() { + return token.Position{} + } + file := loc.ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.SubprogramLine()), + } + } else { + return token.Position{} + } +} diff --git a/transform/interrupt.go b/transform/interrupt.go new file mode 100644 index 000000000..2a0dbe02e --- /dev/null +++ b/transform/interrupt.go @@ -0,0 +1,218 @@ +package transform + +import ( + "fmt" + "strings" + + "tinygo.org/x/go-llvm" +) + +// LowerInterrupts creates interrupt handlers for the interrupts created by +// runtime/interrupt.New. +// +// The operation is as follows. The compiler creates the following during IR +// generation: +// * calls to runtime/interrupt.Register that map interrupt IDs to ISR names. +// * runtime/interrupt.handle objects that store the (constant) interrupt ID and +// interrupt handler func value. +// +// This pass then creates the specially named interrupt handler names that +// simply call the registered handlers. This might seem like it causes extra +// overhead, but in fact inlining and const propagation will eliminate most if +// not all of that. +func LowerInterrupts(mod llvm.Module) []error { + var errs []error + + // Discover interrupts. The runtime/interrupt.Register call is a compiler + // intrinsic that maps interrupt numbers to handler names. + handlerNames := map[int64]string{} + for _, call := range getUses(mod.NamedFunction("runtime/interrupt.Register")) { + if call.IsACallInst().IsNil() { + errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.Register?")) + continue + } + + num := call.Operand(0) + if num.IsAConstant().IsNil() { + errs = append(errs, errorAt(call, "non-constant interrupt number?")) + continue + } + + // extract the interrupt name + nameStrGEP := call.Operand(1) + if nameStrGEP.IsAConstantExpr().IsNil() || nameStrGEP.Opcode() != llvm.GetElementPtr { + errs = append(errs, errorAt(call, "expected a string operand?")) + continue + } + nameStrPtr := nameStrGEP.Operand(0) // note: assuming it's a GEP to the first byte + nameStrLen := call.Operand(2) + if nameStrPtr.IsAGlobalValue().IsNil() || !nameStrPtr.IsGlobalConstant() || nameStrLen.IsAConstant().IsNil() { + errs = append(errs, errorAt(call, "non-constant interrupt name?")) + continue + } + + // keep track of this name + name := string(getGlobalBytes(nameStrPtr)[:nameStrLen.SExtValue()]) + handlerNames[num.SExtValue()] = name + + // remove this pseudo-call + call.ReplaceAllUsesWith(llvm.ConstNull(call.Type())) + call.EraseFromParentAsInstruction() + } + + ctx := mod.Context() + nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0)) + builder := ctx.NewBuilder() + defer builder.Dispose() + + // Create a function type with the signature of an interrupt handler. + fnType := llvm.FunctionType(ctx.VoidType(), nil, false) + + // Collect a slice of interrupt handle objects. The fact that they still + // exist in the IR indicates that they could not be optimized away, + // therefore we need to make real interrupt handlers for them. + var handlers []llvm.Value + handleType := mod.GetTypeByName("runtime/interrupt.handle") + if !handleType.IsNil() { + handlePtrType := llvm.PointerType(handleType, 0) + for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { + if global.Type() != handlePtrType { + continue + } + handlers = append(handlers, global) + } + } + + // Iterate over all handler objects, replacing their ptrtoint uses with a + // real interrupt ID and creating an interrupt handler for them. + for _, global := range handlers { + initializer := global.Initializer() + num := llvm.ConstExtractValue(initializer, []uint32{1, 0}) + name := handlerNames[num.SExtValue()] + + if name == "" { + errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) + continue + } + + // Extract the func value. + handlerContext := llvm.ConstExtractValue(initializer, []uint32{0, 0}) + handlerFuncPtr := llvm.ConstExtractValue(initializer, []uint32{0, 1}) + if !handlerContext.IsConstant() || !handlerFuncPtr.IsConstant() { + // This should have been checked already in the compiler. + errs = append(errs, errorAt(global, "func value must be constant")) + continue + } + if !handlerFuncPtr.IsAConstantExpr().IsNil() && handlerFuncPtr.Opcode() == llvm.PtrToInt { + // This is a ptrtoint: the IR was created for func lowering using a + // switch statement. + global := handlerFuncPtr.Operand(0) + if global.IsAGlobalValue().IsNil() { + errs = append(errs, errorAt(global, "internal error: expected a global for func lowering")) + continue + } + initializer := global.Initializer() + if initializer.Type() != mod.GetTypeByName("runtime.funcValueWithSignature") { + errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected type")) + continue + } + ptrtoint := llvm.ConstExtractValue(initializer, []uint32{0}) + if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt { + errs = append(errs, errorAt(global, "internal error: func lowering global has unexpected func ptr type")) + continue + } + handlerFuncPtr = ptrtoint.Operand(0) + } + if handlerFuncPtr.Type().TypeKind() != llvm.PointerTypeKind || handlerFuncPtr.Type().ElementType().TypeKind() != llvm.FunctionTypeKind { + errs = append(errs, errorAt(global, "internal error: unexpected LLVM types in func value")) + continue + } + + // Check for an existing interrupt handler, and report it as an error if + // there is one. + fn := mod.NamedFunction(name) + if fn.IsNil() { + fn = llvm.AddFunction(mod, name, fnType) + } else if fn.Type().ElementType() != fnType { + // Don't bother with a precise error message (listing the previsous + // location) because this should not normally happen anyway. + errs = append(errs, errorAt(global, name+" redeclared with a different signature")) + continue + } else if !fn.IsDeclaration() { + // Interrupt handler was already defined. Check the first + // instruction (which should be a call) whether this handler would + // be identical anyway. + firstInst := fn.FirstBasicBlock().FirstInstruction() + if !firstInst.IsACallInst().IsNil() && firstInst.OperandsCount() == 4 && firstInst.CalledValue() == handlerFuncPtr && firstInst.Operand(0) == num && firstInst.Operand(1) == handlerContext { + // Already defined and apparently identical, so assume this is + // fine. + continue + } + + errValue := name + " redeclared in this program" + fnPos := getPosition(fn) + if fnPos.IsValid() { + errValue += "\n\tprevious declaration at " + fnPos.String() + } + errs = append(errs, errorAt(global, errValue)) + continue + } + + // Create the wrapper function which is the actual interrupt handler + // that is inserted in the interrupt vector. + fn.SetUnnamedAddr(true) + fn.SetSection(".text." + name) + entryBlock := ctx.AddBasicBlock(fn, "entry") + builder.SetInsertPointAtEnd(entryBlock) + + // Set the 'interrupt' flag if needed on this platform. + if strings.HasPrefix(mod.Target(), "avr") { + // This special calling convention is needed on AVR to save and + // restore all clobbered registers, instead of just the ones that + // would need to be saved/restored in a normal function call. + // Note that the AVR_INTERRUPT calling convention would enable + // interrupts right at the beginning of the handler, potentially + // leading to lots of nested interrupts and a stack overflow. + fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL + } + + // Fill the function declaration with the forwarding call. + // In practice, the called function will often be inlined which avoids + // the extra indirection. + builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "") + builder.CreateRetVoid() + + // Replace all ptrtoint uses of the global with the interrupt constant. + // That can only now be safely done after the interrupt handler has been + // created, doing it before the interrupt handler is created might + // result in this interrupt handler being optimized away entirely. + for _, user := range getUses(global) { + if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt { + errs = append(errs, errorAt(global, "internal error: expected a ptrtoint")) + continue + } + user.ReplaceAllUsesWith(num) + } + + // The runtime/interrput.handle struct can finally be removed. + // It would probably be eliminated anyway by a globaldce pass but it's + // better to do it now to be sure. + global.EraseFromParentAsGlobal() + } + + // Remove now-useless runtime/interrupt.use calls. These are used for some + // platforms like AVR that do not need to enable interrupts to use them, so + // need another way to keep them alive. + // After interrupts have been lowered, this call is useless and would cause + // a linker error so must be removed. + for _, call := range getUses(mod.NamedFunction("runtime/interrupt.use")) { + if call.IsACallInst().IsNil() { + errs = append(errs, errorAt(call, "internal error: expected call to runtime/interrupt.use")) + continue + } + + call.EraseFromParentAsInstruction() + } + + return errs +} diff --git a/transform/interrupt_test.go b/transform/interrupt_test.go new file mode 100644 index 000000000..00a199063 --- /dev/null +++ b/transform/interrupt_test.go @@ -0,0 +1,24 @@ +package transform + +import ( + "testing" + + "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 := LowerInterrupts(mod) + if len(errs) != 0 { + t.Fail() + for _, err := range errs { + t.Error(err) + } + } + }) + }) + } +} diff --git a/transform/testdata/interrupt-avr.ll b/transform/testdata/interrupt-avr.ll new file mode 100644 index 000000000..1c195634b --- /dev/null +++ b/transform/testdata/interrupt-avr.ll @@ -0,0 +1,33 @@ +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-atmel-none" + +%"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 new file mode 100644 index 000000000..b71c43998 --- /dev/null +++ b/transform/testdata/interrupt-avr.out.ll @@ -0,0 +1,35 @@ +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-atmel-none" + +%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*, i32, i8, i8, 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-cortexm.ll new file mode 100644 index 000000000..2c156b5b6 --- /dev/null +++ b/transform/testdata/interrupt-cortexm.ll @@ -0,0 +1,38 @@ +target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" +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.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 } } [email protected] = 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 @"device/arm.EnableIRQ"(i32, i8* nocapture readnone, i8* nocapture readnone) + +declare void @"device/arm.SetPriority"(i32, i32, i8* nocapture readnone, i8* nocapture readnone) + +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) + ret void +} + +define internal void @"(*machine.UART).handleInterrupt$bound"(i32, i8* nocapture %context, i8* nocapture readnone %parentHandle) { +entry: + %unpack.ptr = bitcast i8* %context to %machine.UART* + call void @"(*machine.UART).handleInterrupt"(%machine.UART* %unpack.ptr, i32 %0, i8* undef, i8* undef) + ret void +} + +declare void @"(*machine.UART).handleInterrupt"(%machine.UART* nocapture, i32, i8* nocapture readnone, i8* nocapture readnone) diff --git a/transform/testdata/interrupt-cortexm.out.ll b/transform/testdata/interrupt-cortexm.out.ll new file mode 100644 index 000000000..41fa9a776 --- /dev/null +++ b/transform/testdata/interrupt-cortexm.out.ll @@ -0,0 +1,39 @@ +target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" +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 } + [email protected] = 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 @"device/arm.EnableIRQ"(i32, i8* nocapture readnone, i8* nocapture readnone) + +declare void @"device/arm.SetPriority"(i32, i32, i8* nocapture readnone, i8* nocapture readnone) + +define void @runtime.initAll(i8* nocapture readnone, i8* nocapture readnone) unnamed_addr { +entry: + call void @"device/arm.SetPriority"(i32 2, i32 192, i8* undef, i8* undef) + call void @"device/arm.EnableIRQ"(i32 2, i8* undef, i8* undef) + ret void +} + +define internal void @"(*machine.UART).handleInterrupt$bound"(i32, i8* nocapture %context, i8* nocapture readnone %parentHandle) { +entry: + %unpack.ptr = bitcast i8* %context to %machine.UART* + call void @"(*machine.UART).handleInterrupt"(%machine.UART* %unpack.ptr, i32 %0, i8* undef, i8* undef) + ret void +} + +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 +} |