Signature for ROM based programs

Chat about anything CX16 related that doesn't fit elsewhere
pzembrod
Posts: 94
Joined: Sat Aug 29, 2020 9:21 pm

Signature for ROM based programs

Post by pzembrod »


@kktos Why the boldface "No, please"? What is so horrible about a jump list? In what way would it be less easily expandable than a function id parameter? I don't see that. And I see a number of downsides of a function parameter in A, or the .db function_x, .dw parms_ptr

@BruceMcF Ah, interesting; I realize that my own 65C02 fluidity still needs a good deal of work; I wasn't familiar with JMP (abs, x). Still, this would occupy X, leaving one register less for parameter passing.

In general, a function_id parameter goes against one of my design principles, which is not to needlessly join code execution paths, only to divide them apart again based on a parameter that comes from a constant.

 

TomXP411
Posts: 1781
Joined: Tue May 19, 2020 8:49 pm

Signature for ROM based programs

Post by TomXP411 »



1 hour ago, pzembrod said:




In general, a function_id parameter goes against one of my design principles, which is not to needlessly join code execution paths, only to divide them apart again based on a parameter that comes from a constant



Don't forget the C16 has 32 bytes of memory for passing parameters, starting at address 02. The first 12 bytes are intended for passing volatile parameters, the next 12 are "safe" parameters (ie, not altered by the caller). The full description of the recommended mechanism for passing parameters is in the Programmer's Reference, "New API for the Commander X16"

I do tend to think of the C000 startup address as a "cold start" routine, though. That should be the one that you use the first time you boot the program and to set up the initial memory map and configure the software. I actually do agree that, generally speaking, it should be immune to the values in the registers or the ABI.

For specific applications, such as "open with a file" or "open and print", I would expect those to live in a jump table at the end of the function ROM. Putting the jump table at the end allows you to expand the jump table by growing downward, toward the program code, and (of course) the program code can grow upward, toward the jump table. 

Just remember that the last half-page or so should be copies of the KERNAL jump table and should trampoline over to the KERNAL routines, so code living in User land can consistently call KERNAL routines, no matter which bank is active. So you'd probably want to end your function ROM jump table at $FEFF and grow it downward. 

So... 

C000: jump to the startup routine

C003: program signature: text with program name and version

C010 (approx): start of the program

FE00: empty space, followed by jump table

FEFF: End of Function ROM jump table

FF00: Start of KERNAL jump table and CPU vectors

pzembrod
Posts: 94
Joined: Sat Aug 29, 2020 9:21 pm

Signature for ROM based programs

Post by pzembrod »



40 minutes ago, TomXP411 said:




Don't forget the C16 has 32 bytes of memory for passing parameters, starting at address 02. The first 12 bytes are intended for passing volatile parameters, the next 12 are "safe" parameters (ie, not altered by the caller). The full description of the recommended mechanism for passing parameters is in the Programmer's Reference, "New API for the Commander X16"



Yes, I'm aware. The reference also mentions A, X, Y for byte-sized parms. Quoting:


The 16 bit ABI generally follows the following conventions:



  • arguments


    • word-sized arguments: passed in r0-r5


    • byte-sized arguments: if three or less, passed in .A, .X, .Y; otherwise in 16 bit registers


    • boolean arguments: .C, .N




Incidentally, @Stefan's open-file call at $c003 uses r0 and r1 for the filename pointer and length.


Quote




For specific applications, such as "open with a file" or "open and print", I would expect those to live in a jump table at the end of the function ROM. Putting the jump table at the end allows you to expand the jump table by growing downward, toward the program code, and (of course) the program code can grow upward, toward the jump table.



I'm sorry but I still don't see the point. The program can start after the growing jump table at the beginning of the ROM just as well. If the API is the jump table, then nobody gets hurt if the non-API program code gets shifted by 3 bytes after a new jump gets added to the jump table. It's not like a stack and a heap growing towards each other in a shared RAM space.

The point, as I see it, of a jump table at the end of the ROM is that its position can be established independent of the ROM size, esp. in a case of 6502 machines where the ROM will be at the end of the address space. So, the same jump table works equally well and without change e.g. in 8k KERNALs (VIC20, C64) or 16k KERNALs (C16, C128, X16).


Quote




Just remember that the last page or so should be copies of the KERNAL jump table and should trampoline over to the KERNAL routines, so code living in User land can consistently call KERNAL routines, no matter which bank is active. So you'd probably want to start your function ROM jump table at $FE00



