Skip to content

Commit edcece3

Browse files
aykevldeadprogram
authored andcommitted
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 tinygo-org#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.
1 parent 30bbdd5 commit edcece3

20 files changed

+303
-399
lines changed

compiler/compiler.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,17 @@ func (b *builder) createFunction() {
846846
b.llvmFn.AddFunctionAttr(noinline)
847847
}
848848

849+
if b.info.interrupt {
850+
// Mark this function as an interrupt.
851+
// This is necessary on MCUs that don't push caller saved registers when
852+
// entering an interrupt, such as on AVR.
853+
if strings.HasPrefix(b.Triple, "avr") {
854+
b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("signal", ""))
855+
} else {
856+
b.addError(b.fn.Pos(), "//go:interrupt not supported on this architecture")
857+
}
858+
}
859+
849860
// Add debug info, if needed.
850861
if b.Debug {
851862
if b.fn.Synthetic == "package initializer" {

compiler/func.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,24 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
7878
// value. This may be an expensive operation.
7979
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
8080
context = b.CreateExtractValue(funcValue, 0, "")
81-
llvmSig := b.getRawFuncType(sig)
8281
switch b.FuncImplementation {
8382
case "doubleword":
84-
funcPtr = b.CreateBitCast(b.CreateExtractValue(funcValue, 1, ""), llvmSig, "")
83+
bitcast := b.CreateExtractValue(funcValue, 1, "")
84+
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
85+
funcPtr = bitcast.Operand(0)
86+
return
87+
}
88+
llvmSig := b.getRawFuncType(sig)
89+
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
8590
case "switch":
91+
if !funcValue.IsAConstant().IsNil() {
92+
// If this is a constant func value, the underlying function is
93+
// known and can be returned directly.
94+
funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0)
95+
funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0)
96+
return
97+
}
98+
llvmSig := b.getRawFuncType(sig)
8699
sigGlobal := b.getFuncSignatureID(sig)
87100
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
88101
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")

compiler/interrupt.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
3636
// Fall back to a generic error.
3737
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
3838
}
39+
funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
40+
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
3941

4042
// Create a new global of type runtime/interrupt.handle. Globals of this
4143
// type are lowered in the interrupt lowering pass.
@@ -47,8 +49,9 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
4749
global.SetGlobalConstant(true)
4850
global.SetUnnamedAddr(true)
4951
initializer := llvm.ConstNull(globalLLVMType)
50-
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
51-
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
52+
initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0})
53+
initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1})
54+
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
5255
global.SetInitializer(initializer)
5356

5457
// Add debug info to the interrupt global.

compiler/symbol.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type functionInfo struct {
2626
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
2727
section string // go:section - object file section name
2828
exported bool // go:export, CGo
29+
interrupt bool // go:interrupt
2930
nobounds bool // go:nobounds
3031
variadic bool // go:variadic (CGo only)
3132
inline inlineType // go:inline
@@ -251,6 +252,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
251252

252253
importName = parts[1]
253254
info.exported = true
255+
case "//go:interrupt":
256+
if hasUnsafeImport(f.Pkg.Pkg) {
257+
info.interrupt = true
258+
}
254259
case "//go:wasm-module":
255260
// Alternative comment for setting the import module.
256261
if len(parts) != 2 {

src/machine/machine_gameboyadvance.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,28 @@ package machine
44

55
import (
66
"image/color"
7+
"runtime/interrupt"
78
"runtime/volatile"
89
"unsafe"
910
)
1011

1112
// Interrupt numbers as used on the GameBoy Advance. Register them with
1213
// runtime/interrupt.New.
1314
const (
14-
IRQ_VBLANK = 0
15-
IRQ_HBLANK = 1
16-
IRQ_VCOUNT = 2
17-
IRQ_TIMER0 = 3
18-
IRQ_TIMER1 = 4
19-
IRQ_TIMER2 = 5
20-
IRQ_TIMER3 = 6
21-
IRQ_COM = 7
22-
IRQ_DMA0 = 8
23-
IRQ_DMA1 = 9
24-
IRQ_DMA2 = 10
25-
IRQ_DMA3 = 11
26-
IRQ_KEYPAD = 12
27-
IRQ_GAMEPAK = 13
15+
IRQ_VBLANK = interrupt.IRQ_VBLANK
16+
IRQ_HBLANK = interrupt.IRQ_HBLANK
17+
IRQ_VCOUNT = interrupt.IRQ_VCOUNT
18+
IRQ_TIMER0 = interrupt.IRQ_TIMER0
19+
IRQ_TIMER1 = interrupt.IRQ_TIMER1
20+
IRQ_TIMER2 = interrupt.IRQ_TIMER2
21+
IRQ_TIMER3 = interrupt.IRQ_TIMER3
22+
IRQ_COM = interrupt.IRQ_COM
23+
IRQ_DMA0 = interrupt.IRQ_DMA0
24+
IRQ_DMA1 = interrupt.IRQ_DMA1
25+
IRQ_DMA2 = interrupt.IRQ_DMA2
26+
IRQ_DMA3 = interrupt.IRQ_DMA3
27+
IRQ_KEYPAD = interrupt.IRQ_KEYPAD
28+
IRQ_GAMEPAK = interrupt.IRQ_GAMEPAK
2829
)
2930

3031
// Make it easier to directly write to I/O RAM.

src/runtime/interrupt/interrupt.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// to define interrupts and to enable/disable them.
33
package interrupt
44

5+
import "unsafe"
6+
57
// Interrupt provides direct access to hardware interrupts. You can configure
68
// this interrupt through this interface.
79
//
@@ -28,6 +30,7 @@ func New(id int, handler func(Interrupt)) Interrupt
2830
// individually be enabled/disabled, the compiler should create a pseudo-call
2931
// (like runtime/interrupt.use()) that keeps the interrupt alive.
3032
type handle struct {
31-
handler func(Interrupt)
33+
context unsafe.Pointer
34+
funcPtr uintptr
3235
Interrupt
3336
}

src/runtime/interrupt/interrupt_esp32c3.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func handleInterrupt() {
7474
riscv.MSTATUS.SetBits(0x8)
7575

7676
// Call registered interrupt handler(s)
77-
callInterruptHandler(int(interruptNumber))
77+
esp.HandleInterrupt(int(interruptNumber))
7878

7979
// disable CPU interrupts
8080
riscv.MSTATUS.ClearBits(0x8)
@@ -107,8 +107,3 @@ func handleException(mcause uintptr) {
107107
riscv.Asm("wfi")
108108
}
109109
}
110-
111-
// callInterruptHandler is a compiler-generated function that calls the
112-
// appropriate interrupt handler for the given interrupt ID.
113-
//go:linkname callInterruptHandler runtime.callInterruptHandler
114-
func callInterruptHandler(id int)

src/runtime/interrupt/interrupt_gameboyadvance.go

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ import (
77
"unsafe"
88
)
99

10+
const (
11+
IRQ_VBLANK = 0
12+
IRQ_HBLANK = 1
13+
IRQ_VCOUNT = 2
14+
IRQ_TIMER0 = 3
15+
IRQ_TIMER1 = 4
16+
IRQ_TIMER2 = 5
17+
IRQ_TIMER3 = 6
18+
IRQ_COM = 7
19+
IRQ_DMA0 = 8
20+
IRQ_DMA1 = 9
21+
IRQ_DMA2 = 10
22+
IRQ_DMA3 = 11
23+
IRQ_KEYPAD = 12
24+
IRQ_GAMEPAK = 13
25+
)
26+
1027
var (
1128
regInterruptEnable = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000200)))
1229
regInterruptRequestFlags = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000202)))
@@ -30,10 +47,44 @@ func handleInterrupt() {
3047
}
3148
}
3249

