Skip to content

Commit e74db01

Browse files
aykevldeadprogram
authored andcommitted
interp: improve error reporting
This commit improves error reporting in several ways: * Location information is read from the intruction that causes the error, as far as that's available. * The package that is being interpreted is included in the error message. This may be the most useful part of the improvements. * The hashmap update intrinsics now doesn't panic, instead it logs a clear error (with location information, as in the above two bullet points). This is possible thanks to improvements in LLVM 9. This means that after this change, TinyGo will depend on LLVM 9.
1 parent 86f48da commit e74db01

File tree

6 files changed

+147
-47
lines changed

6 files changed

+147
-47
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ require (
1010
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
1111
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
1212
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
13-
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8
13+
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257
1414
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Z
2828
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
2929
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
3030
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
31+
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
32+
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=

interp/errors.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,89 @@ package interp
33
// This file provides useful types for errors encountered during IR evaluation.
44

55
import (
6+
"errors"
7+
"go/scanner"
8+
"go/token"
9+
"path/filepath"
10+
611
"tinygo.org/x/go-llvm"
712
)
813

14+
// errUnreachable is returned when an unreachable instruction is executed. This
15+
// error should not be visible outside of the interp package.
16+
var errUnreachable = errors.New("interp: unreachable executed")
17+
18+
// Unsupported is the specific error that is returned when an unsupported
19+
// instruction is hit while trying to interpret all initializers.
920
type Unsupported struct {
10-
Inst llvm.Value
21+
ImportPath string
22+
Inst llvm.Value
23+
Pos token.Position
1124
}
1225

1326
func (e Unsupported) Error() string {
1427
// TODO: how to return the actual instruction string?
1528
// It looks like LLVM provides no function for that...
16-
return "interp: unsupported instruction"
29+
return scanner.Error{
30+
Pos: e.Pos,
31+
Msg: "interp: unsupported instruction",
32+
}.Error()
33+
}
34+
35+
// unsupportedInstructionError returns a new "unsupported instruction" error for
36+
// the given instruction. It includes source location information, when
37+
// available.
38+
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
39+
return &Unsupported{
40+
ImportPath: e.packagePath,
41+
Inst: inst,
42+
Pos: getPosition(inst),
43+
}
44+
}
45+
46+
// Error encapsulates compile-time interpretation errors with an associated
47+
// import path. The errors may not have a precise location attached.
48+
type Error struct {
49+
ImportPath string
50+
Errs []scanner.Error
51+
}
52+
53+
// Error returns the string of the first error in the list of errors.
54+
func (e Error) Error() string {
55+
return e.Errs[0].Error()
56+
}
57+
58+
// errorAt returns an error value for the currently interpreted package at the
59+
// location of the instruction. The location information may not be complete as
60+
// it depends on debug information in the IR.
61+
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
62+
return Error{
63+
ImportPath: e.packagePath,
64+
Errs: []scanner.Error{errorAt(inst, msg)},
65+
}
66+
}
67+
68+
// errorAt returns an error value at the location of the instruction.
69+
// The location information may not be complete as it depends on debug
70+
// information in the IR.
71+
func errorAt(inst llvm.Value, msg string) scanner.Error {
72+
return scanner.Error{
73+
Pos: getPosition(inst),
74+
Msg: msg,
75+
}
76+
}
77+
78+
// getPosition returns the position information for the given instruction, as
79+
// far as it is available.
80+
func getPosition(inst llvm.Value) token.Position {
81+
loc := inst.InstructionDebugLoc()
82+
if loc.IsNil() {
83+
return token.Position{}
84+
}
85+
file := loc.LocationScope().ScopeFile()
86+
return token.Position{
87+
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
88+
Line: int(loc.LocationLine()),
89+
Column: int(loc.LocationColumn()),
90+
}
1791
}

interp/frame.go

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@ package interp
44
// functions.
55

66
import (
7-
"errors"
87
"strings"
98

109
"tinygo.org/x/go-llvm"
1110
)
1211

1312
type frame struct {
14-
*Eval
15-
fn llvm.Value
16-
pkgName string
17-
locals map[llvm.Value]Value
13+
*evalPackage
14+
fn llvm.Value
15+
locals map[llvm.Value]Value
1816
}
1917

