FN Variable Format in Commodore BASIC

Maybe intersting, maybe amusing:

1 Like

Here’s a thing though, the formal parameter of a function isn’t a global variable, at least according to

5 X=7
10 DEFFNR(X)=1+X*X
15 PRINT X
20 PRINT FNR(3)
25 PRINT X

which prints 7, 10, 7. And this seems right to me!

There are still some open ends. Still, the variable for X is created together with the FNR at the same time (if there isn#t any).

10 DEFFNR(X)=1+X*X
20 Y=1

→ Show Variables (lists variables as in memory, which mirrors their time of creation, normally their first use)

FNR = «1+X*X»
X   =  0
Y   =  1

So the function parameter (argument) X is a real global variable. Remains the question, what happens in case of a conflict (as in your above example).

Moreover, it isn’t accessible as one, even without there being any conflict:

PRINT FNR(2)
 5

PRINT X
 0

Also, still:

FNR = «1+X*X»
X   =  0
Y   =  1

I assume, the value part (E-M-M-M-M) of the variable is temporary saved in another location, before the variable is accessed as an arguement, and then restored again. (In which case we may rather speak of a local variable.) To be sure what’s happening exactly, we really have to read the ROM code.

But this probably worth an amendment.

Edit: Updated, compare the added section at the bottom of the article.

1 Like

It’s interesting, but Isn’t this the same as every version of MS 6502 BASIC? Why is this unique to the Commodore/PET version…

-Gordon

Let’s say, Commodore BASIC is especially sparse on variables (as there is no single and double discrimination, just integer – and even this one is somewhat compromized), so this may not be the 4th type in other versions of MS BASIC.
Moreover, I guess, those versions having singles and doubles may use the pattern with a sign-bit set for the first byte of the variable name, but not on the second byte, already for single/double identification, since there are 4 basic types even without FN. — Which poses the question, what mechanism is used then?

P.S.: Why the PET specifically? Well, I’m just showing off my disassembly routines… :slight_smile:

The source code is freely available now. No need to disassemble.

I used it a while back to boot CBM Basic2 on my Ruby 6502 system where I get the same:

LIST

 10 A1=2.345
 20 I2%=258
 30 A3$="BLA"
 40 DEF FNR(X)=1+X*X
 50 PRINT FNR(3)
READY.
RUN

 10 

READY.
*MD 0 0E00
00.0E00: 00 0E 0E 0A:00 41 31 B2:32 2E 33 34:35 00 1A 0E  |      A1 2.345    |
00.0E10: 14 00 49 32:25 B2 32 35:38 00 28 0E:1E 00 41 33  |   I2% 258 (   A3 |
00.0E20: 24 B2 22 42:4C 41 22 00:3A 0E 28 00:96 20 A5 52  | $ "BLA" : (    R |
00.0E30: 28 58 29 B2:31 AA 58 AC:58 00 46 0E:32 00 99 20  | (X) 1 X X F 2    |
00.0E40: A5 52 28 33:29 00 00 00:41 31 82 16:14 7A E2 C9  |  R(3)   A1   z   |
00.0E50: B2 01 02 00:00 00 41 B3:03 23 0E 00:00 D2 00 34  |       A  #     4 |
00.0E60: 0E 66 0E 31:58 00 00 00:00 00 00 AA:AA AA AA AA  |  f 1X            |
00.0E70: AA AA AA AA:AA AA AA AA:AA AA AA AA:AA AA AA AA  |                  |

It looks like the bytes after the variable definition of R are: D2 00, 340E 660E 31 where 0E34 is the address of the function definition in the code. Not sure what 0E66 might be though - it’s pointing to 5 zeros - maybe the place to store the global X ?

-Gordon

I do know that on any FN call the pointer for the argument (in the PET version) is stored in the zero-page location else used for the string pointer and the 5th byte is stored in the adjacent byte normally used for the string length. It may be a coincidence, but it may be also that this is then used to copy the contents of the variable into the string area. (Why the 5th byte is copied at all, is beyond my understanding, as it is clearly not used, nor is the value of the first byte to copy of any advantage for any such utility routine.)

An annotated disassembly of the C64 ROM (the PET will be generally the same, but uses different addresses) is available at pagetable, here a direct link to the DEFFN related code:

https://www.pagetable.com/c64ref/c64disasm/#B3DE

(It does use a somewhat unfamiliar assembler syntax, though. Still, aggregating multiple interpretations of the code side by side is quite a feat.)

And, here ($B449) we see, the variable content is first pushed to the stack and then pulled again in order to resore it. (The annotations do not especially help, as they are confusing the function and the variable.)

Did it simplify the code, always to use 2 byte header and 5 byte values? (And surely it does simplify, not to bother to zero out any byte which isn’t needed.)

Have a look at the variable name just before this, at 0xE64-0xE65, which is 0x58 (ASCII “X”) and 0, giving the signature for a float (no sign-bits set) named “X”.
(As we’ve seen in this thread, the global X is used for the parameter, but any global content is first pushed to the stack and then restored from this. So it’s more like a local variable.)

That’s definitely the general idea. All simple variables in Commodore BASIC use 7 bytes in total, 2 for the name and 5 bytes for the value (which is only needed to store a floating point number). Mind that any surplus bytes in integers (uses just two bytes) and string (3 bytes used for length and pointer) are normally filled by zeros, bot not for the FN type.

(When it comes to arrays, things are different, though, and variables are stored at the minimal required size, 5 for floats, 2 for integers, and 3 for strings. Also, BASIC attempts to work out on DIM what range and depth may be available with the given memory and there are no fixed limits. Arrays are stored by name, 2 bytes including the usual type marks, a skip-offset for the entire array, the number of dimensions, a byte for each dimension, from the highest to the lowest in reverse order, followed by the individual values for the respective subscripts with the first dimension rotating fastest. You can play around with this in the emulator and “Disassemble Variables” and “Show Variables” in the “Utils/Export” menu. The latter provides a dump, just like the values would be printed in BASIC.)