aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-12-15 14:16:22 +0100
committerRon Evans <[email protected]>2020-01-20 21:19:12 +0100
commita5ed993f8dda8cfc71de549e60323236feb85c05 (patch)
treeabd0d3577cf13a6e3a2d61379b689e744bda7ef6
parent3729fcfa9edba2230aba65bcd607621d3285704f (diff)
downloadtinygo-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.
-rw-r--r--compiler/compiler.go7
-rw-r--r--compiler/interrupt.go92
-rw-r--r--compiler/optimizer.go10
-rw-r--r--ir/ir.go35
-rw-r--r--src/machine/board_arduino_nano33_baremetal.go18
-rw-r--r--src/machine/board_bluepill.go11
-rw-r--r--src/machine/board_circuitplay_express_baremetal.go10
-rw-r--r--src/machine/board_feather-m0.go10
-rw-r--r--src/machine/board_itsybitsy-m0.go6
-rw-r--r--src/machine/board_nucleof103rb.go11
-rw-r--r--src/machine/board_trinket.go10
-rw-r--r--src/machine/machine_atmega.go25
-rw-r--r--src/machine/machine_atsamd21.go28
-rw-r--r--src/machine/machine_atsamd51.go33
-rw-r--r--src/machine/machine_nrf.go9
-rw-r--r--src/machine/machine_nrf51.go5
-rw-r--r--src/machine/machine_nrf52.go5
-rw-r--r--src/machine/machine_nrf52840.go5
-rw-r--r--src/machine/machine_stm32f103xx.go18
-rw-r--r--src/machine/machine_stm32f407.go14
-rw-r--r--src/runtime/interrupt/interrupt.go38
-rw-r--r--src/runtime/interrupt/interrupt_cortexm.go23
-rw-r--r--src/runtime/runtime_atsamd21.go19
-rw-r--r--src/runtime/runtime_atsamd51.go19
-rw-r--r--src/runtime/runtime_nrf.go17
-rw-r--r--src/runtime/runtime_stm32f103xx.go9
-rw-r--r--src/runtime/runtime_stm32f407.go17
-rwxr-xr-xtools/gen-device-avr/gen-device-avr.go7
-rwxr-xr-xtools/gen-device-svd/gen-device-svd.go19
-rw-r--r--transform/errors.go48
-rw-r--r--transform/interrupt.go218
-rw-r--r--transform/interrupt_test.go24
-rw-r--r--transform/testdata/interrupt-avr.ll33
-rw-r--r--transform/testdata/interrupt-avr.out.ll35
-rw-r--r--transform/testdata/interrupt-cortexm.ll38
-rw-r--r--transform/testdata/interrupt-cortexm.out.ll39
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 {
diff --git a/ir/ir.go b/ir/ir.go
index 9d243bae7..7506400df 100644
--- a/ir/ir.go
+++ b/ir/ir.go
@@ -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
+}