@@ -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