1 package transform
2 3 import (
4 "fmt"
5 "strings"
6 7 "tinygo.org/x/go-llvm"
8 )
9 10 // LowerInterrupts creates interrupt handlers for the interrupts created by
11 // runtime/interrupt.New.
12 //
13 // The operation is as follows. The compiler creates the following during IR
14 // generation:
15 // - calls to runtime/interrupt.callHandlers with an interrupt number.
16 // - runtime/interrupt.handle objects that store the (constant) interrupt ID and
17 // interrupt handler func value.
18 //
19 // This pass then replaces those callHandlers calls with calls to the actual
20 // interrupt handlers. If there are no interrupt handlers for the given call,
21 // the interrupt handler is removed. For hardware vectoring, that means that the
22 // entire function is removed. For software vectoring, that means that the call
23 // is replaced with an 'unreachable' instruction.
24 // This might seem like it causes extra overhead, but in fact inlining and const
25 // propagation will eliminate most if not all of that.
26 func LowerInterrupts(mod llvm.Module) []error {
27 var errs []error
28 29 ctx := mod.Context()
30 builder := ctx.NewBuilder()
31 defer builder.Dispose()
32 33 // Collect a map of interrupt handle objects. The fact that they still
34 // exist in the IR indicates that they could not be optimized away,
35 // therefore we need to make real interrupt handlers for them.
36 handleMap := map[int64][]llvm.Value{}
37 handleType := mod.GetTypeByName("runtime/interrupt.handle")
38 if !handleType.IsNil() {
39 for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
40 if global.GlobalValueType() != handleType {
41 continue
42 }
43 44 // Get the interrupt number from the initializer
45 initializer := global.Initializer()
46 interrupt := builder.CreateExtractValue(initializer, 2, "")
47 num := builder.CreateExtractValue(interrupt, 0, "").SExtValue()
48 pkg := packageFromInterruptHandle(global)
49 50 handles, exists := handleMap[num]
51 52 // If there is an existing interrupt handler, ensure it is in the same package
53 // as the new one. This is to prevent any assumptions in code that a single
54 // compiler pass can see all packages to chain interrupt handlers. When packages are
55 // compiled to separate object files, the linker should spot the duplicate symbols
56 // for the wrapper function, failing the build.
57 if exists && packageFromInterruptHandle(handles[0]) != pkg {
58 errs = append(errs, errorAt(global,
59 fmt.Sprintf("handlers for interrupt %d in multiple packages: %s and %s",
60 num, pkg, packageFromInterruptHandle(handles[0]))))
61 continue
62 }
63 64 handleMap[num] = append(handles, global)
65 }
66 }
67 68 // Discover interrupts. The runtime/interrupt.callHandlers call is a
69 // compiler intrinsic that is replaced with the handlers for the given
70 // function.
71 for _, call := range getUses(mod.NamedFunction("runtime/interrupt.callHandlers")) {
72 if call.IsACallInst().IsNil() {
73 errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.callHandlers?"))
74 continue
75 }
76 77 num := call.Operand(0)
78 if num.IsAConstantInt().IsNil() {
79 errs = append(errs, errorAt(call, "non-constant interrupt number?"))
80 call.InstructionParent().Parent().Dump()
81 continue
82 }
83 84 handlers := handleMap[num.SExtValue()]
85 if len(handlers) != 0 {
86 // This interrupt has at least one handler.
87 // Replace the callHandlers call with (possibly multiple) calls to
88 // these handlers.
89 builder.SetInsertPointBefore(call)
90 for _, handler := range handlers {
91 initializer := handler.Initializer()
92 context := builder.CreateExtractValue(initializer, 0, "")
93 funcPtr := builder.CreateExtractValue(initializer, 1, "").Operand(0)
94 builder.CreateCall(funcPtr.GlobalValueType(), funcPtr, []llvm.Value{
95 num,
96 context,
97 }, "")
98 }
99 call.EraseFromParentAsInstruction()
100 } else {
101 // No handlers. Remove the call.
102 fn := call.InstructionParent().Parent()
103 if fn.Linkage() == llvm.ExternalLinkage {
104 // Hardware vectoring. Remove the function entirely (redirecting
105 // it to the default handler).
106 fn.ReplaceAllUsesWith(llvm.Undef(fn.Type()))
107 fn.EraseFromParentAsFunction()
108 } else {
109 // Software vectoring. Erase the instruction and replace it with
110 // 'unreachable'.
111 builder.SetInsertPointBefore(call)
112 builder.CreateUnreachable()
113 // Erase all instructions that follow the unreachable
114 // instruction (which is a block terminator).
115 inst := call
116 for !inst.IsNil() {
117 next := llvm.NextInstruction(inst)
118 inst.EraseFromParentAsInstruction()
119 inst = next
120 }
121 }
122 }
123 }
124 125 // Replace all ptrtoint uses of the interrupt handler globals with the real
126 // interrupt ID.
127 // This can now be safely done after interrupts have been lowered, doing it
128 // earlier may result in this interrupt handler being optimized away
129 // entirely (which is not what we want).
130 for num, handlers := range handleMap {
131 for _, handler := range handlers {
132 for _, user := range getUses(handler) {
133 if user.IsAConstantExpr().IsNil() || user.Opcode() != llvm.PtrToInt {
134 errs = append(errs, errorAt(handler, "internal error: expected a ptrtoint"))
135 continue
136 }
137 user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true))
138 }
139 140 // The runtime/interrupt.handle struct can finally be removed.
141 // It would probably be eliminated anyway by a globaldce pass but it's
142 // better to do it now to be sure.
143 handler.EraseFromParentAsGlobal()
144 }
145 }
146 147 // Remove now-useless runtime/interrupt.use calls. These are used for some
148 // platforms like AVR that do not need to enable interrupts to use them, so
149 // need another way to keep them alive.
150 // After interrupts have been lowered, this call is useless and would cause
151 // a linker error so must be removed.
152 for _, call := range getUses(mod.NamedFunction("runtime/interrupt.use")) {
153 if call.IsACallInst().IsNil() {
154 errs = append(errs, errorAt(call, "internal error: expected call to runtime/interrupt.use"))
155 continue
156 }
157 158 call.EraseFromParentAsInstruction()
159 }
160 161 return errs
162 }
163 164 func packageFromInterruptHandle(handle llvm.Value) string {
165 return strings.Split(handle.Name(), "$")[0]
166 }
167