9

When running an Applesoft program under ProDOS, BASIC.System watches for OS commands by looking for PRINT statements that start with Ctrl+D. This is similar, but not equivalent, to the DOS behavior. For example, this works in either:

10 PRINT CHR$(4) "CATALOG"

but this only works in DOS:

10 PRINT CHR$(13) CHR$(4) "CATALOG"

The change comes from a difference in the way BASIC.System watches for disk commands. This involves the use of the Applesoft TRACE feature.

To expose this behavior, take a simple program:

10 TEXT:NORMAL
20 PRINT "HI"

If RUN normally, it just prints "HI". If you redirect the I/O hooks to ROM and run it, using :PR#0:IN#0:RUN (the leading ':' is important), it executes as if the Applesoft tracing feature is enabled:

#10 #10 #20 HI

BASIC.System keeps track of whether tracing has been enabled manually, with a flag at $BE41.

One advantage of the ProDOS approach is that Applesoft tracing works better. Under DOS, printed commands must appear at the start of a line, so tracing prevents commands from working with a simple Ctrl+D because everything is prefixed with a line number. (This new-line-required behavior is why many DOS programs used CHR$(13) + CHR$(4) to signal a DOS command.)

This raises a couple of questions:

  • How did the BASIC.System approach work?
  • What advantages did it have over the DOS approach?
  • What were the performance trade-offs?

The only documentation I've been able to find on this is a brief mention in the "differences between DOS and ProDOS" section of Beneath Apple ProDOS, which says:

BASIC programs which are computationally oriented will run about four percent slower on ProDOS than they did under DOS. This is because the ProDOS BASIC Interpreter leaves Applesoft TRACE running (invisibly) at all times so that it can monitor the execution of the program and perform garbage collection and disk commands. On the other hand, if strings or disk accesses are used, this degradation of performance will be more than offset by improvements in these areas.

1 Answer 1

5

IIRC using trace serves two purposes, both related to stability and performance:

Improving command detection

With TRACE enabled BASIC prints the line number `prefixed by a '#' character and suffixed by a space whenever a new BASIC statement is executed. Following that trace output BASIC.SYSTEM can detect whenever a new statement starts. If the next printed character is the DOS command character (*1) then the following output, until line end, will be checked as (Pro)DOS command.

This solves an issue DOS had with commands following a 'dangling' PRINT. To avoid unintended command detection, or swallowing of binary print data, DOS looked for its command indicator only after a previous CR. If the last print before a DOS command did not end with an CR the command was not detected . Such errors could be quite hard to detect within the usual spaghetti code BASIC.

By using trace this was avoided. Each and every Print that started with the command indicator would be noticed and acted on (*2)

Improving speed due better memory handling

Applesoft isn't a bad design (*3) per se, but it got some quirks. One of them is string storage. The strategy for string management is to use a heap where each newly created or modified string is simply put on top, while any older version is left dangling. The heap only gets reorganized (compacted) when there is no more free heap space at the top (*4). Usually not a bad strategy, as it reduces shovelling data in memory by not doing it until really needed and then in a more efficient fashion.The process is called garbage collection and incredible inefficient in Applesoft. Depending on fragmentation running it can take anywhere from several seconds to more than a minute. Usually more to the upper end.

So far no issue, as Applesoft is still Applesoft, right? Yes, it is, but it gets worse under ProDOS as now way less memory is available. ProDOS plus BASIC.SYSTEM needs about twice as much RAM as DOS 3.3. Moving ProDOS into the language card still leaves any II with about 2 KiB less free memory due the size of BASIC.SYSTEM, file buffers and alike which all need to reside in standard memory.

As a result the same program will experience more often a garbage collection under ProDOS than it did under DOS. We're not talking 'Hello World' type programs or such without much string handling, but common interactive programs, or data handling programs.

To elevate this BASIC.SYSTEM brings it's own, way more efficient garbage collection for Applesoft (*5,6). There's just the minor issue that when Applesoft issues a garbage collection it will always be it's own, as that's hard coded in ROM. No way around (*7).

To avoid this BASIC.SYSTEM needs to do a garbage collection before Applesoft decides to do so. This is where TRACEing again comes in handy. BASIC.SYSTEM gets called every line and statement within, so no problem to do a garbage collection when needed. To do so it checks the remaining free space (comparing a ZP pointer against a saved value) at the begin of each line and starts the cleaning just before Applesoft would :))

As a result, string heavy programs run faster under ProDOS despitte having less memory :))


*1 - $04 aka CHR$(4) or D$

*2 - Of course this created, depending on program structure, some new issues with binary output - You can't have everything.

*3 - well, it's MS Basic, so let's skip that discussion :))

*4 - Yes, I know it's growing from memory top downward. This is about the logical top of the heap.

*5 - While Applesofts garbage collection can be invoked using PRINT FRE(0), ProDOS' version is called by doing PRINT CHR$(4);"FRE" withing a program

*6 - The difference is rather easy to detect, as ProDOS' FRE and Applesoft's FRE(0) can both be used at will. Write a program doing a bunch of string operations - like appending an empty string to some existing one in a loof (just make sure it doesn't fill up all memory) and then issue either version.

*7 - Also no way to move Applesoft into Language card and patch it there, as that space is already used by ProDOS.

10
  • In order for the ProDOS STORE command to work, it must consolidate strings in memory. Interestingly, it does this differently when using STORE than when using FRE, but I suspect that some common logic is used for both operations before the STORE operation produces copies of all strings held within the BASIC program text.
    – supercat
    Jan 16, 2023 at 23:03
  • @supercat does it, or are you mixing ProDOS' FRE with Applesoft's FRE(0)?
    – Raffzahn
    Jan 16, 2023 at 23:45
  • Do something like 10 A$="SUPERCALIFRAGILISTICEXPIALIDICIOUS":DIM B$(100):FOR I=0 TO 100:B$(I)=A$:NEXT:PRINT FRE(0):PRINT CHR$(4);"FRE":PRINT FRE(0):PRINT CHR$(4);"STORE X":PRINT FRE(0) and you'll notice that FRE and STORE work differently.
    – supercat
    Jan 17, 2023 at 15:29
  • Normally, A$="SUPERCALIFRAGLISITICEXPIALIDOCIOUS" will store the address of the string literal into A$, and the B$(I)=A$ assignments will copy that address (I was a bit surprised to discover that such assignments will make a duplicate string if the original isn't a literal). The STORE command creates a new GC-managed string for each reference to a literal string in B$. Though now I've got another interesting idea to try--I wonder how FRE will interact with programs that use a few PEEK and POKE operations to implement a crude generational GC?
    – supercat
    Jan 17, 2023 at 15:54
  • @supercat To create a copy you need to do a string operation. A$="A": B$=A$ will just copy the pointer, as you already know. But A$="A": B$=A$+"" (adding an empty string) will create a copy. And it will do so every time executed, leaving a trail of unused strings with the same content filling up memory. Also the behaviour of STORE ofcourse includes literals. That's whyanyone who wants to use any kind of program chaining doesassignlitterals to strings as part of string operations as in H$="Hello"+"" Was already that under DOS.
    – Raffzahn
    Jan 17, 2023 at 16:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .