Skip to content

Commit cc4a4c7

Browse files
aykevldeadprogram
authored andcommitted
interp: show backtrace with error
This should make it much easier to figure out why and where an error happens at package initialization time.
1 parent 2501602 commit cc4a4c7

File tree

6 files changed

+70
-73
lines changed

6 files changed

+70
-73
lines changed

compiler/compiler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,8 @@ func (b *builder) createFunctionDefinition() {
887887
if b.fn.Synthetic == "package initializer" {
888888
// Package initializers have no debug info. Create some fake debug
889889
// info to at least have *something*.
890-
b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", "", 0)
890+
filename := b.fn.Package().Pkg.Path() + "/<init>"
891+
b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", filename, 0)
891892
} else if b.fn.Syntax() != nil {
892893
// Create debug info file if needed.
893894
b.difunc = b.attachDebugInfo(b.fn)

interp/errors.go

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,44 @@ import (
1313

1414
// errUnreachable is returned when an unreachable instruction is executed. This
1515
// 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.
20-
type Unsupported struct {
21-
ImportPath string
22-
Inst llvm.Value
23-
Pos token.Position
24-
}
25-
26-
func (e Unsupported) Error() string {
27-
// TODO: how to return the actual instruction string?
28-
// It looks like LLVM provides no function for that...
29-
return scanner.Error{
30-
Pos: e.Pos,
31-
Msg: "interp: unsupported instruction",
32-
}.Error()
33-
}
16+
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
3417

3518
// unsupportedInstructionError returns a new "unsupported instruction" error for
3619
// the given instruction. It includes source location information, when
3720
// 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-
}
21+
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
22+
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
23+
}
24+
25+
// ErrorLine is one line in a traceback. The position may be missing.
26+
type ErrorLine struct {
27+
Pos token.Position
28+
Inst llvm.Value
4429
}
4530

4631
// Error encapsulates compile-time interpretation errors with an associated
4732
// import path. The errors may not have a precise location attached.
4833
type Error struct {
4934
ImportPath string
50-
Errs []scanner.Error
35+
Inst llvm.Value
36+
Pos token.Position
37+
Err error
38+
Traceback []ErrorLine
5139
}
5240

5341
// 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()
42+
func (e *Error) Error() string {
43+
return e.Pos.String() + ": " + e.Err.Error()
5644
}
5745

5846
// errorAt returns an error value for the currently interpreted package at the
5947
// location of the instruction. The location information may not be complete as
6048
// it depends on debug information in the IR.
61-
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
62-
return Error{
49+
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
50+
return &Error{
6351
ImportPath: e.packagePath,
64-
Errs: []scanner.Error{errorAt(inst, msg)},
52+
Pos: getPosition(inst),
53+
Err: err,
6554
}
6655
}
6756

interp/frame.go

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package interp
44
// functions.
55

66
import (
7+
"errors"
78
"strings"
89

910
"tinygo.org/x/go-llvm"
@@ -21,7 +22,7 @@ type frame struct {
2122
// Most of it works at compile time. Some calls get translated into calls to be
2223
// executed at runtime: calls to functions with side effects, external calls,
2324
// and operations on the result of such instructions.
24-
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err error) {
25+
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err *Error) {
2526
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
2627
if fr.Debug {
2728
print(indent)
@@ -97,7 +98,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
9798
value = operand.Load()
9899
}
99100
if value.Type() != inst.Type() {
100-
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
101+
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
101102
}
102103
fr.locals[inst] = fr.getValue(value)
103104
case !inst.IsAStoreInst().IsNil():
@@ -121,16 +122,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
121122
// Not a constant operation.
122123
// This should be detected by the scanner, but isn't at the
123124
// moment.
124-
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
125+
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
125126
}
126127
indices[i] = uint32(operand.Value().ZExtValue())
127128
}
128129
result, err := value.GetElementPtr(indices)
129130
if err != nil {
130-
return nil, nil, fr.errorAt(inst, err.Error())
131+
return nil, nil, fr.errorAt(inst, err)
131132
}
132133
if result.Type() != inst.Type() {
133-
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
134+
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
134135
}
135136
fr.locals[inst] = result
136137

@@ -185,7 +186,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
185186
}
186187
}
187188
// It is not possible in Go to bitcast a map value to a pointer.
188-
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
189+
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
189190
}
190191
value := fr.getLocal(operand).(*LocalValue)
191192
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -269,7 +270,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
269270
} else {
270271
result, err := result.GetElementPtr([]uint32{0, 0})
271272
if err != nil {
272-
return nil, nil, errorAt(inst, err.Error())
273+
return nil, nil, fr.errorAt(inst, err)
273274
}
274275
fr.locals[resultInst] = result
275276
}
@@ -374,30 +375,30 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
374375
// Re-add this GEP, in the hope that it it is then of the correct type...
375376
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
376377
if err != nil {
377-
return nil, nil, errorAt(inst, err.Error())
378+
return nil, nil, fr.errorAt(inst, err)
378379
}
379380
dstArray = dstArrayValue.(*LocalValue)
380381
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
381382
if err != nil {
382-
return nil, nil, errorAt(inst, err.Error())
383+
return nil, nil, fr.errorAt(inst, err)
383384
}
384385
srcArray = srcArrayValue.(*LocalValue)
385386
}
386387
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
387-
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
388+
return nil, nil, fr.errorAt(inst, errors.New("interp: slice dst element size does not match pointer type"))
388389
}
389390
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
390-
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
391+
return nil, nil, fr.errorAt(inst, errors.New("interp: slice src element size does not match pointer type"))
391392
}
392393
if dstArray.Type() != srcArray.Type() {
393-
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
394+
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
394395
}
395396
length := dstLen.Value().SExtValue()
396397
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
397398
length = srcLength
398399
}
399400
if length < 0 {
400-
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
401+
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
401402
}
402403
for i := int64(0); i < length; i++ {
403404
var err error
@@ -406,13 +407,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
406407
// dst++
407408
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
408409
if err != nil {
409-
return nil, nil, errorAt(inst, err.Error())
410+
return nil, nil, fr.errorAt(inst, err)
410411
}
411412
dstArray = dstArrayValue.(*LocalValue)
412413
// src++
413414
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
414415
if err != nil {
415-
return nil, nil, errorAt(inst, err.Error())
416+
return nil, nil, fr.errorAt(inst, err)
416417
}
417418
srcArray = srcArrayValue.(*LocalValue)
418419
}
@@ -444,11 +445,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
444445
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
445446
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
446447
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
447-
return nil, nil, fr.errorAt(inst, "interp: expected typecode in runtime.typeAssert to be a ptrtoint")
448+
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
448449
}
449450
actualType := actualTypeInt.Operand(0)
450451
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
451-
return nil, nil, fr.errorAt(inst, "interp: unimplemented: type assert with non-constant interface value")
452+
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
452453
}
453454
assertOk := uint64(0)
454455
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
@@ -459,16 +460,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
459460
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
460461
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
461462
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
462-
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
463+
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
463464
}
464465
typecode = typecode.Operand(0)
465466
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
466-
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
467+
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set in runtime.interfaceImplements to be a constant gep"))
467468
}
468469
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
469470
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
470471
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
471-
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
472+
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
472473
}
473474
methodSet = methodSet.Operand(0).Initializer()
474475

