|
|
|
package compiler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/ssa"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
|
|
)
|
|
|
|
|
|
|
|
// createInterruptGlobal 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 (b *builder) createInterruptGlobal(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{}, b.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 := b.getValue(instr.Args[1], getPos(instr))
|
|
|
|
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{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
|
|
|
}
|
|
|
|
// Fall back to a generic error.
|
|
|
|
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
|
|
|
|
}
|
|
|
|
funcRawPtr, funcContext := b.decodeFuncValue(funcValue)
|
transform: refactor interrupt lowering
Instead of doing everything in the interrupt lowering pass, generate
some more code in gen-device to declare interrupt handler functions and
do some work in the compiler so that interrupt lowering becomes a lot
simpler.
This has several benefits:
- Overall code is smaller, in particular the interrupt lowering pass.
- The code should be a bit less "magical" and instead a bit easier to
read. In particular, instead of having a magic
runtime.callInterruptHandler (that is fully written by the interrupt
lowering pass), the runtime calls a generated function like
device/sifive.InterruptHandler where this switch already exists in
code.
- Debug information is improved. This can be helpful during actual
debugging but is also useful for other uses of DWARF debug
information.
For an example on debug information improvement, this is what a
backtrace might look like before this commit:
Breakpoint 1, 0x00000b46 in UART0_IRQHandler ()
(gdb) bt
#0 0x00000b46 in UART0_IRQHandler ()
#1 <signal handler called>
[..etc]
Notice that the debugger doesn't see the source code location where it
has stopped.
After this commit, breaking at the same line might look like this:
Breakpoint 1, (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
200 uart.Receive(byte(nrf.UART0.RXD.Get()))
(gdb) bt
#0 (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
#1 UART0_IRQHandler () at /home/ayke/src/github.com/tinygo-org/tinygo/src/device/nrf/nrf51.go:176
#2 <signal handler called>
[..etc]
By now, the debugger sees an actual source location for UART0_IRQHandler
(in the generated file) and an inlined function.
3 years ago
|
|
|
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
|
|
|
|
|
|
|
|
// Create a new global of type runtime/interrupt.handle. Globals of this
|
|
|
|
// type are lowered in the interrupt lowering pass.
|
|
|
|
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
|
|
|
globalLLVMType := b.getLLVMType(globalType)
|
|
|
|
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
|
|
|
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
|
|
|
global.SetVisibility(llvm.HiddenVisibility)
|
|
|
|
global.SetGlobalConstant(true)
|
|
|
|
global.SetUnnamedAddr(true)
|
|
|
|
initializer := llvm.ConstNull(globalLLVMType)
|
|
|
|
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
|
|
|
|
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
|
|
|
|
initializer = b.CreateInsertValue(initializer, llvm.ConstNamedStruct(globalLLVMType.StructElementTypes()[2], []llvm.Value{
|
|
|
|
llvm.ConstInt(b.intType, uint64(id.Int64()), true),
|
|
|
|
}), 2, "")
|
|
|
|
global.SetInitializer(initializer)
|
|
|
|
|
|
|
|
// Add debug info to the interrupt global.
|
|
|
|
if b.Debug {
|
|
|
|
pos := b.program.Fset.Position(instr.Pos())
|
|
|
|
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
|
|
|
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
|
|
|
LinkageName: globalName,
|
|
|
|
File: b.getDIFile(pos.Filename),
|
|
|
|
Line: pos.Line,
|
|
|
|
Type: b.getDIType(globalType),
|
|
|
|
Expr: b.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, b.intType)
|
|
|
|
interrupt := llvm.ConstNamedStruct(b.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(b.Triple, "avr") {
|
|
|
|
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
|
|
|
if useFn.IsNil() {
|
|
|
|
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
|
|
|
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
|
|
|
|
}
|
|
|
|
b.CreateCall(useFn.GlobalValueType(), useFn, []llvm.Value{interrupt}, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
return interrupt, nil
|
|
|
|
}
|