
This commit switches from the previous behavior of compiling the whole program at once, to compiling every package in parallel and linking the LLVM bitcode files together for further whole-program optimization. This is a small performance win, but it has several advantages in the future: - There are many more things that can be done per package in parallel, avoiding the bottleneck at the end of the compiler phase. This should speed up the compiler futher. - This change is a necessary step towards a non-LTO build mode for fast incremental builds that only rebuild the changed package, when compiler speed is more important than binary size. - This change refactors the compiler in such a way that it will be easier to inspect the IR for one package only. Inspecting this IR will be very helpful for compiler developers.
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.SetVisibility(llvm.HiddenVisibility)
|
|
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
|
|
}
|