20-
var ErrUnreachable = errors.New("interp: unreachable executed")
21-
2218
// evalBasicBlock evaluates a single basic block, returning the return value (if
2319
// ending with a ret instruction), a list of outgoing basic blocks (if not
2420
// ending with a ret instruction), or an error on failure.
@@ -79,13 +75,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
7975
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
8076

8177
default:
82-
return nil, nil, &Unsupported{inst}
78+
return nil, nil, fr.unsupportedInstructionError(inst)
8379
}
8480

8581
// Memory operators
8682
case !inst.IsAAllocaInst().IsNil():
8783
allocType := inst.Type().ElementType()
88-
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
84+
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
8985
alloca.SetInitializer(llvm.ConstNull(allocType))
9086
alloca.SetLinkage(llvm.InternalLinkage)
9187
fr.locals[inst] = &LocalValue{
@@ -247,12 +243,12 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
247243
if size != typeSize {
248244
// allocate an array
249245
if size%typeSize != 0 {
250-
return nil, nil, &Unsupported{inst}
246+
return nil, nil, fr.unsupportedInstructionError(inst)
251247
}
252248
elementCount = int(size / typeSize)
253249
allocType = llvm.ArrayType(allocType, elementCount)
254250
}
255-
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
251+
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
256252
alloc.SetInitializer(llvm.ConstNull(allocType))
257253
alloc.SetLinkage(llvm.InternalLinkage)
258254
result := &LocalValue{
@@ -270,13 +266,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
270266
valueSize := inst.Operand(1).ZExtValue()
271267
fr.locals[inst] = &MapValue{
272268
Eval: fr.Eval,
273-
PkgName: fr.pkgName,
269+
PkgName: fr.packagePath,
274270
KeySize: int(keySize),
275271
ValueSize: int(valueSize),
276272
}
277273
case callee.Name() == "runtime.hashmapStringSet":
278274
// set a string key in the map
279-
m := fr.getLocal(inst.Operand(0)).(*MapValue)
275+
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
276+
if !ok {
277+
return nil, nil, fr.errorAt(inst, "could not update map with string key")
278+
}
280279
// "key" is a Go string value, which in the TinyGo calling convention is split up
281280
// into separate pointer and length parameters.
282281
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
@@ -285,7 +284,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
285284
m.PutString(keyBuf, keyLen, valPtr)
286285
case callee.Name() == "runtime.hashmapBinarySet":
287286
// set a binary (int etc.) key in the map
288-
m := fr.getLocal(inst.Operand(0)).(*MapValue)
287+
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
288+
if !ok {
289+
return nil, nil, fr.errorAt(inst, "could not update map")
290+
}
289291
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
290292
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
291293
m.PutBinary(keyBuf, valPtr)
@@ -304,7 +306,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
304306
}
305307
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
306308
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
307-
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$stringconcat")
309+
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
308310
global.SetInitializer(globalValue)
309311
global.SetLinkage(llvm.InternalLinkage)
310312
global.SetGlobalConstant(true)
@@ -336,20 +338,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
336338
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
337339
}
338340
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
339-
return nil, nil, errors.New("interp: slice dst element size does not match pointer type")
341+
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
340342
}
341343
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
342-
return nil, nil, errors.New("interp: slice src element size does not match pointer type")
344+
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
343345
}
344346
if dstArray.Type() != srcArray.Type() {
345-
return nil, nil, errors.New("interp: slice element types don't match")
347+
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
346348
}
347349
length := dstLen.Value().SExtValue()
348350
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
349351
length = srcLength
350352
}
351353
if length < 0 {
352-
return nil, nil, errors.New("interp: trying to copy a slice with negative length?")
354+
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
353355
}
354356
for i := int64(0); i < length; i++ {
355357
// *dst = *src
@@ -370,7 +372,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
370372
}
371373
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
372374
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
373-
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$bytes")
375+
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
374376
global.SetInitializer(globalValue)
375377
global.SetLinkage(llvm.InternalLinkage)
376378
global.SetGlobalConstant(true)
@@ -489,7 +491,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
489491
// compile time.
490492
// * Unbounded: cannot call at runtime so we'll try to
491493
// interpret anyway and hope for the best.
492-
ret, err = fr.function(callee, params, fr.pkgName, indent+" ")
494+
ret, err = fr.function(callee, params, indent+" ")
493495
if err != nil {
494496
return nil, nil, err
495497
}
@@ -499,7 +501,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
499501
}
500502
default:
501503
// function pointers, etc.
502-
return nil, nil, &Unsupported{inst}
504+
return nil, nil, fr.unsupportedInstructionError(inst)
503505
}
504506
case !inst.IsAExtractValueInst().IsNil():
505507
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
@@ -509,7 +511,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
509511
fr.locals[inst] = fr.getValue(newValue)
510512
} else {
511513
if len(indices) != 1 {
512-
return nil, nil, errors.New("cannot handle extractvalue with not exactly 1 index")
514+
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
513515
}
514516
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
515517
}
@@ -522,7 +524,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
522524
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
523525
} else {
524526
if len(indices) != 1 {
525-
return nil, nil, errors.New("cannot handle insertvalue with not exactly 1 index")
527+
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
526528
}
527529
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
528530
}
@@ -540,12 +542,12 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
540542
thenBB := inst.Operand(1)
541543
elseBB := inst.Operand(2)
542544
if !cond.IsAInstruction().IsNil() {
543-
return nil, nil, errors.New("interp: branch on a non-constant")
545+
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
544546
}
545547
if !cond.IsAConstantExpr().IsNil() {
546548
// This may happen when the instruction builder could not
547549
// const-fold some instructions.
548-
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
550+
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
549551
}
550552
switch cond {
551553
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
@@ -561,10 +563,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
561563
case !inst.IsAUnreachableInst().IsNil():
562564
// Unreachable was reached (e.g. after a call to panic()).
563565
// Report this as an error, as it is not supposed to happen.
564-
return nil, nil, ErrUnreachable
566+
// This is a sentinel error value.
567+
return nil, nil, errUnreachable
565568

566569
default:
567-
return nil, nil, &Unsupported{inst}
570+
return nil, nil, fr.unsupportedInstructionError(inst)
568571
}
569572
}
570573

