In order for this site to work correctly, and for us to improve the site, we need to store a small file (called a cookie) on your computer.
By continuing to use this website, you agree to our cookies and privacy policy.
  
Home page Home page Home page Home page
Pixel
Pixel Header R1 C1 Pixel
Pixel Header R2 C1 Pixel
Pixel Header R3 C1 Pixel
Pixel

The End of the Line - Mike Pope and Hal Wyman

What R/BASIC statement do you use most often? PRINT? REM? Angle brackets? No, easily the most common statement is an obscure, even invisible, one: LINEMARK. Even if you've never typed it in, you have used it: the compiler puts this statement at the end of every physical line of the program, wherever there is an @FM (but not ';'). (Note: the discussion here pertains to Advanced Revelation 2.1; minor differences obtain in earlier versions.)

If it's so common, what does the LINEMARK statement do? Basically, it has the indirect role of being the place in an executing program where the debugger can grab hold. Consider that the debugger can appear for many reasons (besides the occasional fatal error). One is that [Ctrl-Break] has been pressed; another is if you have set the execution counter; yet another is that you have a trace on a variable or program. Each of these involves flags set by the system.

For example, when you press the break key, not much really happens. About the only activity is that the keyboard handler within AREV.EXE -- not the BIOS, or life would be simpler -- sets a flag stating "break key pressed". Actually doing something about the break key is the function of a higher-level process, and in Advanced Revelation, that's the LINEMARK opcode. Each time this statement is executed that is, at every line it checks the "break key has been hit" flag. If the flag is set, and the "break key enabled" flag is also set (i.e., you haven't executed BREAK OFF), LINEMARK springs into action by calling RTP25, Advanced Revelation's own debugger.

Note that the linemark opcode itself doesn't actually do too much. Its sole function is to check the flags. If it sees one, it calls the debugger to sort out why the flag was set and what to do about it. If you ever wondered why a program with a trace executes so slowly, now you know: it's not only checking the trace flag for each line, but calling the debugger to determine if it's time to call a halt or just carry on to the next line.

The most visible evidence of linemarks is the line counter that appears when you drop into the debugger. Again, though, this is an indirect function. Having decided to call the debugger, the engine first paws through the object code for the currently executing program and analyzes each opcode, counting linemarks, producing at the end a "line count". In this case, the linemark really is nothing more than the passive place marker it is often assumed to be.

Using LINEMARK

There is some overhead involved in having a LINEMARK. An empty loop with LINEMARK runs in 15.16 seconds; without LINEMARK, in 13.97. Obviously, the linemark opcode adds roughly 9% onto the execution speed of a line. Just as interesting is the space taken up by all those bonus linemarks. For example, look at the size of the object code for RTP18 (TCL), a program of about 750 lines (29K), compiled with and without linemarks:

     with      6,312 bytes
     without   5,300 bytes

The space you save would vary with the number of comments, the statements you'd used, etc. But you can see that excluding linemarks can make quite a difference.

To remove linemarks, compile at TCL (not within the editor) using the (L) option.

Do this and you'll also soon experience the downside. No linemark, no checking of flags, no debugger, no stopping the program. Run a long index search, then press [Ctrl-Break]: nothing happens, at least not immediately. Most system programs are compiled -- yes, for speed reasons -- without linemarks. Therefore there is nothing to detect the "break key pressed flag", until, of course, you do actually encounter a linemark, even if the next linemark to happen along is in, say, INPUT.CHAR.

In some system programs you'll stop if you press [Ctrl-Break], but you'll get an absurd line number: "WINDOW, Line 2 ...". It hasn't really stopped on line 2; that's just how many linemarks the engine detected up the point of execution before calling the debugger. This illustrates a compromise you can make when using linemarks. It's efficient to suppress linemarks, but it's drastic: there will be no ex machina way into the program. However, you can add the odd LINEMARK here and there to provide a backdoor for the debugger. In fact, it's a sound piece of advice to always put a LINEMARK statement inside a loop, so:


0001         LOOP
0002            LINEMARK
0003         UNTIL PIGS_FLY REPEAT

None of the above applies to dictionary items. First, the compiler forces an (L) option for dict items, and second, even explicit LINEMARKs are ignored in symbolic fields.

$INSERT statements pose another problem. These cause code to be included into the source; by the time the code gets to the 'real' compiler, it's all one program. Normally, of course, there would be a linemark at the end of each line, and any line counting would be completely out of whack. But the system seems to ignore the lines within the $INSERT as far as counting lines is concerned.

This trick is accomplished using the little known STOPLINECOUNT and STARTLINECOUNT statements that act as switches telling the compiler to do what their names suggest. They are embedded by the compiler's preprocessor around any $INSERT statements. These give you a means within a program of doing what the (L) option does in the compiler, namely to cause linemarks not to be added, with the added advantage of being able to turn linemark suppression off and on for only specific portions of a program.

You can therefore protect critical areas of your program from user interference (and make them execute that slight bit faster), while retaining the flexibility to break into a program at any point. As with the (L) compiler option, these statements have no effect on any hard-coded LINEMARK statements, which remain in place (and executable) no matter what, though the line numbers they report will not be accurate any more.

You now know everything you need to about LINEMARK. As with many humble things, there is more to it than meets the eye. And now at least you can feel that you are on intimate terms with the R/BASIC statement that you use, consciously or otherwise, more than any other in the system.

(Volume 4, Issue 2, Pages 14,15)
Pixel
Pixel Footer R1 C1 Pixel
Pixel
Pixel
Pixel