diff --git a/builder/build.go b/builder/build.go index 3a510815..f9577801 100644 --- a/builder/build.go +++ b/builder/build.go @@ -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") diff --git a/builder/error.go b/builder/error.go index a6f59eb0..0f920a03 100644 --- a/builder/error.go +++ b/builder/error.go @@ -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 { diff --git a/compiler/check.go b/compiler/check.go index 97bb4459..8be5c702 100644 --- a/compiler/check.go +++ b/compiler/check.go @@ -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 } diff --git a/compiler/errors.go b/compiler/errors.go index e0030464..f47ad991 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -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()), + } +} diff --git a/compiler/optimizer.go b/compiler/optimizer.go index 70084c98..bcdd4843 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -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")} } }