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  }