interrupt.go raw

   1  package compiler
   2  
   3  import (
   4  	"strconv"
   5  	"strings"
   6  
   7  	"golang.org/x/tools/go/ssa"
   8  	"tinygo.org/x/go-llvm"
   9  )
  10  
  11  // createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
  12  // will be lowered to a real interrupt during interrupt lowering.
  13  //
  14  // This two-stage approach allows unused interrupts to be optimized away if
  15  // necessary.
  16  func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
  17  	// Get the interrupt number, which must be a compile-time constant.
  18  	id, ok := instr.Args[0].(*ssa.Const)
  19  	if !ok {
  20  		return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
  21  	}
  22  
  23  	// Get the func value, which also must be a compile time constant.
  24  	// Note that bound functions are allowed if the function has a pointer
  25  	// receiver and is a global. This is rather strict but still allows for
  26  	// idiomatic Go code.
  27  	funcValue := b.getValue(instr.Args[1], getPos(instr))
  28  	if funcValue.IsAConstant().IsNil() {
  29  		// Try to determine the cause of the non-constantness for a nice error
  30  		// message.
  31  		switch instr.Args[1].(type) {
  32  		case *ssa.MakeClosure:
  33  			// This may also be a bound method.
  34  			return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
  35  		}
  36  		// Fall back to a generic error.
  37  		return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
  38  	}
  39  	funcRawPtr, funcContext := b.decodeFuncValue(funcValue)
  40  	funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
  41  
  42  	// Create a new global of type runtime/interrupt.handle. Globals of this
  43  	// type are lowered in the interrupt lowering pass.
  44  	// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
  45  	// global has the lower bits unset.
  46  	globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
  47  	globalLLVMType := b.getLLVMType(globalType)
  48  	globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
  49  	global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
  50  	global.SetVisibility(llvm.HiddenVisibility)
  51  	global.SetGlobalConstant(true)
  52  	global.SetUnnamedAddr(true)
  53  	global.SetAlignment(1)
  54  	initializer := llvm.ConstNull(globalLLVMType)
  55  	initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
  56  	initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
  57  	initializer = b.CreateInsertValue(initializer, llvm.ConstNamedStruct(globalLLVMType.StructElementTypes()[2], []llvm.Value{
  58  		llvm.ConstInt(b.intType, uint64(id.Int64()), true),
  59  	}), 2, "")
  60  	global.SetInitializer(initializer)
  61  
  62  	// Add debug info to the interrupt global.
  63  	if b.Debug {
  64  		pos := b.program.Fset.Position(instr.Pos())
  65  		diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
  66  			Name:        "interrupt" + strconv.FormatInt(id.Int64(), 10),
  67  			LinkageName: globalName,
  68  			File:        b.getDIFile(pos.Filename),
  69  			Line:        pos.Line,
  70  			Type:        b.getDIType(globalType),
  71  			Expr:        b.dibuilder.CreateExpression(nil),
  72  			LocalToUnit: false,
  73  		})
  74  		global.AddMetadata(0, diglobal)
  75  	}
  76  
  77  	// Create the runtime/interrupt.Interrupt type. It is a struct with a single
  78  	// member of type int.
  79  	num := llvm.ConstPtrToInt(global, b.intType)
  80  	interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
  81  
  82  	// Add dummy "use" call for AVR, because interrupts may be used even though
  83  	// they are never referenced again. This is unlike Cortex-M or the RISC-V
  84  	// PLIC where each interrupt must be enabled using the interrupt number, and
  85  	// thus keeps the Interrupt object alive.
  86  	// This call is removed during interrupt lowering.
  87  	if strings.HasPrefix(b.Triple, "avr") {
  88  		useFn := b.mod.NamedFunction("runtime/interrupt.use")
  89  		if useFn.IsNil() {
  90  			useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
  91  			useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
  92  		}
  93  		b.CreateCall(useFn.GlobalValueType(), useFn, []llvm.Value{interrupt}, "")
  94  	}
  95  
  96  	return interrupt, nil
  97  }
  98