diff --git a/compiler/compiler.go b/compiler/compiler.go index 28c0ceb69..ac8a0cf02 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1309,6 +1309,8 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e return c.emitVolatileLoad(frame, instr) case strings.HasPrefix(name, "runtime/volatile.Store"): return c.emitVolatileStore(frame, instr) + case name == "runtime/interrupt.New": + return c.emitInterruptGlobal(frame, instr) } targetFunc := c.ir.GetFunction(fn) diff --git a/compiler/interrupt.go b/compiler/interrupt.go new file mode 100644 index 000000000..ac9d8268c --- /dev/null +++ b/compiler/interrupt.go @@ -0,0 +1,91 @@ +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) + initializer := llvm.ConstNull(globalLLVMType) + initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0}) + initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0}) + global.SetInitializer(initializer) + + // Add debug info to the interrupt global. + if c.Debug() { + pos := c.ir.Program.Fset.Position(instr.Pos()) + diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ + Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), + LinkageName: globalName, + File: c.getDIFile(pos.Filename), + Line: pos.Line, + Type: c.getDIType(globalType), + Expr: c.dibuilder.CreateExpression(nil), + LocalToUnit: false, + }) + global.AddMetadata(0, diglobal) + } + + // Create the runtime/interrupt.Interrupt type. It is a struct with a single + // member of type int. + num := llvm.ConstPtrToInt(global, c.intType) + interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) + + // Add dummy "use" call for AVR, because interrupts may be used even though + // they are never referenced again. This is unlike Cortex-M or the RISC-V + // PLIC where each interrupt must be enabled using the interrupt number, and + // thus keeps the Interrupt object alive. + // This call is removed during interrupt lowering. + if strings.HasPrefix(c.Triple(), "avr") { + useFn := c.mod.NamedFunction("runtime/interrupt.use") + if useFn.IsNil() { + useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) + useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType) + } + c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "") + } + + return interrupt, nil +} diff --git a/src/machine/board_arduino_nano33_baremetal.go b/src/machine/board_arduino_nano33_baremetal.go index 829989a16..8f491142b 100644 --- a/src/machine/board_arduino_nano33_baremetal.go +++ b/src/machine/board_arduino_nano33_baremetal.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. var ( @@ -13,11 +16,6 @@ var ( } ) -//go:export SERCOM3_IRQHandler -func handleUART1() { - defaultUART1Handler() -} - // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins. var ( UART2 = UART{ @@ -27,11 +25,10 @@ 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() { + // Work around circular definitions. + UART1.interrupt = interrupt.New(sam.IRQ_SERCOM3, UART1.handleInterrupt) + UART2.interrupt = interrupt.New(sam.IRQ_SERCOM5, UART2.handleInterrupt) } // I2C on the Arduino Nano 33. diff --git a/src/machine/board_bluepill.go b/src/machine/board_bluepill.go index 39c7d421f..036dd2071 100644 --- a/src/machine/board_bluepill.go +++ b/src/machine/board_bluepill.go @@ -2,7 +2,10 @@ package machine -import "device/stm32" +import ( + "device/stm32" + "runtime/interrupt" +) // https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif const ( @@ -61,14 +64,12 @@ var ( UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART1, - IRQVal: stm32.IRQ_USART1, } UART1 = &UART0 ) -//go:export USART1_IRQHandler -func handleUART1() { - UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF))) +func init() { + UART0.interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt) } // SPI pins diff --git a/src/machine/board_circuitplay_express_baremetal.go b/src/machine/board_circuitplay_express_baremetal.go index 446b1790f..e62d1afea 100644 --- a/src/machine/board_circuitplay_express_baremetal.go +++ b/src/machine/board_circuitplay_express_baremetal.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // UART1 on the Circuit Playground Express. var ( @@ -13,9 +16,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.interrupt = interrupt.New(sam.IRQ_SERCOM4, UART1.handleInterrupt) } // I2C on the Circuit Playground Express. diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index 97af7f901..6c21370ab 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -60,9 +63,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt) } // I2C pins diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index 3238d2037..cbbbadd49 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -4,6 +4,7 @@ package machine import ( "device/sam" + "runtime/interrupt" ) // used to reset into bootloader @@ -62,9 +63,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.interrupt = interrupt.New(sam.IRQ_SERCOM1, UART1.handleInterrupt) } // I2C pins diff --git a/src/machine/board_nucleof103rb.go b/src/machine/board_nucleof103rb.go index 1186ce2a6..e5aa21508 100644 --- a/src/machine/board_nucleof103rb.go +++ b/src/machine/board_nucleof103rb.go @@ -2,7 +2,10 @@ package machine -import "device/stm32" +import ( + "device/stm32" + "runtime/interrupt" +) const ( PA0 = portA + 0 @@ -100,14 +103,12 @@ var ( UART0 = UART{ Buffer: NewRingBuffer(), Bus: stm32.USART2, - IRQVal: stm32.IRQ_USART2, } UART2 = &UART0 ) -//go:export USART2_IRQHandler -func handleUART2() { - UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF))) +func init() { + UART0.interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt) } // SPI pins diff --git a/src/machine/board_trinket.go b/src/machine/board_trinket.go index f2c62160e..fc54e2e3a 100644 --- a/src/machine/board_trinket.go +++ b/src/machine/board_trinket.go @@ -2,7 +2,10 @@ package machine -import "device/sam" +import ( + "device/sam" + "runtime/interrupt" +) // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -51,9 +54,8 @@ var ( } ) -//go:export SERCOM1_IRQHandler -func handleUART1() { - defaultUART1Handler() +func init() { + UART1.interrupt = interrupt.New(sam.IRQ_SERCOM0, UART1.handleInterrupt) } // SPI pins diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 8eb0eff68..e6b94db27 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -293,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 ( @@ -402,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 } @@ -433,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. diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 79a5261c3..6daa4068e 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -11,6 +11,7 @@ import ( "device/arm" "device/sam" "errors" + "runtime/interrupt" "unsafe" ) @@ -1402,11 +1403,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() { @@ -1452,27 +1453,7 @@ func handlePadCalibration() { sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } -//go:export USB_OTHER_IRQHandler -func handleUSBOther() { - handleUSBIRQ() -} - -//go:export USB_SOF_HSOF_IRQHandler -func handleUSBSOFHSOF() { - handleUSBIRQ() -} - -//go:export USB_TRCPT0_IRQHandler -func handleUSBTRCPT0() { - handleUSBIRQ() -} - -//go:export USB_TRCPT1_IRQHandler -func handleUSBTRCPT1() { - handleUSBIRQ() -} - -func handleUSBIRQ() { +func handleUSBIRQ(interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) diff --git a/src/machine/machine_stm32f103xx.go b/src/machine/machine_stm32f103xx.go index 5e77712f0..fb3bd94fa 100644 --- a/src/machine/machine_stm32f103xx.go +++ b/src/machine/machine_stm32f103xx.go @@ -5,9 +5,9 @@ package machine // Peripheral abstraction layer for the stm32. import ( - "device/arm" "device/stm32" "errors" + "runtime/interrupt" ) func CPUFrequency() uint32 { @@ -111,9 +111,9 @@ func (p Pin) Get() bool { // UART type UART struct { - Buffer *RingBuffer - Bus *stm32.USART_Type - IRQVal uint32 + Buffer *RingBuffer + Bus *stm32.USART_Type + interrupt interrupt.Interrupt } // Configure the UART. @@ -155,8 +155,8 @@ func (uart UART) Configure(config UARTConfig) { uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) // Enable RX IRQ - arm.SetPriority(uart.IRQVal, 0xc0) - arm.EnableIRQ(uart.IRQVal) + uart.interrupt.SetPriority(0xc0) + uart.interrupt.Enable() } // SetBaudRate sets the communication speed for the UART. @@ -182,6 +182,12 @@ func (uart UART) WriteByte(c byte) error { return nil } +// handleInterrupt should be called from the appropriate interrupt handler for +// this UART instance. +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + uart.Receive(byte((uart.Bus.DR.Get() & 0xFF))) +} + // SPI on the STM32. type SPI struct { Bus *stm32.SPI_Type diff --git a/src/machine/machine_stm32f407.go b/src/machine/machine_stm32f407.go index 54c0190d8..c75e38771 100644 --- a/src/machine/machine_stm32f407.go +++ b/src/machine/machine_stm32f407.go @@ -5,8 +5,8 @@ package machine // Peripheral abstraction layer for the stm32. import ( - "device/arm" "device/stm32" + "runtime/interrupt" ) func CPUFrequency() uint32 { @@ -203,8 +203,11 @@ func (uart UART) Configure(config UARTConfig) { stm32.USART2.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) // Enable RX IRQ. - arm.SetPriority(stm32.IRQ_USART2, 0xc0) - arm.EnableIRQ(stm32.IRQ_USART2) + intr := interrupt.New(stm32.IRQ_USART2, func(interrupt.Interrupt) { + UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF))) + }) + intr.SetPriority(0xc0) + intr.Enable() } // WriteByte writes a byte of data to the UART. @@ -215,8 +218,3 @@ func (uart UART) WriteByte(c byte) error { } return nil } - -//go:export USART2_IRQHandler -func handleUSART2() { - UART1.Receive(byte((stm32.USART2.DR.Get() & 0xFF))) -} diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go index 6999c8ec3..6d853d3d3 100644 --- a/src/runtime/interrupt/interrupt.go +++ b/src/runtime/interrupt/interrupt.go @@ -23,3 +23,8 @@ func New(id int, handler func(Interrupt)) Interrupt // 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 + +type handle struct { + handler func(Interrupt) + Interrupt +} diff --git a/src/runtime/runtime_stm32f103xx.go b/src/runtime/runtime_stm32f103xx.go index 397e3c92f..8c29c264b 100644 --- a/src/runtime/runtime_stm32f103xx.go +++ b/src/runtime/runtime_stm32f103xx.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/stm32" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -101,8 +102,9 @@ func initRTC() { func initTIM() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) - arm.SetPriority(stm32.IRQ_TIM3, 0xc3) - arm.EnableIRQ(stm32.IRQ_TIM3) + intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3) + intr.SetPriority(0xc3) + intr.Enable() } const asyncScheduler = false @@ -186,8 +188,7 @@ func timerSleep(ticks uint32) { } } -//go:export TIM3_IRQHandler -func handleTIM3() { +func handleTIM3(interrupt.Interrupt) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { // Disable the timer. stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go index 270e1b8a8..fd1be5b43 100644 --- a/src/runtime/runtime_stm32f407.go +++ b/src/runtime/runtime_stm32f407.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/stm32" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -121,8 +122,9 @@ var timerWakeup volatile.Register8 func initTIM3() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) - arm.SetPriority(stm32.IRQ_TIM3, 0xc3) - arm.EnableIRQ(stm32.IRQ_TIM3) + intr := interrupt.New(stm32.IRQ_TIM3, handleTIM3) + intr.SetPriority(0xc3) + intr.Enable() } // Enable the TIM7 clock.(tick count) @@ -139,8 +141,9 @@ func initTIM7() { // Enable the timer. stm32.TIM7.CR1.SetBits(stm32.TIM_CR1_CEN) - arm.SetPriority(stm32.IRQ_TIM7, 0xc1) - arm.EnableIRQ(stm32.IRQ_TIM7) + intr := interrupt.New(stm32.IRQ_TIM7, handleTIM7) + intr.SetPriority(0xc1) + intr.Enable() } const asyncScheduler = false @@ -183,8 +186,7 @@ func timerSleep(ticks uint32) { } } -//go:export TIM3_IRQHandler -func handleTIM3() { +func handleTIM3(interrupt.Interrupt) { if stm32.TIM3.SR.HasBits(stm32.TIM_SR_UIF) { // Disable the timer. stm32.TIM3.CR1.ClearBits(stm32.TIM_CR1_CEN) @@ -197,8 +199,7 @@ func handleTIM3() { } } -//go:export TIM7_IRQHandler -func handleTIM7() { +func handleTIM7(interrupt.Interrupt) { if stm32.TIM7.SR.HasBits(stm32.TIM_SR_UIF) { // clear the update flag stm32.TIM7.SR.ClearBits(stm32.TIM_SR_UIF) diff --git a/transform/interrupt.go b/transform/interrupt.go index 87869af5a..cbac131ab 100644 --- a/transform/interrupt.go +++ b/transform/interrupt.go @@ -47,52 +47,43 @@ func LowerInterruptRegistrations(mod llvm.Module) []error { call.EraseFromParentAsInstruction() } - create := mod.NamedFunction("runtime/interrupt.New") - if create.IsNil() { - // No interrupt handlers to create. - return nil - } - ctx := mod.Context() nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0)) builder := ctx.NewBuilder() defer builder.Dispose() - var dibuilder *llvm.DIBuilder - // Create a function type with the signature of an interrupt handler. fnType := llvm.FunctionType(ctx.VoidType(), nil, false) - for _, call := range getUses(create) { - if call.IsACallInst().IsNil() { - errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.New")) - continue - } - if call.OperandsCount() != 6 { - // 6 params: - // * 1 for the IRQ number - // * 2 for the function - // * 2 extra: context and parentHandle - // * one more for the called value? - errs = append(errs, errorAt(call, fmt.Sprintf("unexpected interrupt.New signature, expected 6 operands, got %d", call.OperandsCount()))) - continue - } - num := call.Operand(0) - if num.IsAConstant().IsNil() { - errs = append(errs, errorAt(call, "non-constant interrupt number")) + handleType := mod.GetTypeByName("runtime/interrupt.handle") + if handleType.IsNil() { + // Nothing to do here. + return errs + } + handlePtrType := llvm.PointerType(handleType, 0) + var handlers []llvm.Value + for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { + if global.Type() != handlePtrType { continue } + handlers = append(handlers, global) + } + + for _, global := range handlers { + initializer := global.Initializer() + num := llvm.ConstExtractValue(initializer, []uint32{1, 0}) name := handlerNames[num.SExtValue()] + if name == "" { - errs = append(errs, errorAt(call, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) + errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) continue } // Create the func value. - handlerContext := call.Operand(1) - handlerFuncPtr := call.Operand(2) + handlerContext := llvm.ConstExtractValue(initializer, []uint32{0, 0}) + handlerFuncPtr := llvm.ConstExtractValue(initializer, []uint32{0, 1}) if isFunctionLocal(handlerContext) || isFunctionLocal(handlerFuncPtr) { - errs = append(errs, errorAt(call, "func value must be constant")) + errs = append(errs, errorAt(global, "func value must be constant")) continue } if !handlerFuncPtr.IsAConstantExpr().IsNil() && handlerFuncPtr.Opcode() == llvm.PtrToInt { @@ -100,23 +91,23 @@ func LowerInterruptRegistrations(mod llvm.Module) []error { // switch statement. global := handlerFuncPtr.Operand(0) if global.IsAGlobalValue().IsNil() { - errs = append(errs, errorAt(call, "internal error: expected a global for func lowering")) + 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(call, "internal error: func lowering global has unexpected type")) + 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(call, "internal error: func lowering global has unexpected func ptr type")) + 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(call, "internal error: unexpected LLVM types in func value")) + errs = append(errs, errorAt(global, "internal error: unexpected LLVM types in func value")) continue } @@ -129,14 +120,14 @@ func LowerInterruptRegistrations(mod llvm.Module) []error { // Don't bother with a precise error message (listing the // previsous location) because this should not normally happen // anyway. - errs = append(errs, errorAt(call, name+" redeclared with a different signature")) + errs = append(errs, errorAt(global, name+" redeclared with a different signature")) continue - } else if fn.IsDeclaration() { + } 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.Operand(0) == num && firstInst.Operand(1) != handlerContext { + 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 @@ -147,7 +138,7 @@ func LowerInterruptRegistrations(mod llvm.Module) []error { if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() } - errs = append(errs, errorAt(call, errValue)) + errs = append(errs, errorAt(global, errValue)) continue } @@ -161,61 +152,33 @@ func LowerInterruptRegistrations(mod llvm.Module) []error { fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL } - // Attach a proper debug location to this function. - // Use the location of the call instruction, because as far as the user - // is concerned that is where the interrupt is defined. This is - // especially relevant for multiple definition errors, where you'd - // really want the previous interrupt.New call location to be used for - // easy debugging. - loc := call.InstructionDebugLoc() - if !loc.IsNil() { - if dibuilder == nil { - dibuilder = llvm.NewDIBuilder(mod) - defer func() { - dibuilder.Finalize() - dibuilder.Destroy() - }() - // Must create a new compile unit for some reason. - dibuilder.CreateCompileUnit(llvm.DICompileUnit{ - File: "", - Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?) - Producer: "TinyGo", - Optimized: true, - }) - } - - // Attach debug info to the function. - file := loc.LocationScope().ScopeFile() - diFnType := dibuilder.CreateSubroutineType(llvm.DISubroutineType{ - File: file, - }) - difunc := dibuilder.CreateFunction(file, llvm.DIFunction{ - Name: name, - LinkageName: name, - File: file, - Line: int(loc.LocationLine()), - Type: diFnType, - LocalToUnit: false, - IsDefinition: true, - ScopeLine: 0, - Flags: llvm.FlagPrototyped, - Optimized: true, - }) - fn.SetSubprogram(difunc) - - // Use the same location inside the thunk. - builder.SetCurrentDebugLocation(loc.LocationLine(), loc.LocationColumn(), difunc, llvm.Metadata{}) - - } - // Fill the function declaration with the forwarding call. builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "") builder.CreateRetVoid() - // Replace the function call. - interruptValue := llvm.ConstNamedStruct(mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num}) - call.ReplaceAllUsesWith(interruptValue) + 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) + } + 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 }