Where 16-bit versions of SCI had two blank spaces in their PMachine instruction set, SCI2 introduced the _line_
and _file_
instructions, which the compiler could inject into the final bytecode so that the built-in debugger could then work out exactly which source code line matched the current instruction. Here’s how that goes down in practice.
First we make two test scripts, 42.SC
and 69.SC
:
(script# 42) (procedure Test ) (public Test 1 ) (procedure (Test) (Display "This is Test, (42 1).") )
(script# 69) (procedure TestA TestB ) (public TestA 0 TestB 1 ) (extern Test 42 1 ) (procedure (TestA) (Display "This is TestA, about to call TestB.") (TestB) (Display "Back in TestA, gonna call (42 1).") (Test) (Display "Back in TestA.") ) (procedure (TestB) (Display "This is TestB.") )
This may look a mite different from SCI Companion code because despite everything, they are not the same. Now, compiling them both in SC version 4.100, from January 12 1995, and then pulling them back through a disassembler and annotating it a bit, we get this output:
; 42.SC ;------- Test: _line_ 11 ;(procedure (Test) _file_ "42.sc" _line_ 12 ; (Display "Hello my darling.") push1 lofsa $6 push callk Display, 2 bnot _line_ 13 ;) ret ; 69.SC ;------- TestA: _line_ 17 ;(procedure (TestA) _file_ "69.sc" _line_ 18 ; (Display "This is TestA, about to call TestB.") push1 lofsa $6 push callk Display, 2 _line_ 19 ; (TestB) push0 call TestB, 0 _line_ 20 ; (Display "Back in TestA, gonna call (42 1).") push1 lofsa $2a push callk Display, 2 _line_ 21 ; (Test) push0 calle Test, 0 _line_ 22 ; (Display "Back in TestA.") push1 lofsa $4a push callk Display, 2 _line_ 23 ;) ret TestB: _line_ 25 ;(procedure (TestB) _file_ "69.sc" _line_ 26 ; (Display "This is TestB.") push1 lofsa $59 push callk Display, 2 _line_ 27 ;) ret
Every time the PMachine encounters a _file_
opcode, it grabs a null-terminated string from the bytecode stream and places it into pm.curSourceFile
. Likewise, _line_
takes a 16-bit number and places it into pm.curSourceLineNum
. The built-in debugger can then notice when these two values change, find the source file, and display the correct line of code.
But there’s one tiny detail that threw me off initially. Can you see it?
When TestA
calls Test
, the current source file changes to 69.sc
, but it doesn’t change back afterwards.
Although the SCI2 source I have here doesn’t seem to call it, there is in fact a pair of functions to push and pop debug state, preserving the value of pm.curSourceFile
and pm.curSourceLineNum
across module calls. Which is quite obvious when you think about it. The alternative I can see would be to insert another _file_
opcode after each out-of-module call.