diff --git a/compiler/compiler.go b/compiler/compiler.go index adafbdee0..28c0ceb69 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error { path = path[len(tinygoPath+"/src/"):] } switch path { - case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite": + case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite": return path default: if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { @@ -828,9 +828,6 @@ func (c *Compiler) parseFunc(frame *Frame) { frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) frame.fn.LLVMFn.SetUnnamedAddr(true) } - if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple(), "avr") { - frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL - } // Some functions have a pragma controlling the inlining level. switch frame.fn.Inline() { diff --git a/compiler/optimizer.go b/compiler/optimizer.go index 17b782e83..d34a2f49f 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -57,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er transform.OptimizeStringToBytes(c.mod) transform.OptimizeAllocs(c.mod) transform.LowerInterfaces(c.mod) + + errs := transform.LowerInterruptRegistrations(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.LowerInterruptRegistrations(c.mod) + if len(errs) > 0 { + return errs + } } if c.VerifyIR() { if errs := c.checkModule(); errs != nil { diff --git a/ir/ir.go b/ir/ir.go index 9d243bae7..7506400df 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -27,14 +27,13 @@ type Program struct { // Function or method. type Function struct { *ssa.Function - LLVMFn llvm.Value - module string // go:wasm-module - linkName string // go:linkname, go:export, go:interrupt - exported bool // go:export - nobounds bool // go:nobounds - flag bool // used by dead code elimination - interrupt bool // go:interrupt - inline InlineType // go:inline + LLVMFn llvm.Value + module string // go:wasm-module + linkName string // go:linkname, go:export + exported bool // go:export + nobounds bool // go:nobounds + flag bool // used by dead code elimination + inline InlineType // go:inline } // Interface type that is at some point used in a type assert (to check whether @@ -243,18 +242,6 @@ func (f *Function) parsePragmas() { f.inline = InlineHint case "//go:noinline": f.inline = InlineNone - case "//go:interrupt": - if len(parts) != 2 { - continue - } - name := parts[1] - if strings.HasSuffix(name, "_vect") { - // AVR vector naming - name = "__vector_" + name[:len(name)-5] - } - f.linkName = name - f.exported = true - f.interrupt = true case "//go:linkname": if len(parts) != 3 || parts[1] != f.Name() { continue @@ -288,14 +275,6 @@ func (f *Function) IsExported() bool { return f.exported || f.CName() != "" } -// Return true for functions annotated with //go:interrupt. The function name is -// already customized in LinkName() to hook up in the interrupt vector. -// -// On some platforms (like AVR), interrupts need a special compiler flag. -func (f *Function) IsInterrupt() bool { - return f.interrupt -} - // Return the inline directive of this function. func (f *Function) Inline() InlineType { return f.inline diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 83c501e52..16395fe42 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -4,6 +4,7 @@ package machine import ( "device/avr" + "runtime/interrupt" "runtime/volatile" ) @@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) { config.BaudRate = 9600 } + // Register the UART interrupt. + interrupt.New(avr.IRQ_USART_RX, func(intr interrupt.Interrupt) { + // Read register to clear it. + data := avr.UDR0.Get() + + // Ensure no error. + if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { + // Put data from UDR register into buffer. + UART0.Receive(byte(data)) + } + }) + // Set baud rate based on prescale formula from // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html // ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) @@ -254,15 +267,3 @@ func (uart UART) WriteByte(c byte) error { avr.UDR0.Set(c) // send char return nil } - -//go:interrupt USART_RX_vect -func handleUSART_RX() { - // Read register to clear it. - data := avr.UDR0.Get() - - // Ensure no error. - if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { - // Put data from UDR register into buffer. - UART0.Receive(byte(data)) - } -} diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index c1bb9e632..8eb0eff68 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -11,6 +11,7 @@ import ( "device/arm" "device/sam" "errors" + "runtime/interrupt" "unsafe" ) @@ -1368,7 +1369,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 +1416,7 @@ func handlePadCalibration() { sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) } -//go:export USB_IRQHandler -func handleUSB() { +func handleUSB(intr interrupt.Interrupt) { // reset all interrupt flags flags := sam.USB_DEVICE.INTFLAG.Get() sam.USB_DEVICE.INTFLAG.Set(flags) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 7e12c8a84..579e97a50 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,9 +3,9 @@ package machine import ( - "device/arm" "device/nrf" "errors" + "runtime/interrupt" ) var ( @@ -88,8 +88,11 @@ 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, func(intr interrupt.Interrupt) { + UART0.handleInterrupt() + }) + intr.SetPriority(0xc0) // low priority + intr.Enable() } // SetBaudRate sets the communication speed for the UART. diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index 6425b2ae2..2c9af2405 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSELRXD.Set(uint32(rx)) } -//go:export UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go index 392e237c3..b2181770a 100644 --- a/src/machine/machine_nrf52.go +++ b/src/machine/machine_nrf52.go @@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSELRXD.Set(uint32(rx)) } -//go:export UARTE0_UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSELSCL.Set(uint32(scl)) i2c.Bus.PSELSDA.Set(uint32(sda)) diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go index e192567dd..9e50fb195 100644 --- a/src/machine/machine_nrf52840.go +++ b/src/machine/machine_nrf52840.go @@ -25,11 +25,6 @@ func (uart UART) setPins(tx, rx Pin) { nrf.UART0.PSEL.RXD.Set(uint32(rx)) } -//go:export UARTE0_UART0_IRQHandler -func handleUART0() { - UART0.handleInterrupt() -} - func (i2c I2C) setPins(scl, sda Pin) { i2c.Bus.PSEL.SCL.Set(uint32(scl)) i2c.Bus.PSEL.SDA.Set(uint32(sda)) diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go new file mode 100644 index 000000000..6999c8ec3 --- /dev/null +++ b/src/runtime/interrupt/interrupt.go @@ -0,0 +1,25 @@ +// 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 diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go new file mode 100644 index 000000000..5354be9e0 --- /dev/null +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -0,0 +1,23 @@ +// +build cortexm + +package interrupt + +import ( + "device/arm" +) + +// Enable enables this interrupt. Right after calling this function, the +// interrupt may be invoked if it was already pending. +func (irq Interrupt) Enable() { + arm.EnableIRQ(uint32(irq.num)) +} + +// SetPriority sets the interrupt priority for this interrupt. A lower number +// means a higher priority. Additionally, most hardware doesn't implement all +// priority bits (only the uppoer bits). +// +// Examples: 0xff (lowest priority), 0xc0 (low priority), 0x00 (highest possible +// priority). +func (irq Interrupt) SetPriority(priority uint8) { + arm.SetPriority(uint32(irq.num), uint32(priority)) +} diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index dbbf8f142..440cd148e 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/sam" "machine" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -214,8 +215,14 @@ func initRTC() { sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE) waitForSync() - arm.SetPriority(sam.IRQ_RTC, 0xc0) - arm.EnableIRQ(sam.IRQ_RTC) + intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) { + // disable IRQ for CMP0 compare + sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0) + + timerWakeup.Set(1) + }) + intr.SetPriority(0xc0) + intr.Enable() } func waitForSync() { @@ -286,14 +293,6 @@ func timerSleep(ticks uint32) { } } -//go:export RTC_IRQHandler -func handleRTC() { - // disable IRQ for CMP0 compare - sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0) - - timerWakeup.Set(1) -} - func initUSBClock() { // Turn on clock for USB sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_) diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index c289437d2..50d3e5f2b 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -6,6 +6,7 @@ import ( "device/arm" "device/nrf" "machine" + "runtime/interrupt" "runtime/volatile" ) @@ -43,8 +44,13 @@ func initLFCLK() { func initRTC() { nrf.RTC1.TASKS_START.Set(1) - arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority - arm.EnableIRQ(nrf.IRQ_RTC1) + intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) { + nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0) + nrf.RTC1.EVENTS_COMPARE[0].Set(0) + rtc_wakeup.Set(1) + }) + intr.SetPriority(0xc0) // low priority + intr.Enable() } func putchar(c byte) { @@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) { arm.Asm("wfi") } } - -//go:export RTC1_IRQHandler -func handleRTC1() { - nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0) - nrf.RTC1.EVENTS_COMPARE[0].Set(0) - rtc_wakeup.Set(1) -} diff --git a/tools/gen-device-avr/gen-device-avr.go b/tools/gen-device-avr/gen-device-avr.go index ac4c8b996..8f9298a21 100755 --- a/tools/gen-device-avr/gen-device-avr.go +++ b/tools/gen-device-avr/gen-device-avr.go @@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error { package {{.pkgName}} import ( + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -277,6 +278,12 @@ const ({{range .interrupts}} IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. ) +// Map interrupt numbers to function names. +// These aren't real calls, they're removed by the compiler. +var ({{range .interrupts}} + _ = interrupt.Register(IRQ_{{.Name}}, "__vector_{{.Name}}"){{end}} +) + // Peripherals. var ({{range .peripherals}} // {{.Caption}} diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index cef0365d5..f5aebcfe8 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -82,6 +82,7 @@ type Device struct { type interrupt struct { Name string + HandlerName string peripheralIndex int Value int // interrupt number Description string @@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) { groupName := cleanName(periphEl.GroupName) for _, interrupt := range periphEl.Interrupts { - addInterrupt(interrupts, interrupt.Name, interrupt.Index, description) + addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description) // As a convenience, also use the peripheral name as the interrupt // name. Only do that for the nrf for now, as the stm32 .svd files // don't always put interrupts in the correct peripheral... if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") { - addInterrupt(interrupts, periphEl.Name, interrupt.Index, description) + addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description) } } @@ -388,7 +389,7 @@ func readSVD(path, sourceURL string) (*Device, error) { }, nil } -func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) { +func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { // Note: some SVD files like the one for STM32H7x7 contain mistakes. @@ -409,6 +410,7 @@ func addInterrupt(interrupts map[string]*interrupt, name string, index int, desc } else { interrupts[name] = &interrupt{ Name: name, + HandlerName: interruptName + "_IRQHandler", peripheralIndex: len(interrupts), Value: index, Description: description, @@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error { package {{.pkgName}} import ( + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -628,12 +631,18 @@ const ( DEVICE = "{{.metadata.name}}" ) -// Interrupt numbers +// Interrupt numbers. const ({{range .interrupts}} IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}} IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. ) +// Map interrupt numbers to function names. +// These aren't real calls, they're removed by the compiler. +var ({{range .interrupts}} + _ = interrupt.Register(IRQ_{{.Name}}, "{{.HandlerName}}"){{end}} +) + // Peripherals. var ( {{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}} @@ -879,7 +888,7 @@ Default_Handler: num++ } num++ - fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name) + fmt.Fprintf(w, " .long %s\n", intr.HandlerName) } w.WriteString(` diff --git a/transform/errors.go b/transform/errors.go new file mode 100644 index 000000000..226ead28c --- /dev/null +++ b/transform/errors.go @@ -0,0 +1,48 @@ +package transform + +import ( + "go/scanner" + "go/token" + "path/filepath" + + "tinygo.org/x/go-llvm" +) + +// errorAt returns an error value at the location of the value. +// The location information may not be complete as it depends on debug +// information in the IR. +func errorAt(val llvm.Value, msg string) scanner.Error { + return scanner.Error{ + Pos: getPosition(val), + Msg: msg, + } +} + +// getPosition returns the position information for the given value, as far as +// it is available. +func getPosition(val llvm.Value) token.Position { + if !val.IsAInstruction().IsNil() { + loc := val.InstructionDebugLoc() + if loc.IsNil() { + return token.Position{} + } + file := loc.LocationScope().ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.LocationLine()), + Column: int(loc.LocationColumn()), + } + } else if !val.IsAFunction().IsNil() { + loc := val.Subprogram() + if loc.IsNil() { + return token.Position{} + } + file := loc.ScopeFile() + return token.Position{ + Filename: filepath.Join(file.FileDirectory(), file.FileFilename()), + Line: int(loc.SubprogramLine()), + } + } else { + return token.Position{} + } +} diff --git a/transform/interrupt.go b/transform/interrupt.go new file mode 100644 index 000000000..87869af5a --- /dev/null +++ b/transform/interrupt.go @@ -0,0 +1,221 @@ +package transform + +import ( + "fmt" + "strings" + + "tinygo.org/x/go-llvm" +) + +func LowerInterruptRegistrations(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() + } + + 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")) + continue + } + name := handlerNames[num.SExtValue()] + if name == "" { + errs = append(errs, errorAt(call, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue()))) + continue + } + + // Create the func value. + handlerContext := call.Operand(1) + handlerFuncPtr := call.Operand(2) + if isFunctionLocal(handlerContext) || isFunctionLocal(handlerFuncPtr) { + errs = append(errs, errorAt(call, "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(call, "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")) + 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")) + 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")) + continue + } + + // Check for an existing 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(call, 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.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(call, errValue)) + continue + } + + // Create the wrapper function. + fn.SetUnnamedAddr(true) + entryBlock := ctx.AddBasicBlock(fn, "entry") + builder.SetInsertPointAtEnd(entryBlock) + + // Set the 'interrupt' flag if needed on this platform. + if strings.HasPrefix(mod.Target(), "avr") { + 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) + call.EraseFromParentAsInstruction() + } + return errs +} diff --git a/transform/llvm.go b/transform/llvm.go index 86dd897e9..c592c1b14 100644 --- a/transform/llvm.go +++ b/transform/llvm.go @@ -89,3 +89,19 @@ func typeHasPointers(t llvm.Type) bool { return false } } + +// isFunctionLocal returns true if (and only if) this value is local to a +// function. That is, it returns true for instructions and parameters, and false +// for constants and globals. +func isFunctionLocal(val llvm.Value) bool { + if !val.IsAConstant().IsNil() { + return false + } + if !val.IsAInstruction().IsNil() { + return true + } + if !val.IsAGlobalValue().IsNil() { + return false + } + panic("unknown value kind") +}