Exactly the point why I would not place my own jump table at the end of the ROM. It even goes against extensibility. If I start my own ROM jump table at $FE00, I'd be in trouble as soon as the X16 KERNAL table grows down beyond that point. So, the two fixed-address things I would have grow towards each other would be my own jump table and the KERNAL jump table. My program could live happily in whatever space it finds in between.

BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Signature for ROM based programs

Post by BruceMcF »



6 hours ago, pzembrod said:




@kktos Why the boldface "No, please"? What is so horrible about a jump list? In what way would it be less easily expandable than a function id parameter? I don't see that. And I see a number of downsides of a function parameter in A, or the .db function_x, .dw parms_ptr



@BruceMcF Ah, interesting; I realize that my own 65C02 fluidity still needs a good deal of work; I wasn't familiar with JMP (abs, x). Still, this would occupy X, leaving one register less for parameter passing.



In general, a function_id parameter goes against one of my design principles, which is not to needlessly join code execution paths, only to divide them apart again based on a parameter that comes from a constant.



 



Though it's an option, not a requirement ... given a vector table, JMP (opvector3) also works, if you know the address of "opvector3".

 

I understand the benefit of a jump table at the end of the ROM if it's an actual EPROM being written to incrementally, since you can add code to the not yet burned space following the code at the beginning and add the next entry in the jump table to the net yet burned space below the already written jumps, and also when you have page-boundary-sensitive 6502 code and don't want your carefully stacked house of cards to come tumbling down, but I reckon with flash ROM and with the 65c02 fixing the vector page crossing bug, it's more six of one and half dozen of the other with putting the vector / jump table at the beginning or the end.

pzembrod
Posts: 94
Joined: Sat Aug 29, 2020 9:21 pm

Signature for ROM based programs

Post by pzembrod »



3 hours ago, BruceMcF said:




I understand the benefit of a jump table at the end of the ROM if it's an actual EPROM being written to incrementally, since you can add code to the not yet burned space following the code at the beginning and add the next entry in the jump table to the net yet burned space below the already written jumps,



Ah! I never thought of that work flow. Thanks for the insight!


3 hours ago, BruceMcF said:




and also when you have page-boundary-sensitive 6502 code and don't want your carefully stacked house of cards to come tumbling down,



I just realize that the page boundary bug is something of which I may have been not actively aware enough. Good reminder.


3 hours ago, BruceMcF said:




but I reckon with flash ROM and with the 65c02 fixing the vector page crossing bug, it's more six of one and half dozen of the other with putting the vector / jump table at the beginning or the end.



 

kktos
Posts: 50
Joined: Wed Dec 09, 2020 4:32 pm

Signature for ROM based programs

Post by kktos »



10 hours ago, pzembrod said:




@kktos Why the boldface "No, please"? What is so horrible about a jump list? In what way would it be less easily expandable than a function id parameter? I don't see that. And I see a number of downsides of a function parameter in A, or the .db function_x, .dw parms_ptr



@BruceMcF Ah, interesting; I realize that my own 65C02 fluidity still needs a good deal of work; I wasn't familiar with JMP (abs, x). Still, this would occupy X, leaving one register less for parameter passing.



In general, a function_id parameter goes against one of my design principles, which is not to needlessly join code execution paths, only to divide them apart again based on a parameter that comes from a constant.



 



? It was me saying oh no, not again ? But I'm smiling while writing it down. No worries ?

So you asked a very good question: why ? Good, let's think about it.



- list of JMPs

-1- (Weak argument) you'll have a 3 bytes * n addresses, bigger than a 2 bytes * n.

-2- (Weak argument) you'll have to remember a lot of addresses



- vectors table

-1- you'll have a single point of entry

-2- your API could be very easily consistent and coherent. You can have a stub code dealing with the parms and then calling the internal function. you're making a toolbox rather than a bag of tricks.

-3- for the dev, once he gets how to deal with your entrypoint API, job's done. Pretty easy to use.

-4- (Weak argument) pretty easy to add, change or remove a function as all calls go thru the stub.



About how to pass the parms, it's open.

From a dev point of view, I like to call "things" that are not messing with my code. In other words, I have only a few registers on my 6502. please, don't scrambled them.

Therefore, I prefer to go towards the parms after the the JSR entrypoint so I'm not using the registers for the call.

But there are other ways to do that.

The main point here is that I want the call to have the minimalistic impact on my code.

BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Signature for ROM based programs

Post by BruceMcF »



8 hours ago, kktos said:




About how to pass the parms, it's open.



From a dev point of view, I like to call "things" that are not messing with my code. In other words, I have only a few registers on my 6502. please, don't scrambled them.

Therefore, I prefer to go towards the parms after the the JSR entrypoint so I'm not using the registers for the call.

But there are other ways to do that.