33-
// callInterruptHandler is a compiler-generated function that calls the
34-
// appropriate interrupt handler for the given interrupt ID.
35-
//go:linkname callInterruptHandler runtime.callInterruptHandler
36-
func callInterruptHandler(id int)
50+
// Pseudo function call that is replaced by the compiler with the actual
51+
// functions registered through interrupt.New. If there are none, calls will be
52+
// replaced with 'unreachablecalls will be replaced with 'unreachable'.
53+
//go:linkname callHandlers runtime/interrupt.callHandlers
54+
func callHandlers(num int)
55+
56+
func callInterruptHandler(id int) {
57+
switch id {
58+
case IRQ_VBLANK:
59+
callHandlers(IRQ_VBLANK)
60+
case IRQ_HBLANK:
61+
callHandlers(IRQ_HBLANK)
62+
case IRQ_VCOUNT:
63+
callHandlers(IRQ_VCOUNT)
64+
case IRQ_TIMER0:
65+
callHandlers(IRQ_TIMER0)
66+
case IRQ_TIMER1:
67+
callHandlers(IRQ_TIMER1)
68+
case IRQ_TIMER2:
69+
callHandlers(IRQ_TIMER2)
70+
case IRQ_TIMER3:
71+
callHandlers(IRQ_TIMER3)
72+
case IRQ_COM:
73+
callHandlers(IRQ_COM)
74+
case IRQ_DMA0:
75+
callHandlers(IRQ_DMA0)
76+
case IRQ_DMA1:
77+
callHandlers(IRQ_DMA1)
78+
case IRQ_DMA2:
79+
callHandlers(IRQ_DMA2)
80+
case IRQ_DMA3:
81+
callHandlers(IRQ_DMA3)
82+
case IRQ_KEYPAD:
83+
callHandlers(IRQ_KEYPAD)
84+
case IRQ_GAMEPAK:
85+
callHandlers(IRQ_GAMEPAK)
86+
}
87+
}
3788

3889
// State represents the previous global interrupt state.
3990
type State uint8

src/runtime/interrupt/interrupt_hwvector.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/runtime/runtime_fe310.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func handleInterrupt() {
6565
// Claim this interrupt.
6666
id := sifive.PLIC.CLAIM.Get()
6767
// Call the interrupt handler, if any is registered for this ID.
68-
callInterruptHandler(int(id))
68+
sifive.HandleInterrupt(int(id))
6969
// Complete this interrupt.
7070
sifive.PLIC.CLAIM.Set(id)
7171
}
@@ -147,7 +147,3 @@ func handleException(code uint) {
147147
println()
148148
abort()
149149
}
150-
151-
// callInterruptHandler is a compiler-generated function that calls the
152-
// appropriate interrupt handler for the given interrupt ID.
153-
func callInterruptHandler(id int)

0 commit comments

Comments
 (0)