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.
Этот коммит содержится в:
Ayke van Laethem 2019-12-15 14:16:22 +01:00 коммит произвёл Ron Evans
родитель 3729fcfa9e
коммит a5ed993f8d
36 изменённых файлов: 766 добавлений и 199 удалений

Просмотреть файл

@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error {
path = path[len(tinygoPath+"/src/"):] path = path[len(tinygoPath+"/src/"):]
} }
switch path { 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 return path
default: default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { 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.SetLinkage(llvm.InternalLinkage)
frame.fn.LLVMFn.SetUnnamedAddr(true) 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. // Some functions have a pragma controlling the inlining level.
switch frame.fn.Inline() { 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) return c.emitVolatileLoad(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
return c.emitVolatileStore(frame, instr) return c.emitVolatileStore(frame, instr)
case name == "runtime/interrupt.New":
return c.emitInterruptGlobal(frame, instr)
} }
targetFunc := c.ir.GetFunction(fn) 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
}

Просмотреть файл

@ -57,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
transform.OptimizeStringToBytes(c.mod) transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod) transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod) transform.LowerInterfaces(c.mod)
errs := transform.LowerInterrupts(c.mod)
if len(errs) > 0 {
return errs
}
if c.funcImplementation() == funcValueSwitch { if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod) transform.LowerFuncValues(c.mod)
} }
@ -100,6 +106,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
errs := transform.LowerInterrupts(c.mod)
if len(errs) > 0 {
return errs
}
} }
if c.VerifyIR() { if c.VerifyIR() {
if errs := c.checkModule(); errs != nil { if errs := c.checkModule(); errs != nil {

Просмотреть файл

@ -29,11 +29,10 @@ type Function struct {
*ssa.Function *ssa.Function
LLVMFn llvm.Value LLVMFn llvm.Value
module string // go:wasm-module module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt linkName string // go:linkname, go:export
exported bool // go:export exported bool // go:export
nobounds bool // go:nobounds nobounds bool // go:nobounds
flag bool // used by dead code elimination flag bool // used by dead code elimination
interrupt bool // go:interrupt
inline InlineType // go:inline inline InlineType // go:inline
} }
@ -243,18 +242,6 @@ func (f *Function) parsePragmas() {
f.inline = InlineHint f.inline = InlineHint
case "//go:noinline": case "//go:noinline":
f.inline = InlineNone 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": case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() { if len(parts) != 3 || parts[1] != f.Name() {
continue continue
@ -288,14 +275,6 @@ func (f *Function) IsExported() bool {
return f.exported || f.CName() != "" 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. // Return the inline directive of this function.
func (f *Function) Inline() InlineType { func (f *Function) Inline() InlineType {
return f.inline return f.inline

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. // UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
var ( 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. // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
var ( var (
UART2 = UART{ UART2 = UART{
@ -27,11 +25,9 @@ var (
} }
) )
//go:export SERCOM5_IRQHandler func init() {
func handleUART2() { UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3, UART1.handleInterrupt)
// should reset IRQ UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM5, UART2.handleInterrupt)
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
} }
// I2C on the Arduino Nano 33. // I2C on the Arduino Nano 33.

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/stm32" import (
"device/stm32"
"runtime/interrupt"
)
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif // https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
const ( const (
@ -61,14 +64,12 @@ var (
UART0 = UART{ UART0 = UART{
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: stm32.USART1, Bus: stm32.USART1,
IRQVal: stm32.IRQ_USART1,
} }
UART1 = &UART0 UART1 = &UART0
) )
//go:export USART1_IRQHandler func init() {
func handleUART1() { UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt)
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
} }
// SPI pins // SPI pins

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// UART1 on the Circuit Playground Express. // UART1 on the Circuit Playground Express.
var ( var (
@ -13,9 +16,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM4, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C on the Circuit Playground Express. // I2C on the Circuit Playground Express.

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader // used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef const RESET_MAGIC_VALUE = 0xf01669ef
@ -60,9 +63,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C pins // I2C pins

Просмотреть файл

@ -4,6 +4,7 @@ package machine
import ( import (
"device/sam" "device/sam"
"runtime/interrupt"
) )
// used to reset into bootloader // used to reset into bootloader
@ -62,9 +63,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt)
defaultUART1Handler()
} }
// I2C pins // I2C pins

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/stm32" import (
"device/stm32"
"runtime/interrupt"
)
const ( const (
PA0 = portA + 0 PA0 = portA + 0
@ -100,14 +103,12 @@ var (
UART0 = UART{ UART0 = UART{
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: stm32.USART2, Bus: stm32.USART2,
IRQVal: stm32.IRQ_USART2,
} }
UART2 = &UART0 UART2 = &UART0
) )
//go:export USART2_IRQHandler func init() {
func handleUART2() { UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
} }
// SPI pins // SPI pins

Просмотреть файл

@ -2,7 +2,10 @@
package machine package machine
import "device/sam" import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader // used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef const RESET_MAGIC_VALUE = 0xf01669ef
@ -51,9 +54,8 @@ var (
} }
) )
//go:export SERCOM1_IRQHandler func init() {
func handleUART1() { UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM0, UART1.handleInterrupt)
defaultUART1Handler()
} }
// SPI pins // SPI pins