The main point here is that I want the call to have the minimalistic impact on my code.



I don't think how to pass parameters can be anything but open, but it was not lack of awareness of how coding the 6502 works which accounts for the way that KERNAL calls use the registers, but familiarity.

The high overhead of passing parameters in a data block after the subroutine call only gets higher if you force it to have a generic place other than the stack to stash the A register as you do: STA TA : STY TY : PLA  : STA PARMS : PLA : STA PARMS+1 : LDY #1 : STA TA : LDY #1 : LDA (PARMS),Y : CLC : ADC PARMS : TAY : LDA PARMS+1 : ADC #0 : PHA : PHY : .... and the LDY TY : LDA TA before returning.

And your code may be using A, may be using Y, may be using both, may be using neither, it is much cleaner, if it is using Y, to:

PHY : JSR op5 :  .byte 15," ParameterString" : PLY ...

Indeed, it is lower overhead if you branch over the parameter string yourself, and it is known that the parameter string is 3 bytes after the subroutine vector:

PHX : LDX #10 : JSR ROMSTART : BRA ret1 : .byte "ParameterString", 0

ret1: PLX ...

... calling JMP (OP0,X) ...

... which as a parameter string routine, does: TSX : LDA $101,X : CLC : ADC #3 : STA PARMS : LDA $102,X : ADC #0 : STA PARMS+1 ...

But all of that is programmer preference. The API is open in the sense that there is no way to enforce an API. Enforcing a ROM signature format is only done by having tools which work using that format.

kktos
Posts: 50
Joined: Wed Dec 09, 2020 4:32 pm

Signature for ROM based programs

Post by kktos »



6 minutes ago, BruceMcF said:




But all of that is programmer preference.



? That's the very definition of the developer main trait. :):)

So, yes, the API is open. And to each choice, we have PROS and CONS. Either for the sake of readability or for the sake of cycles count.



-1- fastcall

all in registers, A, X and Y.


  • lda #function_id


  • ldx #<parms


  • ldy #<parms


  • jsr ENTRYPOINT


  • bcc :+


  • sta error


  • rts


  • :keep_going


scrambled: A, X, Y

that means you are to manage our registers, save them if needed. And this for ALL our code. Meaning that the cleanup is on us.

That's the lazy way from the point of view of the library developer.

And quite an annoyance for the developer using it.

The call is fast but we will have to manage an overhead on each call.

-2- stack call (pascal/c)


  • lda #function_id


  • pha


  • lda #<parms


  • pha


  • lda #<parms


  • pha


  • jsr ENTRYPOINT


  • bcc :+


  • sta error


  • rts


  • :keep_going


scrambled: A

Here, the stack is used to pass all parms. The stack cleanup has to be done either by the callee or the caller.

-3- "magic" stack call


  • jsr ENTRYPOINT


  • .byte function_id


  • .word parms


  • bcc :+


  • sta error


  • rts


  • :keep_going


scrambled: none

like the stack call but using the RTS address as pointer. Very compact and could be applied on any calls.

Those are the cleanest ways to do API. The 1 and 2 are the ones used by a lot of compilers, if not all. The 3rd is very ASM oriented. And I only saw this method on 6502. With 6809 and 68K, stack is the way. But we have 2 stack pointers ?

BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Signature for ROM based programs

Post by BruceMcF »


To be sure, if you want, efficiency can be called laziness ... IT CAN be laziness, but it can also be freeing up clock cycles to get more done. Only the caller knows which registers are in use at the time of the call, and saving & restoring a register that doesn't have a value in it that will be used by the caller is a pure inefficiency.

But this only has anything to do with signing ROM blocks if the specific opcode at the traditional ROM startup up point is a defined part of the signature to tell whether the signature is present in the block. If it's just a reserved three bytes before the signature is given, it's entirely on the ROM code author how to access their ROM.

IF what the menu selection of a named ROM does is do a far call to that ROM block at $C000, it could well be an initialization routine.

kktos
Posts: 50
Joined: Wed Dec 09, 2020 4:32 pm

Signature for ROM based programs

Post by kktos »



56 minutes ago, BruceMcF said:




To be sure, if you want, efficiency can be called laziness ... IT CAN be laziness, but it can also be freeing up clock cycles to get more done. Only the caller knows which registers are in use at the time of the call, and saving & restoring a register that doesn't have a value in it that will be used by the caller is a pure inefficiency.



oops. Looks like I felt into the web trap. ?

Bruce, my apologies. Laziness is too strong a word to be used here. I shouldn't have use it. Around a beer, talking with you, yes. But not in writing.

Again, apologies.

Post Reply