Skip to content

Commit 5510dec

Browse files
aykevldeadprogram
authored andcommitted
compiler: add location information to the IR checker
1 parent dffb9fb commit 5510dec

File tree

5 files changed

+85
-42
lines changed

5 files changed

+85
-42
lines changed

builder/build.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
3333
// Compile Go code to IR.
3434
errs := c.Compile(pkgName)
3535
if len(errs) != 0 {
36-
if len(errs) == 1 {
37-
return errs[0]
38-
}
39-
return &MultiError{errs}
36+
return newMultiError(errs)
4037
}
4138
if config.Options.PrintIR {
4239
fmt.Println("; Generated LLVM IR:")
@@ -72,22 +69,23 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
7269

7370
// Optimization levels here are roughly the same as Clang, but probably not
7471
// exactly.
72+
errs = nil
7573
switch config.Options.Opt {
7674
case "none:", "0":
77-
err = c.Optimize(0, 0, 0) // -O0
75+
errs = c.Optimize(0, 0, 0) // -O0
7876
case "1":
79-
err = c.Optimize(1, 0, 0) // -O1
77+
errs = c.Optimize(1, 0, 0) // -O1
8078
case "2":
81-
err = c.Optimize(2, 0, 225) // -O2
79+
errs = c.Optimize(2, 0, 225) // -O2
8280
case "s":
83-
err = c.Optimize(2, 1, 225) // -Os
81+
errs = c.Optimize(2, 1, 225) // -Os
8482
case "z":
85-
err = c.Optimize(2, 2, 5) // -Oz, default
83+
errs = c.Optimize(2, 2, 5) // -Oz, default
8684
default:
87-
err = errors.New("unknown optimization level: -opt=" + config.Options.Opt)
85+
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
8886
}
89-
if err != nil {
90-
return err
87+
if len(errs) > 0 {
88+
return newMultiError(errs)
9189
}
9290
if err := c.Verify(); err != nil {
9391
return errors.New("verification failure after LLVM optimization passes")

builder/error.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ func (e *MultiError) Error() string {
1212
return e.Errs[0].Error()
1313
}
1414

15+
// newMultiError returns a *MultiError if there is more than one error, or
16+
// returns that error directly when there is only one. Passing an empty slice
17+
// will lead to a panic.
18+
func newMultiError(errs []error) error {
19+
switch len(errs) {
20+
case 0:
21+
panic("attempted to create empty MultiError")
22+
case 1:
23+
return errs[0]
24+
default:
25+
return &MultiError{errs}
26+
}
27+
}
28+
1529
// commandError is an error type to wrap os/exec.Command errors. This provides
1630
// some more information regarding what went wrong while running a command.
1731
type commandError struct {

compiler/check.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -98,76 +98,75 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
9898
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
9999
// check value properties
100100
if err := c.checkValue(inst, types, specials); err != nil {
101-
return fmt.Errorf("failed to validate value of instruction %q: %s", inst.Name(), err.Error())
101+
return errorAt(inst, err.Error())
102102
}
103103

104104
// check operands
105105
for i := 0; i < inst.OperandsCount(); i++ {
106106
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
107-
return fmt.Errorf("failed to validate argument %d of instruction %q: %s", i, inst.Name(), err.Error())
107+
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
108108
}
109109
}
110110

111111
return nil
112112
}
113113

114-
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
114+
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
115115
// check basic block value and type
116+
var errs []error
116117
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
117-
return fmt.Errorf("failed to validate value of basic block %s: %s", bb.AsValue().Name(), err.Error())
118+
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
118119
}
119120

120121
// check instructions
121122
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
122123
if err := c.checkInstruction(inst, types, specials); err != nil {
123-
return fmt.Errorf("failed to validate basic block %q: %s", bb.AsValue().Name(), err.Error())
124+
errs = append(errs, err)
124125
}
125126
}
126127

127-
return nil
128+
return errs
128129
}
129130

130-
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
131+
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
131132
// check function value and type
133+
var errs []error
132134
if err := c.checkValue(fn, types, specials); err != nil {
133-
return fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())
135+
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
134136
}
135137

