github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/transform/interrupt.go (about) 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/interrput.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 }