From e22df631c4428f8dea4d5784a8a7840d2f84c6be Mon Sep 17 00:00:00 2001 From: Mel Date: Mon, 22 Aug 2022 23:41:49 +0000 Subject: Generate runtime debug info with source locations --- pkg/lang/compiler/compiler.go | 46 +++++++++++++++++------ pkg/lang/vm/code/builder.go | 6 +++ pkg/lang/vm/code/debug.go | 29 ++++++++++----- pkg/lang/vm/vm.go | 8 +++- pkg/libs/rangemap/rangemap.go | 67 ++++++++++++++++++++++++++++++--- pkg/libs/rangemap/rangemap_test.go | 76 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 27 deletions(-) (limited to 'pkg') diff --git a/pkg/lang/compiler/compiler.go b/pkg/lang/compiler/compiler.go index b08a215..b1d4dfb 100644 --- a/pkg/lang/compiler/compiler.go +++ b/pkg/lang/compiler/compiler.go @@ -70,6 +70,8 @@ func (comp *Compiler) Compile() (modules.Module, error) { } func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error { + startPc := t.Len() + var err error switch stmt.Kind { case ast.StmtKindEmpty: @@ -119,15 +121,24 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error { } // Wrap errors in a compiler error, if it's not already one. - if err != nil && !isCompilerError(err) { - err = Error{ - Module: comp.name, - Loc: stmt.At, - Err: err, + if err != nil { + if !isCompilerError(err) { + err = Error{ + Module: comp.name, + Loc: stmt.At, + Err: err, + } } + return err } - return err + // Add debug info if needed. + endPc := t.Len() + if startPc != endPc { + t.AddDebugInfo(startPc, endPc-1, stmt.At) + } + + return nil } func (comp *Compiler) compileUseStmt(t *code.Builder, stmt ast.StmtUse) error { @@ -645,6 +656,8 @@ func (comp *Compiler) compileExprStmt(t *code.Builder, exprStmt ast.StmtExpr) er } func (comp *Compiler) compileExpr(t *code.Builder, expr ast.Expr) error { + startPc := t.Len() + var err error switch expr.Kind { case ast.ExprKindBinary: @@ -683,15 +696,24 @@ func (comp *Compiler) compileExpr(t *code.Builder, expr ast.Expr) error { } // If the error was not a compiler error, we need to wrap it in one. - if err != nil && !isCompilerError(err) { - err = Error{ - Module: comp.name, - Loc: expr.At, - Err: err, + if err != nil { + if !isCompilerError(err) { + err = Error{ + Module: comp.name, + Loc: expr.At, + Err: err, + } } + + return err } - return err + endPc := t.Len() + if startPc != endPc { + t.AddDebugInfo(startPc, endPc-1, expr.At) + } + + return nil } func (comp *Compiler) compileBinaryExpr(t *code.Builder, expr ast.ExprBinary) error { diff --git a/pkg/lang/vm/code/builder.go b/pkg/lang/vm/code/builder.go index f413ba1..618608a 100644 --- a/pkg/lang/vm/code/builder.go +++ b/pkg/lang/vm/code/builder.go @@ -2,6 +2,7 @@ package code import ( "encoding/binary" + "jinx/pkg/libs/source" "math" ) @@ -76,6 +77,7 @@ func (b *Builder) AppendBuilder(other Builder) error { } b.AppendRaw(other.code) + b.debugInfo.AppendOther(other.debugInfo) return nil } @@ -114,6 +116,10 @@ func (b *Builder) EndLine() { b.debugInfo.AppendLine(b.Len()-1, b.currentLine) } +func (b *Builder) AddDebugInfo(fromPc, toPc int, loc source.Loc) { + b.debugInfo.FillInfo(fromPc, toPc, loc) +} + func (b *Builder) SetInt(at int, x int64) { binary.LittleEndian.PutUint64(b.code[at:], uint64(x)) } diff --git a/pkg/lang/vm/code/debug.go b/pkg/lang/vm/code/debug.go index ebf0ea1..efb788c 100644 --- a/pkg/lang/vm/code/debug.go +++ b/pkg/lang/vm/code/debug.go @@ -1,16 +1,19 @@ package code -import "jinx/pkg/libs/rangemap" +import ( + "jinx/pkg/libs/rangemap" + "jinx/pkg/libs/source" +) type DebugInfo struct { file string - pcToLine rangemap.RangeMap[int] + pcToLine rangemap.RangeMap[source.Loc] } func NewDebugInfo(file string) DebugInfo { return DebugInfo{ file: file, - pcToLine: rangemap.New[int](), + pcToLine: rangemap.New[source.Loc](), } } @@ -18,14 +21,22 @@ func (di *DebugInfo) File() string { return di.file } -func (di *DebugInfo) PCToLine(pc int) int { - line := di.pcToLine.Get(pc) - if line == nil { - return -1 +func (di *DebugInfo) PCToLoc(pc int) (source.Loc, bool) { + loc := di.pcToLine.Get(pc) + if loc == nil { + return source.Loc{}, false } - return *line + return *loc, true } func (di *DebugInfo) AppendLine(uptoPc, line int) { - di.pcToLine.AppendToLast(uptoPc, line) + di.pcToLine.AppendToLast(uptoPc, source.Loc{Row: line, Col: 0}) +} + +func (di *DebugInfo) FillInfo(from, to int, loc source.Loc) { + di.pcToLine.Fill(from, to, loc) +} + +func (di *DebugInfo) AppendOther(other DebugInfo) { + di.pcToLine.AppendRanges(other.pcToLine) } diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go index 4d4d5ca..b18e7cb 100644 --- a/pkg/lang/vm/vm.go +++ b/pkg/lang/vm/vm.go @@ -121,10 +121,16 @@ func (vm *VM) executeModule(moduleID int) error { vm.advancePC(advance) if decision, err := vm.step(code, op); err != nil { + loc, ok := code.Debug().PCToLoc(vm.pc()) + line := loc.Row + if !ok { + line = -1 + } + return Error{ Pos: vm.pos, Module: module.Name(), - Line: code.Debug().PCToLine(vm.pc()), + Line: line, Err: err, } } else if decision == stepDecisionHalt { diff --git a/pkg/libs/rangemap/rangemap.go b/pkg/libs/rangemap/rangemap.go index 650deec..367483a 100644 --- a/pkg/libs/rangemap/rangemap.go +++ b/pkg/libs/rangemap/rangemap.go @@ -73,6 +73,54 @@ func (rm *RangeMap[D]) Set(from, to int, data D) { rm.ranges = newRanges } +func (rm *RangeMap[D]) Fill(from, to int, data D) { + if to < from { + return + } + + leftIndex := rm.entry(from) + rightIndex := rm.entry(to) + + for i := leftIndex; i <= rightIndex; i++ { + r := rm.ranges[i] + // Range is not inside fill zone or is already filled. + if r.from > to || (r.to < from && r.to != -1) || r.data != nil { + continue + } + + if r.from >= from && r.to <= to && r.to != -1 { + // The empty range is completely contained in the range to fill, overwrite it. + rm.ranges[i].data = &data + } else { + // The empty range is partially contained in the range to fill, split it. + rm.Set(max(r.from, from), min(r.to, to), data) + } + } +} + +func (rm *RangeMap[D]) AppendRanges(other RangeMap[D]) { + newRanges := make([]rangeEntry[D], 0, len(rm.ranges)+len(other.ranges)) + offset := rm.ranges[len(rm.ranges)-1].from + + // No need to copy the sentinel. + newRanges = append(newRanges, rm.ranges[:len(rm.ranges)-1]...) + + for _, otherEntry := range other.ranges { + to := otherEntry.to + offset + if otherEntry.to == -1 { + to = -1 // Sentinel not affected by offset. + } + + newRanges = append(newRanges, rangeEntry[D]{ + from: otherEntry.from + offset, + to: to, + data: otherEntry.data, + }) + } + + rm.ranges = newRanges +} + func (rm *RangeMap[D]) Get(i int) *D { return rm.ranges[rm.entry(i)].data } @@ -100,11 +148,6 @@ func (rm *RangeMap[D]) entry(i int) int { return 0 } - lastRange := rm.ranges[len(rm.ranges)-1] - if i >= lastRange.to && lastRange.to != -1 { - return len(rm.ranges) - 1 - } - left := 0 right := len(rm.ranges) - 1 for left <= right { @@ -127,3 +170,17 @@ type rangeEntry[D any] struct { to int data *D } + +func min(a, b int) int { + if a < b && a != -1 { + return a + } + return b +} + +func max(a, b int) int { + if a > b && b != -1 { + return a + } + return b +} diff --git a/pkg/libs/rangemap/rangemap_test.go b/pkg/libs/rangemap/rangemap_test.go index deb6dc7..7ea7f52 100644 --- a/pkg/libs/rangemap/rangemap_test.go +++ b/pkg/libs/rangemap/rangemap_test.go @@ -112,3 +112,79 @@ func TestSpaceBetween(t *testing.T) { // Since our search can never fail, we pad the space with a sentinel value. assert.Equal(t, "[{0..2: a}, {3..3}, {4..6: x}, {7..8}, {9..9: b}, {10..-1}]", rm.String()) } + +func TestAppendRanges(t *testing.T) { + rm1 := rangemap.New[string]() + + rm1.Set(0, 2, "a") + rm1.Set(3, 5, "b") + + rm2 := rangemap.New[string]() + rm2.Set(0, 2, "c") + rm2.Set(3, 3, "d") + + assert.Equal(t, "[{0..2: a}, {3..5: b}, {6..-1}]", rm1.String()) + assert.Equal(t, "[{0..2: c}, {3..3: d}, {4..-1}]", rm2.String()) + + rm1.AppendRanges(rm2) + + assert.Equal(t, "[{0..2: a}, {3..5: b}, {6..8: c}, {9..9: d}, {10..-1}]", rm1.String()) +} + +func TestAppendRangesSpaceBetween(t *testing.T) { + rm1 := rangemap.New[string]() + + rm1.Set(1, 2, "a") + rm1.Set(4, 4, "b") + + rm2 := rangemap.New[string]() + + rm2.Set(2, 4, "c") + rm2.Set(6, 6, "d") + + rm1.AppendRanges(rm2) + + assert.Equal(t, "[{0..0}, {1..2: a}, {3..3}, {4..4: b}, {5..6}, {7..9: c}, {10..10}, {11..11: d}, {12..-1}]", rm1.String()) +} + +func TestFill(t *testing.T) { + rm := rangemap.New[string]() + + rm.Set(0, 1, "a") + rm.Set(3, 5, "b") + rm.Set(9, 10, "c") + + rm.Fill(2, 10, "x") + + assert.Equal(t, "[{0..1: a}, {2..2: x}, {3..5: b}, {6..8: x}, {9..10: c}, {11..-1}]", rm.String()) +} + +func TestFillFromMiddle(t *testing.T) { + rm := rangemap.New[string]() + + rm.Set(0, 2, "a") + rm.Set(9, 10, "c") + + rm.Fill(5, 6, "x") + + assert.Equal(t, "[{0..2: a}, {3..4}, {5..6: x}, {7..8}, {9..10: c}, {11..-1}]", rm.String()) +} + +func TestFillEdge(t *testing.T) { + rm := rangemap.New[string]() + + rm.Set(0, 2, "a") + rm.Set(9, 10, "c") + + rm.Fill(2, 6, "x") + + assert.Equal(t, "[{0..2: a}, {3..6: x}, {7..8}, {9..10: c}, {11..-1}]", rm.String()) +} + +func TestFillFromEmpty(t *testing.T) { + rm := rangemap.New[string]() + rm.Fill(2, 4, "x") + rm.Fill(8, 10, "x") + + assert.Equal(t, "[{0..1}, {2..4: x}, {5..7}, {8..10: x}, {11..-1}]", rm.String()) +} -- cgit 1.4.1