
Moving settings to a separate config struct has two benefits: - It decouples the compiler a bit from other packages, most importantly the compileopts package. Decoupling is generally a good thing. - Perhaps more importantly, it precisely specifies which settings are used while compiling and affect the resulting LLVM module. This will be necessary for caching the LLVM module. While it would have been possible to cache without this refactor, it would have been very easy to miss a setting and thus let the compiler work with invalid/stale data.
92 строки
3,7 КиБ
Go
92 строки
3,7 КиБ
Go
package compiler
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/ssa"
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
|
|
// will be lowered to a real interrupt during interrupt lowering.
|
|
//
|
|
// This two-stage approach allows unused interrupts to be optimized away if
|
|
// necessary.
|
|
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
|
|
// Get the interrupt number, which must be a compile-time constant.
|
|
id, ok := instr.Args[0].(*ssa.Const)
|
|
if !ok {
|
|
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
|
|
}
|
|
|
|
// Get the func value, which also must be a compile time constant.
|
|
// Note that bound functions are allowed if the function has a pointer
|
|
// receiver and is a global. This is rather strict but still allows for
|
|
// idiomatic Go code.
|
|
funcValue := b.getValue(instr.Args[1])
|
|
if funcValue.IsAConstant().IsNil() {
|
|
// Try to determine the cause of the non-constantness for a nice error
|
|
// message.
|
|
switch instr.Args[1].(type) {
|
|
case *ssa.MakeClosure:
|
|
// This may also be a bound method.
|
|
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
|
}
|
|
// Fall back to a generic error.
|
|
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
|
|
}
|
|
|
|
// Create a new global of type runtime/interrupt.handle. Globals of this
|
|
// type are lowered in the interrupt lowering pass.
|
|
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
|
globalLLVMType := b.getLLVMType(globalType)
|
|
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
|
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
|
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
|
|
}
|
|
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
|
global.SetLinkage(llvm.PrivateLinkage)
|
|
global.SetGlobalConstant(true)
|
|
global.SetUnnamedAddr(true)
|
|
initializer := llvm.ConstNull(globalLLVMType)
|
|
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
|
|
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
|
|
global.SetInitializer(initializer)
|
|
|
|
// Add debug info to the interrupt global.
|
|
if b.Debug {
|
|
pos := b.program.Fset.Position(instr.Pos())
|
|
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
|
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
|
LinkageName: globalName,
|
|
File: b.getDIFile(pos.Filename),
|
|
Line: pos.Line,
|
|
Type: b.getDIType(globalType),
|
|
Expr: b.dibuilder.CreateExpression(nil),
|
|
LocalToUnit: false,
|
|
})
|
|
global.AddMetadata(0, diglobal)
|
|
}
|
|
|
|
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
|
|
// member of type int.
|
|
num := llvm.ConstPtrToInt(global, b.intType)
|
|
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
|
|
|
// Add dummy "use" call for AVR, because interrupts may be used even though
|
|
// they are never referenced again. This is unlike Cortex-M or the RISC-V
|
|
// PLIC where each interrupt must be enabled using the interrupt number, and
|
|
// thus keeps the Interrupt object alive.
|
|
// This call is removed during interrupt lowering.
|
|
if strings.HasPrefix(b.Triple, "avr") {
|
|
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
|
if useFn.IsNil() {
|
|
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
|
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
|
|
}
|
|
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
|
|
}
|
|
|
|
return interrupt, nil
|
|
}
|