Просмотреть файл

@ -4,6 +4,7 @@ package machine
import ( import (
"device/avr" "device/avr"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) {
config.BaudRate = 9600 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 // Set baud rate based on prescale formula from
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) // ((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 avr.UDR0.Set(c) // send char
return nil 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))
}
}

Просмотреть файл

@ -11,6 +11,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"errors" "errors"
"runtime/interrupt"
"unsafe" "unsafe"
) )
@ -295,6 +296,7 @@ type UART struct {
Buffer *RingBuffer Buffer *RingBuffer
Bus *sam.SERCOM_USART_Type Bus *sam.SERCOM_USART_Type
SERCOM uint8 SERCOM uint8
Interrupt interrupt.Interrupt
} }
var ( var (
@ -401,10 +403,7 @@ func (uart UART) Configure(config UARTConfig) error {
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC) uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
// Enable RX IRQ. // Enable RX IRQ.
// IRQ lines are in the same order as SERCOM instance numbers on SAMD21 uart.Interrupt.Enable()
// chips, so the IRQ number can be trivially determined from the SERCOM
// number.
arm.EnableIRQ(sam.IRQ_SERCOM0 + uint32(uart.SERCOM))
return nil return nil
} }
@ -432,11 +431,12 @@ func (uart UART) WriteByte(c byte) error {
return nil return nil
} }
// defaultUART1Handler handles the UART1 IRQ. // handleInterrupt should be called from the appropriate interrupt handler for
func defaultUART1Handler() { // this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
// should reset IRQ // should reset IRQ
UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF))) uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
} }
// I2C on the SAMD21. // I2C on the SAMD21.
@ -1368,7 +1368,8 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
// enable IRQ // enable IRQ
arm.EnableIRQ(sam.IRQ_USB) intr := interrupt.New(sam.IRQ_USB, handleUSB)
intr.Enable()
} }
func handlePadCalibration() { func handlePadCalibration() {
@ -1414,8 +1415,7 @@ func handlePadCalibration() {
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
} }
//go:export USB_IRQHandler func handleUSB(intr interrupt.Interrupt) {
func handleUSB() {
// reset all interrupt flags // reset all interrupt flags
flags := sam.USB_DEVICE.INTFLAG.Get() flags := sam.USB_DEVICE.INTFLAG.Get()
sam.USB_DEVICE.INTFLAG.Set(flags) sam.USB_DEVICE.INTFLAG.Set(flags)

Просмотреть файл

@ -11,6 +11,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"errors" "errors"
"runtime/interrupt"
"unsafe" "unsafe"
) )
@ -1547,11 +1548,11 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
// enable USB // enable USB
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
// enable IRQ // enable IRQ at highest priority
arm.EnableIRQ(sam.IRQ_USB_OTHER) interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_SOF_HSOF) interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_TRCPT0) interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable()
arm.EnableIRQ(sam.IRQ_USB_TRCPT1) interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable()
} }
func handlePadCalibration() { func handlePadCalibration() {
@ -1597,27 +1598,7 @@ func handlePadCalibration() {
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
} }
//go:export USB_OTHER_IRQHandler func handleUSBIRQ(interrupt.Interrupt) {
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() {
// reset all interrupt flags // reset all interrupt flags
flags := sam.USB_DEVICE.INTFLAG.Get() flags := sam.USB_DEVICE.INTFLAG.Get()
sam.USB_DEVICE.INTFLAG.Set(flags) sam.USB_DEVICE.INTFLAG.Set(flags)

Просмотреть файл

@ -3,9 +3,9 @@
package machine package machine
import ( import (
"device/arm"
"device/nrf" "device/nrf"
"errors" "errors"
"runtime/interrupt"
) )
var ( var (
@ -88,8 +88,9 @@ func (uart UART) Configure(config UARTConfig) {
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk) nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
// Enable RX IRQ. // Enable RX IRQ.
arm.SetPriority(nrf.IRQ_UART0, 0xc0) // low priority intr := interrupt.New(nrf.IRQ_UART0, UART0.handleInterrupt)
arm.EnableIRQ(nrf.IRQ_UART0) intr.SetPriority(0xc0) // low priority
intr.Enable()
} }
// SetBaudRate sets the communication speed for the UART. // SetBaudRate sets the communication speed for the UART.
@ -116,7 +117,7 @@ func (uart UART) WriteByte(c byte) error {
return nil return nil
} }
func (uart UART) handleInterrupt() { func (uart *UART) handleInterrupt(interrupt.Interrupt) {
if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { if nrf.UART0.EVENTS_RXDRDY.Get() != 0 {
uart.Receive(byte(nrf.UART0.RXD.Get())) uart.Receive(byte(nrf.UART0.RXD.Get()))
nrf.UART0.EVENTS_RXDRDY.Set(0x0) nrf.UART0.EVENTS_RXDRDY.Set(0x0)

Просмотреть файл

@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx)) nrf.UART0.PSELRXD.Set(uint32(rx))
} }
//go:export UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda)) i2c.Bus.PSELSDA.Set(uint32(sda))

Просмотреть файл

@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSELRXD.Set(uint32(rx)) nrf.UART0.PSELRXD.Set(uint32(rx))
} }
//go:export UARTE0_UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSCL.Set(uint32(scl))
i2c.Bus.PSELSDA.Set(uint32(sda)) i2c.Bus.PSELSDA.Set(uint32(sda))