@@ -568,6 +569,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
568569
// interpret anyway and hope for the best.
569570
ret, err = fr.function(callee, params, indent+" ")
570571
if err != nil {
572+
// Record this function call in the backtrace.
573+
err.Traceback = append(err.Traceback, ErrorLine{
574+
Pos: getPosition(inst),
575+
Inst: inst,
576+
})
571577
return nil, nil, err
572578
}
573579
}
@@ -586,7 +592,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
586592
fr.locals[inst] = fr.getValue(newValue)
587593
} else {
588594
if len(indices) != 1 {
589-
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
595+
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle extractvalue with not exactly 1 index"))
590596
}
591597
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
592598
}
@@ -599,7 +605,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
599605
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
600606
} else {
601607
if len(indices) != 1 {
602-
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
608+
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle insertvalue with not exactly 1 index"))
603609
}
604610
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
605611
}
@@ -624,25 +630,25 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
624630
// conditional branch (if/then/else)
625631
cond := fr.getLocal(inst.Operand(0)).Value()
626632
if cond.Type() != fr.Mod.Context().Int1Type() {
627-
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
633+
return nil, nil, fr.errorAt(inst, errors.New("expected an i1 in a branch instruction"))
628634
}
629635
thenBB := inst.Operand(1)
630636
elseBB := inst.Operand(2)
631637
if !cond.IsAInstruction().IsNil() {
632-
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
638+
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
633639
}
634640
if !cond.IsAConstantExpr().IsNil() {
635641
// This may happen when the instruction builder could not
636642
// const-fold some instructions.
637-
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
643+
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-const-propagated constant expression"))
638644
}
639645
switch cond {
640646
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
641647
return nil, []llvm.Value{thenBB}, nil // then
642648
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
643649
return nil, []llvm.Value{elseBB}, nil // else
644650
default:
645-
return nil, nil, fr.errorAt(inst, "branch was not true or false")
651+
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
646652
}
647653
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
648654
// unconditional branch (goto)

