compiler: add location information to the IR checker

Этот коммит содержится в:
Ayke van Laethem 2019-12-07 22:10:21 +01:00 коммит произвёл Ron Evans
родитель 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")}
} }
} }