Просмотреть файл

@ -77,11 +77,6 @@ func (uart UART) setPins(tx, rx Pin) {
nrf.UART0.PSEL.RXD.Set(uint32(rx)) nrf.UART0.PSEL.RXD.Set(uint32(rx))
} }
//go:export UARTE0_UART0_IRQHandler
func handleUART0() {
UART0.handleInterrupt()
}
func (i2c I2C) setPins(scl, sda Pin) { func (i2c I2C) setPins(scl, sda Pin) {
i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SCL.Set(uint32(scl))
i2c.Bus.PSEL.SDA.Set(uint32(sda)) i2c.Bus.PSEL.SDA.Set(uint32(sda))

Просмотреть файл

@ -5,9 +5,9 @@ package machine
// Peripheral abstraction layer for the stm32. // Peripheral abstraction layer for the stm32.
import ( import (
"device/arm"
"device/stm32" "device/stm32"
"errors" "errors"
"runtime/interrupt"
) )
func CPUFrequency() uint32 { func CPUFrequency() uint32 {
@ -113,7 +113,7 @@ func (p Pin) Get() bool {
type UART struct { type UART struct {
Buffer *RingBuffer Buffer *RingBuffer
Bus *stm32.USART_Type Bus *stm32.USART_Type
IRQVal uint32 Interrupt interrupt.Interrupt
} }
// Configure the UART. // 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) uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ // Enable RX IRQ
arm.SetPriority(uart.IRQVal, 0xc0) uart.Interrupt.SetPriority(0xc0)
arm.EnableIRQ(uart.IRQVal) uart.Interrupt.Enable()
} }
// SetBaudRate sets the communication speed for the UART. // SetBaudRate sets the communication speed for the UART.
@ -182,6 +182,12 @@ func (uart UART) WriteByte(c byte) error {
return nil 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. // SPI on the STM32.
type SPI struct { type SPI struct {
Bus *stm32.SPI_Type Bus *stm32.SPI_Type

Просмотреть файл

@ -5,8 +5,8 @@ package machine
// Peripheral abstraction layer for the stm32. // Peripheral abstraction layer for the stm32.
import ( import (
"device/arm"
"device/stm32" "device/stm32"
"runtime/interrupt"
) )
func CPUFrequency() uint32 { 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) stm32.USART2.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ. // Enable RX IRQ.
arm.SetPriority(stm32.IRQ_USART2, 0xc0) intr := interrupt.New(stm32.IRQ_USART2, func(interrupt.Interrupt) {
arm.EnableIRQ(stm32.IRQ_USART2) UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF)))
})
intr.SetPriority(0xc0)
intr.Enable()
} }
// WriteByte writes a byte of data to the UART. // WriteByte writes a byte of data to the UART.
@ -215,8 +218,3 @@ func (uart UART) WriteByte(c byte) error {
} }
return nil 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))
}