136138
// check basic blocks
137139
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
138-
if err := c.checkBasicBlock(bb, types, specials); err != nil {
139-
return fmt.Errorf("failed to validate basic block of function %s: %s", fn.Name(), err.Error())
140-
}
140+
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
141141
}
142142

143-
return nil
143+
return errs
144144
}
145145

146-
func (c *Compiler) checkModule() error {
146+
func (c *Compiler) checkModule() []error {
147147
// check for any context mismatches
148+
var errs []error
148149
switch {
149150
case c.mod.Context() == c.ctx:
150151
// this is correct
151152
case c.mod.Context() == llvm.GlobalContext():
152153
// somewhere we accidentally used the global context instead of a real context
153-
return errors.New("module uses global context")
154+
errs = append(errs, errors.New("module uses global context"))
154155
default:
155156
// we used some other context by accident
156-
return fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx)
157+
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
157158
}
158159

159160
types := map[llvm.Type]struct{}{}
160161
specials := map[llvm.TypeKind]llvm.Type{}
161162
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
162-
if err := c.checkFunction(fn, types, specials); err != nil {
163-
return err
164-
}
163+
errs = append(errs, c.checkFunction(fn, types, specials)...)
165164
}
166165
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
167166
if err := c.checkValue(g, types, specials); err != nil {
168-
return fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())
167+
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
169168
}
170169
}
171170

172-
return nil
171+
return errs
173172
}

compiler/errors.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package compiler
22

33
import (
4+
"go/scanner"
45
"go/token"
56
"go/types"
7+
"path/filepath"
8+
9+
"tinygo.org/x/go-llvm"
610
)
711

812
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 {
1620
func (c *Compiler) addError(pos token.Pos, msg string) {
1721
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
1822
}
23+
24+
// errorAt returns an error value at the location of the instruction.
25+
// The location information may not be complete as it depends on debug
26+
// information in the IR.
27+
func errorAt(inst llvm.Value, msg string) scanner.Error {
28+
return scanner.Error{
29+
Pos: getPosition(inst),
30+
Msg: msg,
31+
}
32+
}
33+
34+
// getPosition returns the position information for the given instruction, as
35+
// far as it is available.
36+
func getPosition(inst llvm.Value) token.Position {
37+
if inst.IsAInstruction().IsNil() {
38+
return token.Position{}
39+
}
40+
loc := inst.InstructionDebugLoc()
41+
if loc.IsNil() {
42+
return token.Position{}
43+
}
44+
file := loc.LocationScope().ScopeFile()
45+
return token.Position{
46+
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
47+
Line: int(loc.LocationLine()),
48+
Column: int(loc.LocationColumn()),
49+
}
50+
}

compiler/optimizer.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
// Run the LLVM optimizer over the module.
1111
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
12-
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
12+
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
1313
builder := llvm.NewPassManagerBuilder()
1414
defer builder.Dispose()
1515
builder.SetOptLevel(optLevel)
@@ -25,9 +25,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
2525

2626
// run a check of all of our code
2727
if c.VerifyIR() {
28-
err := c.checkModule()
29-
if err != nil {
30-
return err
28+
errs := c.checkModule()
29+
if errs != nil {
30+
return errs
3131
}
3232
}
3333

@@ -87,7 +87,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
8787

8888
err := c.LowerGoroutines()
8989
if err != nil {
90-
return err
90+
return []error{err}
9191
}
9292
} else {
9393
// Must be run at any optimization level.
@@ -97,16 +97,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
9797
}
9898
err := c.LowerGoroutines()
9999
if err != nil {
100-
return err
100+
return []error{err}
101101
}
102102
}
103103
if c.VerifyIR() {
104-
if err := c.checkModule(); err != nil {
105-
return err
104+
if errs := c.checkModule(); errs != nil {
105+
return errs
106106
}
107107
}
108108
if err := c.Verify(); err != nil {
109-
return errors.New("optimizations caused a verification failure")
109+
return []error{errors.New("optimizations caused a verification failure")}
110110
}
111111

112112
if sizeLevel >= 2 {
@@ -145,7 +145,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
145145
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
146146
if hasGCPass {
147147
if err := c.Verify(); err != nil {
148-
return errors.New("GC pass caused a verification failure")
148+
return []error{errors.New("GC pass caused a verification failure")}
149149
}
150150
}
151151

0 commit comments

Comments
 (0)