interp/interp.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package interp
77
// methods.
88

99
import (
10-
"errors"
1110
"strings"
1211

1312
"tinygo.org/x/go-llvm"
@@ -22,6 +21,15 @@ type Eval struct {
2221
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
2322
}
2423

24+
// evalPackage encapsulates the Eval type for just a single package. The Eval
25+
// type keeps state across the whole program, the evalPackage type keeps extra
26+
// state for the currently interpreted package.
27+
type evalPackage struct {
28+
*Eval
29+
packagePath string
30+
errors []error
31+
}
32+
2533
// Run evaluates the function with the given name and then eliminates all
2634
// callers.
2735
func Run(mod llvm.Module, debug bool) error {
@@ -56,7 +64,7 @@ func Run(mod llvm.Module, debug bool) error {
5664
break // ret void
5765
}
5866
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
59-
return errors.New("expected all instructions in " + name + " to be direct calls")
67+
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
6068
}
6169
initCalls = append(initCalls, inst)
6270
}
@@ -66,13 +74,17 @@ func Run(mod llvm.Module, debug bool) error {
6674
for _, call := range initCalls {
6775
initName := call.CalledValue().Name()
6876
if !strings.HasSuffix(initName, ".init") {
69-
return errors.New("expected all instructions in " + name + " to be *.init() calls")
77+
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
7078
}
7179
pkgName := initName[:len(initName)-5]
7280
fn := call.CalledValue()
7381
call.EraseFromParentAsInstruction()
74-
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
75-
if err == ErrUnreachable {
82+
evalPkg := evalPackage{
83+
Eval: e,
84+
packagePath: pkgName,
85+
}
86+
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
87+
if err == errUnreachable {
7688
break
7789
}
7890
if err != nil {
@@ -83,16 +95,14 @@ func Run(mod llvm.Module, debug bool) error {
8395
return nil
8496
}
8597

86-
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
87-
return e.function(fn, params, pkgName, "")
88-
}
89-
90-
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
98+
// function interprets the given function. The params are the function params
99+
// and the indent is the string indentation to use when dumping all interpreted
100+
// instructions.
101+
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
91102
fr := frame{
92-
Eval: e,
93-
fn: fn,
94-
pkgName: pkgName,
95-
locals: make(map[llvm.Value]Value),
103+
evalPackage: e,
104+
fn: fn,
105+
locals: make(map[llvm.Value]Value),
96106
}
97107
for i, param := range fn.Params() {
98108
fr.locals[param] = params[i]

0 commit comments

Comments
 (0)