interrupt.go raw

   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