Просмотреть файл

@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@ -214,8 +215,14 @@ func initRTC() {
sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE) sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE)
waitForSync() waitForSync()
arm.SetPriority(sam.IRQ_RTC, 0xc0) intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
arm.EnableIRQ(sam.IRQ_RTC) // 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() { 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() { func initUSBClock() {
// Turn on clock for USB // Turn on clock for USB
sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_) sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_)

Просмотреть файл

@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@ -202,8 +203,14 @@ func initRTC() {
for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) { for sam.RTC_MODE0.SYNCBUSY.HasBits(sam.RTC_MODE0_SYNCBUSY_ENABLE) {
} }
arm.SetPriority(sam.IRQ_RTC, 0xc0) irq := interrupt.New(sam.IRQ_RTC, func(interrupt.Interrupt) {
arm.EnableIRQ(sam.IRQ_RTC) // 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() { 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() { func initUSBClock() {
// Turn on clock(s) for USB // Turn on clock(s) for USB
//MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; //MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB;

Просмотреть файл

@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/nrf" "device/nrf"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@ -43,8 +44,13 @@ func initLFCLK() {
func initRTC() { func initRTC() {
nrf.RTC1.TASKS_START.Set(1) nrf.RTC1.TASKS_START.Set(1)
arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
arm.EnableIRQ(nrf.IRQ_RTC1) 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) { func putchar(c byte) {
@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) {
arm.Asm("wfi") 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)
}

Просмотреть файл

@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/stm32" "device/stm32"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@ -101,8 +102,9 @@ func initRTC() {
func initTIM() { func initTIM() {
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
arm.SetPriority(stm32.IRQ_TIM3, 0xc3) intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
arm.EnableIRQ(stm32.IRQ_TIM3) intr.SetPriority(0xc3)
intr.Enable()
} }
const asyncScheduler = false const asyncScheduler = false
@ -186,8 +188,7 @@ func timerSleep(ticks uint32) {
} }
} }
//go:export TIM3_IRQHandler func handleTIM3(interrupt.Interrupt) {
func handleTIM3() {
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
// Disable the timer. // Disable the timer.
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)

