Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:")
Expand Down Expand Up @@ -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")
Expand Down
14 changes: 14 additions & 0 deletions builder/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 19 additions & 20 deletions compiler/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
32 changes: 32 additions & 0 deletions compiler/errors.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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()),
}
}
20 changes: 10 additions & 10 deletions compiler/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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")}
}
}

Expand Down