Browse Source

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.
pull/859/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
a5ed993f8d
  1. 7
      compiler/compiler.go
  2. 92
      compiler/interrupt.go
  3. 10
      compiler/optimizer.go
  4. 35
      ir/ir.go
  5. 18
      src/machine/board_arduino_nano33_baremetal.go
  6. 11
      src/machine/board_bluepill.go
  7. 10
      src/machine/board_circuitplay_express_baremetal.go
  8. 10
      src/machine/board_feather-m0.go
  9. 6
      src/machine/board_itsybitsy-m0.go
  10. 11
      src/machine/board_nucleof103rb.go
  11. 10
      src/machine/board_trinket.go
  12. 25
      src/machine/machine_atmega.go
  13. 28
      src/machine/machine_atsamd21.go
  14. 33
      src/machine/machine_atsamd51.go
  15. 9
      src/machine/machine_nrf.go
  16. 5
      src/machine/machine_nrf51.go
  17. 5
      src/machine/machine_nrf52.go
  18. 5
      src/machine/machine_nrf52840.go
  19. 18
      src/machine/machine_stm32f103xx.go
  20. 14
      src/machine/machine_stm32f407.go
  21. 38
      src/runtime/interrupt/interrupt.go
  22. 23
      src/runtime/interrupt/interrupt_cortexm.go
  23. 19
      src/runtime/runtime_atsamd21.go
  24. 19
      src/runtime/runtime_atsamd51.go
  25. 17
      src/runtime/runtime_nrf.go
  26. 9
      src/runtime/runtime_stm32f103xx.go
  27. 17
      src/runtime/runtime_stm32f407.go
  28. 7
      tools/gen-device-avr/gen-device-avr.go
  29. 19
      tools/gen-device-svd/gen-device-svd.go
  30. 48
      transform/errors.go
  31. 218
      transform/interrupt.go
  32. 24
      transform/interrupt_test.go
  33. 33
      transform/testdata/interrupt-avr.ll
  34. 35
      transform/testdata/interrupt-avr.out.ll
  35. 38
      transform/testdata/interrupt-cortexm.ll
  36. 39
      transform/testdata/interrupt-cortexm.out.ll

7
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)

92
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
}

10
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 {

35
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

18
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.

11
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

10
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.

10
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

6
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

11
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

10
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

25
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))
}
}

28
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)

33
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)

9
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)

5
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))

5
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))

5
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))

18
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

14
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)))
}

38
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
}

23
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))
}

19
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_)

19
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;

17
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)
}

9
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)

17
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)

7
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}}

19
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(`

48
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{}
}
}

218
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
}

24
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)
}
}
})
})
}
}

33
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 } }
@machine.UART0 = internal global %machine.UART zeroinitializer
@"device/avr.init$string.18" = internal unnamed_addr constant [17 x i8] c"__vector_USART_RX"
declare void @"(machine.UART).Configure$1"(i32, i8*, i8*) unnamed_addr addrspace(1)
declare i32 @"runtime/interrupt.Register"(i32, i8*, i16, i8*, i8*) addrspace(1)
declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") addrspace(1)
define void @"(machine.UART).Configure"(%machine.RingBuffer*, i32, i8, i8, i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) {
call addrspace(1) void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt" { i32 ptrtoint (%"runtime/interrupt.handle"* @"runtime/interrupt.$interrupt18" to i32) })
ret void
}
define void @"device/avr.init"(i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) {
entry:
%0 = call addrspace(1) i32 @"runtime/interrupt.Register"(i32 18, i8* getelementptr inbounds ([17 x i8], [17 x i8]* @"device/avr.init$string.18", i32 0, i32 0), i16 17, i8* undef, i8* undef)
ret void
}

35
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}{}" }
@machine.UART0 = internal global %machine.UART zeroinitializer
@"device/avr.init$string.18" = internal unnamed_addr constant [17 x i8] c"__vector_USART_RX"
declare void @"(machine.UART).Configure$1"(i32, i8*, i8*) unnamed_addr addrspace(1)
declare i32 @"runtime/interrupt.Register"(i32, i8*, i16, i8*, i8*) addrspace(1)
declare void @"runtime/interrupt.use"(%"runtime/interrupt.Interrupt") addrspace(1)
define void @"(machine.UART).Configure"(%machine.RingBuffer*, i32, i8, i8, i8* %context, i8* %parentHandle) unnamed_addr addrspace(1) {
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
}

38
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 } }
@machine.UART0 = internal global %machine.UART { %machine.RingBuffer* @"machine$alloc.335" }
@"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer
@"device/nrf.init$string.2" = internal unnamed_addr constant [23 x i8] c"UARTE0_UART0_IRQHandler"
@"device/nrf.init$string.3" = internal unnamed_addr constant [44 x i8] c"SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler"
declare i32 @"runtime/interrupt.Register"(i32, i8*, i32, i8*, i8*) local_unnamed_addr
declare void @"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)

39
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 }
@machine.UART0 = internal global %machine.UART { %machine.RingBuffer* @"machine$alloc.335" }
@"machine$alloc.335" = internal global %machine.RingBuffer zeroinitializer
@"device/nrf.init$string.2" = internal unnamed_addr constant [23 x i8] c"UARTE0_UART0_IRQHandler"
@"device/nrf.init$string.3" = internal unnamed_addr constant [44 x i8] c"SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler"
declare i32 @"runtime/interrupt.Register"(i32, i8*, i32, i8*, i8*) local_unnamed_addr
declare void @"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
}
Loading…
Cancel
Save