Просмотреть файл

@ -6,6 +6,7 @@ import (
"device/arm" "device/arm"
"device/stm32" "device/stm32"
"machine" "machine"
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@ -121,8 +122,9 @@ var timerWakeup volatile.Register8
func initTIM3() { func initTIM3() {
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
arm.SetPriority(stm32.IRQ_TIM3, 0xc3) intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3)
arm.EnableIRQ(stm32.IRQ_TIM3) intr.SetPriority(0xc3)
intr.Enable()
} }
// Enable the TIM7 clock.(tick count) // Enable the TIM7 clock.(tick count)
@ -139,8 +141,9 @@ func initTIM7() {
// Enable the timer. // Enable the timer.
stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN) stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN)
arm.SetPriority(stm32.IRQ_TIM7, 0xc1) intr := interrupt.New(stm32.IRQ_TIM7, handleTIM7)
arm.EnableIRQ(stm32.IRQ_TIM7) intr.SetPriority(0xc1)
intr.Enable()
} }
const asyncScheduler = false const asyncScheduler = false
@ -183,8 +186,7 @@ func timerSleep(ticks uint32) {
} }
} }
//go:export TIM3_IRQHandler func handleTIM3(interrupt.Interrupt) {
func handleTIM3() {
if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) {
// Disable the timer. // Disable the timer.
stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN)
@ -197,8 +199,7 @@ func handleTIM3() {
} }
} }
//go:export TIM7_IRQHandler func handleTIM7(interrupt.Interrupt) {
func handleTIM7() {
if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) { if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) {
// clear the update flag // clear the update flag
stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF) stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF)

Просмотреть файл

@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error {
package {{.pkgName}} package {{.pkgName}}
import ( import (
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@ -277,6 +278,12 @@ const ({{range .interrupts}}
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. 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. // Peripherals.
var ({{range .peripherals}} var ({{range .peripherals}}
// {{.Caption}} // {{.Caption}}

Просмотреть файл

@ -82,6 +82,7 @@ type Device struct {
type interrupt struct { type interrupt struct {
Name string Name string
HandlerName string
peripheralIndex int peripheralIndex int
Value int // interrupt number Value int // interrupt number
Description string Description string
@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) {
groupName := cleanName(periphEl.GroupName) groupName := cleanName(periphEl.GroupName)
for _, interrupt := range periphEl.Interrupts { 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 // 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 // name. Only do that for the nrf for now, as the stm32 .svd files
// don't always put interrupts in the correct peripheral... // don't always put interrupts in the correct peripheral...
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") { 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 }, 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 _, ok := interrupts[name]; ok {
if interrupts[name].Value != index { if interrupts[name].Value != index {
// Note: some SVD files like the one for STM32H7x7 contain mistakes. // 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 { } else {
interrupts[name] = &interrupt{ interrupts[name] = &interrupt{
Name: name, Name: name,
HandlerName: interruptName + "_IRQHandler",
peripheralIndex: len(interrupts), peripheralIndex: len(interrupts),
Value: index, Value: index,
Description: description, Description: description,
@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error {
package {{.pkgName}} package {{.pkgName}}
import ( import (
"runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
) )
@ -628,12 +631,18 @@ const (
DEVICE = "{{.metadata.name}}" DEVICE = "{{.metadata.name}}"
) )
// Interrupt numbers // Interrupt numbers.
const ({{range .interrupts}} const ({{range .interrupts}}
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}} IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. 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. // Peripherals.
var ( var (
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}} {{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
@ -879,7 +888,7 @@ Default_Handler:
num++ num++
} }
num++ num++
fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name) fmt.Fprintf(w, " .long %s\n", intr.HandlerName)
} }
w.WriteString(` 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
}