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.
errs := c.Compile(pkgName)
if len(errs) != 0 {
if len(errs) == 1 {
return errs[0]
}
return &MultiError{errs}
return newMultiError(errs)
}
if config.Options.PrintIR {
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
// exactly.
errs = nil
switch config.Options.Opt {
case "none:", "0":
err = c.Optimize(0, 0, 0) // -O0
errs = c.Optimize(0, 0, 0) // -O0
case "1":
err = c.Optimize(1, 0, 0) // -O1
errs = c.Optimize(1, 0, 0) // -O1
case "2":
err = c.Optimize(2, 0, 225) // -O2
errs = c.Optimize(2, 0, 225) // -O2
case "s":
err = c.Optimize(2, 1, 225) // -Os
errs = c.Optimize(2, 1, 225) // -Os
case "z":
err = c.Optimize(2, 2, 5) // -Oz, default
errs = c.Optimize(2, 2, 5) // -Oz, 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 {
return err
if len(errs) > 0 {
return newMultiError(errs)
}
if err := c.Verify(); err != nil {
return errors.New("verification failure after LLVM optimization passes")

Просмотреть файл

@ -12,6 +12,20 @@ func (e *MultiError) Error() string {
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
// some more information regarding what went wrong while running a command.
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 {
// check value properties
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
for i := 0; i < inst.OperandsCount(); i++ {
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
}
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
var errs []error
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
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
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
var errs []error
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
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
if err := c.checkBasicBlock(bb, types, specials); err != nil {
return fmt.Errorf("failed to validate basic block of function %s: %s", fn.Name(), err.Error())
}
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
}
return nil
return errs
}
func (c *Compiler) checkModule() error {
func (c *Compiler) checkModule() []error {
// check for any context mismatches
var errs []error
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// 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:
// 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{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if err := c.checkFunction(fn, types, specials); err != nil {
return err
}
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
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
import (
"go/scanner"
"go/token"
"go/types"
"path/filepath"
"tinygo.org/x/go-llvm"
)
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) {
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.
// 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()
defer builder.Dispose()
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
if c.VerifyIR() {
err := c.checkModule()
if err != nil {
return err
errs := c.checkModule()
if errs != nil {
return errs
}
}
@ -87,7 +87,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
err := c.LowerGoroutines()
if err != nil {
return err
return []error{err}
}
} else {
// Must be run at any optimization level.
@ -97,16 +97,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
err := c.LowerGoroutines()
if err != nil {
return err
return []error{err}
}
}
if c.VerifyIR() {
if err := c.checkModule(); err != nil {
return err
if errs := c.checkModule(); errs != nil {
return errs
}
}
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 {
@ -145,7 +145,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
if hasGCPass {
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")}
}
}