@@ -198,6 +198,10 @@ public final class VirtualMachine: TrackedObject {
198
198
/// Will be set to true if the `exit` function was invoked.
199
199
public internal( set) var exitTriggered : Bool = false
200
200
201
+ /// When set to true, will print call and return traces
202
+ public var traceCalls : Bool = false
203
+
204
+
201
205
/// Initializes a new virtual machine for the given context.
202
206
public init ( for context: Context ) {
203
207
self . context = context
@@ -563,23 +567,24 @@ public final class VirtualMachine: TrackedObject {
563
567
*/
564
568
565
569
private func exitFrame( ) {
570
+ let fp = self . registers. fp
566
571
// 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 {
568
573
preconditionFailure ( )
569
574
}
570
575
self . registers. ip = Int ( newip)
571
576
// 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 {
573
578
preconditionFailure ( )
574
579
}
575
580
// 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 ]
577
582
// 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 {
579
584
self . stack [ i] = . undef
580
585
}
581
586
// Set new fp and sp
582
- self . sp = self . registers . fp &- 2
587
+ self . sp = fp &- 2
583
588
self . registers. fp = Int ( newfp)
584
589
// Determine closure to which execution returns to
585
590
guard case . procedure( let proc) = self . stack [ Int ( newfp) - 1 ] else {
@@ -594,17 +599,19 @@ public final class VirtualMachine: TrackedObject {
594
599
self . registers. code = newcode
595
600
}
596
601
597
- private func getStackTrace( ) -> [ Procedure ] {
602
+ internal func getStackTrace( ) -> [ Procedure ] {
598
603
var stackTrace : [ Procedure ] = [ ]
599
604
var fp = self . registers. fp
600
605
while fp > 0 {
601
606
guard case . procedure( let proc) = self . stack [ fp &- 1 ] else {
602
- preconditionFailure ( )
607
+ // This may happen if an error is thrown
608
+ return stackTrace
603
609
}
604
610
stackTrace. append ( proc)
605
611
if fp > 2 {
606
612
guard case . fixnum( let newfp) = self . stack [ fp &- 3 ] else {
607
- preconditionFailure ( )
613
+ // This may happen if an error is thrown
614
+ return stackTrace
608
615
}
609
616
fp = Int ( newfp)
610
617
} else {
@@ -614,6 +621,42 @@ public final class VirtualMachine: TrackedObject {
614
621
return stackTrace
615
622
}
616
623
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
+
617
660
private func invoke( _ n: inout Int , _ overhead: Int ) throws -> Procedure {
618
661
// Get procedure to call
619
662
guard case . procedure( let p) = self . stack [ self . sp &- n &- 1 ] else {
@@ -1184,14 +1227,18 @@ public final class VirtualMachine: TrackedObject {
1184
1227
// Push top value onto stack again
1185
1228
self . push ( top)
1186
1229
case . call( let n) :
1230
+ self . printCallTrace ( n, tailCall: false )
1187
1231
// Store instruction pointer
1188
1232
self . stack [ self . sp &- n &- 2 ] = . fixnum( Int64 ( self . registers. ip) )
1189
1233
// Invoke native function
1190
1234
var m = n
1191
1235
if case . closure( _, let newcaptured, let newcode) = try self . invoke ( & m, 3 ) . kind {
1192
1236
self . registers. use ( code: newcode, captured: newcaptured, fp: self . sp &- m)
1237
+ } else {
1238
+ self . printReturnTrace ( tailCall: false )
1193
1239
}
1194
1240
case . tailCall( let m) :
1241
+ self . printCallTrace ( m, tailCall: true )
1195
1242
// Invoke native function
1196
1243
var n = m
1197
1244
let proc = try self . invoke ( & n, 1 )
@@ -1220,6 +1267,7 @@ public final class VirtualMachine: TrackedObject {
1220
1267
self . sp = self . registers. initialFp &- 1
1221
1268
return res
1222
1269
} else {
1270
+ self . printReturnTrace ( tailCall: true )
1223
1271
self . exitFrame ( )
1224
1272
}
1225
1273
case . assertArgCount( let n) :
@@ -1267,6 +1315,7 @@ public final class VirtualMachine: TrackedObject {
1267
1315
self . sp = self . registers. initialFp &- 1
1268
1316
return res
1269
1317
} else {
1318
+ self . printReturnTrace ( tailCall: false )
1270
1319
self . exitFrame ( )
1271
1320
}
1272
1321
case . branch( let offset) :
0 commit comments