interp/interp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func Run(mod llvm.Module, debug bool) error {
9797
// function interprets the given function. The params are the function params
9898
// and the indent is the string indentation to use when dumping all interpreted
9999
// instructions.
100-
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
100+
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
101101
fr := frame{
102102
evalPackage: e,
103103
fn: fn,

interp/scan.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package interp
22

33
import (
4+
"errors"
45
"strings"
56

67
"tinygo.org/x/go-llvm"
@@ -40,7 +41,7 @@ type sideEffectResult struct {
4041
// hasSideEffects scans this function and all descendants, recursively. It
4142
// returns whether this function has side effects and if it does, which globals
4243
// it mentions anywhere in this function or any called functions.
43-
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
44+
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
4445
name := fn.Name()
4546
switch {
4647
case name == "runtime.alloc":
@@ -99,7 +100,7 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
99100
switch inst.InstructionOpcode() {
100101
case llvm.IndirectBr, llvm.Invoke:
101102
// Not emitted by the compiler.
102-
return nil, e.errorAt(inst, "unknown instructions")
103+
return nil, e.errorAt(inst, errors.New("unknown instructions"))
103104
case llvm.Call:
104105
child := inst.CalledValue()
105106
if !child.IsAInlineAsm().IsNil() {

main.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -658,22 +658,22 @@ func usage() {
658658
// to limitations in the LLVM bindings.
659659
func printCompilerError(logln func(...interface{}), err error) {
660660
switch err := err.(type) {
661-
case *interp.Unsupported:
662-
// hit an unknown/unsupported instruction
663-
logln("#", err.ImportPath)
664-
msg := "unsupported instruction during init evaluation:"
665-
if err.Pos.String() != "" {
666-
msg = err.Pos.String() + " " + msg
667-
}
668-
logln(msg)
669-
err.Inst.Dump()
670-
logln()
671661
case types.Error, scanner.Error:
672662
logln(err)
673-
case interp.Error:
663+
case *interp.Error:
674664
logln("#", err.ImportPath)
675-
for _, err := range err.Errs {
676-
logln(err)
665+
logln(err.Error())
666+
if !err.Inst.IsNil() {
667+
err.Inst.Dump()
668+
logln()
669+
}
670+
if len(err.Traceback) > 0 {
671+
logln("\ntraceback:")
672+
for _, line := range err.Traceback {
673+
logln(line.Pos.String() + ":")
674+
line.Inst.Dump()
675+
logln()
676+
}
677677
}
678678
case loader.Errors:
679679
logln("#", err.Pkg.ImportPath)

0 commit comments

Comments
 (0)