compiler: add location information to the IR checker
Этот коммит содержится в:
родитель
dffb9fbfa7
коммит
5510dec846
5 изменённых файлов: 85 добавлений и 42 удалений
|
@ -33,10 +33,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||||
// Compile Go code to IR.
|
// Compile Go code to IR.
|
||||||
errs := c.Compile(pkgName)
|
errs := c.Compile(pkgName)
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
if len(errs) == 1 {
|
return newMultiError(errs)
|
||||||
return errs[0]
|
|
||||||
}
|
|
||||||
return &MultiError{errs}
|
|
||||||
}
|
}
|
||||||
if config.Options.PrintIR {
|
if config.Options.PrintIR {
|
||||||
fmt.Println("; Generated LLVM IR:")
|
fmt.Println("; Generated LLVM IR:")
|
||||||
|
@ -72,22 +69,23 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||||
|
|
||||||
// Optimization levels here are roughly the same as Clang, but probably not
|
// Optimization levels here are roughly the same as Clang, but probably not
|
||||||
// exactly.
|
// exactly.
|
||||||
|
errs = nil
|
||||||
switch config.Options.Opt {
|
switch config.Options.Opt {
|
||||||
case "none:", "0":
|
case "none:", "0":
|
||||||
err = c.Optimize(0, 0, 0) // -O0
|
errs = c.Optimize(0, 0, 0) // -O0
|
||||||
case "1":
|
case "1":
|
||||||
err = c.Optimize(1, 0, 0) // -O1
|
errs = c.Optimize(1, 0, 0) // -O1
|
||||||
case "2":
|
case "2":
|
||||||
err = c.Optimize(2, 0, 225) // -O2
|
errs = c.Optimize(2, 0, 225) // -O2
|
||||||
case "s":
|
case "s":
|
||||||
err = c.Optimize(2, 1, 225) // -Os
|
errs = c.Optimize(2, 1, 225) // -Os
|
||||||
case "z":
|
case "z":
|
||||||
err = c.Optimize(2, 2, 5) // -Oz, default
|
errs = c.Optimize(2, 2, 5) // -Oz, default
|
||||||
default:
|
default:
|
||||||
err = errors.New("unknown optimization level: -opt=" + config.Options.Opt)
|
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if len(errs) > 0 {
|
||||||
return err
|
return newMultiError(errs)
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("verification failure after LLVM optimization passes")
|
return errors.New("verification failure after LLVM optimization passes")
|
||||||
|
|
|
@ -12,6 +12,20 @@ func (e *MultiError) Error() string {
|
||||||
return e.Errs[0].Error()
|
return e.Errs[0].Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newMultiError returns a *MultiError if there is more than one error, or
|
||||||
|
// returns that error directly when there is only one. Passing an empty slice
|
||||||
|
// will lead to a panic.
|
||||||
|
func newMultiError(errs []error) error {
|
||||||
|
switch len(errs) {
|
||||||
|
case 0:
|
||||||
|
panic("attempted to create empty MultiError")
|
||||||
|
case 1:
|
||||||
|
return errs[0]
|
||||||
|
default:
|
||||||
|
return &MultiError{errs}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||||
// some more information regarding what went wrong while running a command.
|
// some more information regarding what went wrong while running a command.
|
||||||
type commandError struct {
|
type commandError struct {
|
||||||
|
|
|
@ -98,76 +98,75 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
|
||||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
// check value properties
|
// check value properties
|
||||||
if err := c.checkValue(inst, types, specials); err != nil {
|
if err := c.checkValue(inst, types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to validate value of instruction %q: %s", inst.Name(), err.Error())
|
return errorAt(inst, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// check operands
|
// check operands
|
||||||
for i := 0; i < inst.OperandsCount(); i++ {
|
for i := 0; i < inst.OperandsCount(); i++ {
|
||||||
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to validate argument %d of instruction %q: %s", i, inst.Name(), err.Error())
|
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||||
// check basic block value and type
|
// check basic block value and type
|
||||||
|
var errs []error
|
||||||
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to validate value of basic block %s: %s", bb.AsValue().Name(), err.Error())
|
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// check instructions
|
// check instructions
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if err := c.checkInstruction(inst, types, specials); err != nil {
|
if err := c.checkInstruction(inst, types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to validate basic block %q: %s", bb.AsValue().Name(), err.Error())
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||||
// check function value and type
|
// check function value and type
|
||||||
|
var errs []error
|
||||||
if err := c.checkValue(fn, types, specials); err != nil {
|
if err := c.checkValue(fn, types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())
|
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// check basic blocks
|
// check basic blocks
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
if err := c.checkBasicBlock(bb, types, specials); err != nil {
|
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
|
||||||
return fmt.Errorf("failed to validate basic block of function %s: %s", fn.Name(), err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkModule() error {
|
func (c *Compiler) checkModule() []error {
|
||||||
// check for any context mismatches
|
// check for any context mismatches
|
||||||
|
var errs []error
|
||||||
switch {
|
switch {
|
||||||
case c.mod.Context() == c.ctx:
|
case c.mod.Context() == c.ctx:
|
||||||
// this is correct
|
// this is correct
|
||||||
case c.mod.Context() == llvm.GlobalContext():
|
case c.mod.Context() == llvm.GlobalContext():
|
||||||
// somewhere we accidentally used the global context instead of a real context
|
// somewhere we accidentally used the global context instead of a real context
|
||||||
return errors.New("module uses global context")
|
errs = append(errs, errors.New("module uses global context"))
|
||||||
default:
|
default:
|
||||||
// we used some other context by accident
|
// we used some other context by accident
|
||||||
return fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx)
|
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
types := map[llvm.Type]struct{}{}
|
types := map[llvm.Type]struct{}{}
|
||||||
specials := map[llvm.TypeKind]llvm.Type{}
|
specials := map[llvm.TypeKind]llvm.Type{}
|
||||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
if err := c.checkFunction(fn, types, specials); err != nil {
|
errs = append(errs, c.checkFunction(fn, types, specials)...)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||||
if err := c.checkValue(g, types, specials); err != nil {
|
if err := c.checkValue(g, types, specials); err != nil {
|
||||||
return fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())
|
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return errs
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go/scanner"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||||
|
@ -16,3 +20,31 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errorAt returns an error value at the location of the instruction.
|
||||||
|
// The location information may not be complete as it depends on debug
|
||||||
|
// information in the IR.
|
||||||
|
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||||
|
return scanner.Error{
|
||||||
|
Pos: getPosition(inst),
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPosition returns the position information for the given instruction, as
|
||||||
|
// far as it is available.
|
||||||
|
func getPosition(inst llvm.Value) token.Position {
|
||||||
|
if inst.IsAInstruction().IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
loc := inst.InstructionDebugLoc()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.LocationScope().ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.LocationLine()),
|
||||||
|
Column: int(loc.LocationColumn()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
// Run the LLVM optimizer over the module.
|
// Run the LLVM optimizer over the module.
|
||||||
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
|
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
|
||||||
builder := llvm.NewPassManagerBuilder()
|
builder := llvm.NewPassManagerBuilder()
|
||||||
defer builder.Dispose()
|
defer builder.Dispose()
|
||||||
builder.SetOptLevel(optLevel)
|
builder.SetOptLevel(optLevel)
|
||||||
|
@ -25,9 +25,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
|
|
||||||
// run a check of all of our code
|
// run a check of all of our code
|
||||||
if c.VerifyIR() {
|
if c.VerifyIR() {
|
||||||
err := c.checkModule()
|
errs := c.checkModule()
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
return err
|
return errs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
|
|
||||||
err := c.LowerGoroutines()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return []error{err}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Must be run at any optimization level.
|
// Must be run at any optimization level.
|
||||||
|
@ -97,16 +97,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
}
|
}
|
||||||
err := c.LowerGoroutines()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return []error{err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.VerifyIR() {
|
if c.VerifyIR() {
|
||||||
if err := c.checkModule(); err != nil {
|
if errs := c.checkModule(); errs != nil {
|
||||||
return err
|
return errs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("optimizations caused a verification failure")
|
return []error{errors.New("optimizations caused a verification failure")}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sizeLevel >= 2 {
|
if sizeLevel >= 2 {
|
||||||
|
@ -145,7 +145,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||||
if hasGCPass {
|
if hasGCPass {
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("GC pass caused a verification failure")
|
return []error{errors.New("GC pass caused a verification failure")}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче