Skip to content

Commit ddfd435

Browse files
committed
Implement call tracing.
1 parent 3dd6aa4 commit ddfd435

File tree

4 files changed

+90
-28
lines changed

4 files changed

+90
-28
lines changed

Sources/LispKit/Base/StringBuilder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public struct StringBuilder: CustomStringConvertible {
105105
case 8:
106106
return " "
107107
default:
108-
return String(repeating: " ", count: n)
108+
return n < 0 ? "" : String(repeating: " ", count: n)
109109
}
110110
}
111111
}

Sources/LispKit/Primitives/SystemLibrary.swift

+13
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public final class SystemLibrary: NativeLibrary {
8181
self.define(Procedure("gc", self.gc))
8282
self.define(Procedure("compile", self.compile))
8383
self.define(Procedure("disassemble", self.disassemble))
84+
self.define(Procedure("trace-calls", self.traceCalls))
8485
self.define(Procedure("available-symbols", self.availableSymbols))
8586
self.define(Procedure("loaded-libraries", self.loadedLibraries))
8687
self.define(Procedure("environment-info", self.environmentInfo))
@@ -410,6 +411,18 @@ public final class SystemLibrary: NativeLibrary {
410411
return .void
411412
}
412413

414+
private func traceCalls(_ expr: Expr?) throws -> Expr {
415+
if let expr = expr {
416+
switch (expr) {
417+
case .false:
418+
self.context.machine.traceCalls = false
419+
default:
420+
self.context.machine.traceCalls = true
421+
}
422+
}
423+
return .makeBoolean(self.context.machine.traceCalls)
424+
}
425+
413426
private func availableSymbols() -> Expr {
414427
var res = Expr.null
415428
for sym in self.context.symbols {

Sources/LispKit/Resources/Libraries/lispkit/wt-tree.sld

+19-19
Original file line numberDiff line numberDiff line change
@@ -79,27 +79,27 @@
7979
;; ordering relation.
8080

8181
;; MIT-Scheme structure definition
82-
;;(define-structure
83-
;; (tree-type
82+
;; (define-structure
83+
;; (tree-type
8484
;; (conc-name tree-type/)
8585
;; (constructor %make-tree-type))
86-
;; (key<? #F read-only true)
87-
;; (alist->tree #F read-only true)
88-
;; (add #F read-only true)
89-
;; (insert! #F read-only true)
90-
;; (delete #F read-only true)
91-
;; (delete! #F read-only true)
92-
;; (member? #F read-only true)
93-
;; (lookup #F read-only true)
94-
;; (split-lt #F read-only true)
95-
;; (split-gt #F read-only true)
96-
;; (union #F read-only true)
97-
;; (union-merge #F read-only true)
98-
;; (intersection #F read-only true)
99-
;; (difference #F read-only true)
100-
;; (subset? #F read-only true)
101-
;; (rank #F read-only true)
102-
;;)
86+
;; (key<? #F read-only true)
87+
;; (alist->tree #F read-only true)
88+
;; (add #F read-only true)
89+
;; (insert! #F read-only true)
90+
;; (delete #F read-only true)
91+
;; (delete! #F read-only true)
92+
;; (member? #F read-only true)
93+
;; (lookup #F read-only true)
94+
;; (split-lt #F read-only true)
95+
;; (split-gt #F read-only true)
96+
;; (union #F read-only true)
97+
;; (union-merge #F read-only true)
98+
;; (intersection #F read-only true)
99+
;; (difference #F read-only true)
100+
;; (subset? #F read-only true)
101+
;; (rank #F read-only true)
102+
;; )
103103

104104
;; Written out by hand, using vectors:
105105
;;

Sources/LispKit/Runtime/VirtualMachine.swift

+57-8
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ public final class VirtualMachine: TrackedObject {
198198
/// Will be set to true if the `exit` function was invoked.
199199
public internal(set) var exitTriggered: Bool = false
200200

201+
/// When set to true, will print call and return traces
202+
public var traceCalls: Bool = false
203+
204+
201205
/// Initializes a new virtual machine for the given context.
202206
public init(for context: Context) {
203207
self.context = context
@@ -563,23 +567,24 @@ public final class VirtualMachine: TrackedObject {
563567
*/
564568

565569
private func exitFrame() {
570+
let fp = self.registers.fp
566571
// Determine former ip
567-
guard case .fixnum(let newip) = self.stack[self.registers.fp &- 2] else {
572+
guard case .fixnum(let newip) = self.stack[fp &- 2] else {
568573
preconditionFailure()
569574
}
570575
self.registers.ip = Int(newip)
571576
// Determine former fp
572-
guard case .fixnum(let newfp) = self.stack[self.registers.fp &- 3] else {
577+
guard case .fixnum(let newfp) = self.stack[fp &- 3] else {
573578
preconditionFailure()
574579
}
575580
// Shift result down
576-
self.stack[self.registers.fp &- 3] = self.stack[self.sp &- 1]
581+
self.stack[fp &- 3] = self.stack[self.sp &- 1]
577582
// Clean up stack that is freed up
578-
for i in (self.registers.fp &- 2)..<self.sp {
583+
for i in (fp &- 2)..<self.sp {
579584
self.stack[i] = .undef
580585
}
581586
// Set new fp and sp
582-
self.sp = self.registers.fp &- 2
587+
self.sp = fp &- 2
583588
self.registers.fp = Int(newfp)
584589
// Determine closure to which execution returns to
585590
guard case .procedure(let proc) = self.stack[Int(newfp) - 1] else {
@@ -594,17 +599,19 @@ public final class VirtualMachine: TrackedObject {
594599
self.registers.code = newcode
595600
}
596601

597-
private func getStackTrace() -> [Procedure] {
602+
internal func getStackTrace() -> [Procedure] {
598603
var stackTrace: [Procedure] = []
599604
var fp = self.registers.fp
600605
while fp > 0 {
601606
guard case .procedure(let proc) = self.stack[fp &- 1] else {
602-
preconditionFailure()
607+
// This may happen if an error is thrown
608+
return stackTrace
603609
}
604610
stackTrace.append(proc)
605611
if fp > 2 {
606612
guard case .fixnum(let newfp) = self.stack[fp &- 3] else {
607-
preconditionFailure()
613+
// This may happen if an error is thrown
614+
return stackTrace
608615
}
609616
fp = Int(newfp)
610617
} else {
@@ -614,6 +621,42 @@ public final class VirtualMachine: TrackedObject {
614621
return stackTrace
615622
}
616623

624+
@inline(__always) private func printCallTrace(_ n: Int, tailCall: Bool = false) {
625+
if self.traceCalls && self.sp > (n &+ 1) {
626+
if case .procedure(let proc) = self.stack[self.sp &- n &- 1] {
627+
let stackTrace = self.getStackTrace()
628+
var builder = StringBuilder()
629+
let offset = tailCall ? 0 : 1
630+
builder.append(tailCall ? "↪︎ (" : "➝ (",
631+
width: (stackTrace.count + offset) * 2 + 3,
632+
alignRight: true)
633+
builder.append(proc.originalName ?? proc.name)
634+
for i in 0..<n {
635+
builder.append(" ", self.stack[self.sp &- n &+ i].description)
636+
}
637+
builder.append(")")
638+
if stackTrace.count > 1 {
639+
builder.append(" in ", stackTrace.last!.originalName ?? stackTrace.last!.name)
640+
}
641+
builder.append("\n")
642+
self.context.console.print(builder.description)
643+
}
644+
}
645+
}
646+
647+
@inline(__always) private func printReturnTrace(tailCall: Bool = false) {
648+
if self.traceCalls && self.sp > 0 {
649+
var builder = StringBuilder()
650+
let offset = tailCall ? 0 : 1
651+
builder.append(tailCall ? "↩︎ " : "",
652+
width: (self.getStackTrace().count + offset) * 2 + 2,
653+
alignRight: true)
654+
builder.append(self.stack[self.sp &- 1].description)
655+
builder.append("\n")
656+
self.context.console.print(builder.description)
657+
}
658+
}
659+
617660
private func invoke(_ n: inout Int, _ overhead: Int) throws -> Procedure {
618661
// Get procedure to call
619662
guard case .procedure(let p) = self.stack[self.sp &- n &- 1] else {
@@ -1184,14 +1227,18 @@ public final class VirtualMachine: TrackedObject {
11841227
// Push top value onto stack again
11851228
self.push(top)
11861229
case .call(let n):
1230+
self.printCallTrace(n, tailCall: false)
11871231
// Store instruction pointer
11881232
self.stack[self.sp &- n &- 2] = .fixnum(Int64(self.registers.ip))
11891233
// Invoke native function
11901234
var m = n
11911235
if case .closure(_, let newcaptured, let newcode) = try self.invoke(&m, 3).kind {
11921236
self.registers.use(code: newcode, captured: newcaptured, fp: self.sp &- m)
1237+
} else {
1238+
self.printReturnTrace(tailCall: false)
11931239
}
11941240
case .tailCall(let m):
1241+
self.printCallTrace(m, tailCall: true)
11951242
// Invoke native function
11961243
var n = m
11971244
let proc = try self.invoke(&n, 1)
@@ -1220,6 +1267,7 @@ public final class VirtualMachine: TrackedObject {
12201267
self.sp = self.registers.initialFp &- 1
12211268
return res
12221269
} else {
1270+
self.printReturnTrace(tailCall: true)
12231271
self.exitFrame()
12241272
}
12251273
case .assertArgCount(let n):
@@ -1267,6 +1315,7 @@ public final class VirtualMachine: TrackedObject {
12671315
self.sp = self.registers.initialFp &- 1
12681316
return res
12691317
} else {
1318+
self.printReturnTrace(tailCall: false)
12701319
self.exitFrame()
12711320
}
12721321
case .branch(let offset):

0 commit comments

Comments
 (0)