Structure: Best Practices? Subroutines vs. Main Program
-
DougBrunelle
- Posts: 4
- Joined: 04 May 2015
Structure: Best Practices? Subroutines vs. Main Program
I'm getting back into 6502 assembly programming, and I have been wondering about a couple of things. I'm using Relaunch64 for my IDE, Kick Assembler, and VICE to emulate a C64 (I don't currently have any 6502 hardware).
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this? In some cases, I have stored the registers in memory locations- other times, I have pushed them onto the stack and popped them off the stack after subroutine calls. A related question-- should it be the responsibility of the main program to save the registers, or should it be done by each subroutine? Seems to me that we should be able to jsr to any subroutine and assume that our registers will not be changed upon return from the subroutine.
2. How best to implement other variables? If, for example, I have a main program which uses a zero-page pointer "ScrPtr" at $fb, should the main program be the only code block which manipulates ScrPtr, or is it common practice for subroutines to work with this kind of variable (global)? In some cases, I have subroutines which manipulate pointers and other variables which the main program uses, but I'm not sure if this is a good idea. The thing is, zero-page memory locations are in short supply, so I'm tempted to just treat them as global variables.
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this? In some cases, I have stored the registers in memory locations- other times, I have pushed them onto the stack and popped them off the stack after subroutine calls. A related question-- should it be the responsibility of the main program to save the registers, or should it be done by each subroutine? Seems to me that we should be able to jsr to any subroutine and assume that our registers will not be changed upon return from the subroutine.
2. How best to implement other variables? If, for example, I have a main program which uses a zero-page pointer "ScrPtr" at $fb, should the main program be the only code block which manipulates ScrPtr, or is it common practice for subroutines to work with this kind of variable (global)? In some cases, I have subroutines which manipulate pointers and other variables which the main program uses, but I'm not sure if this is a good idea. The thing is, zero-page memory locations are in short supply, so I'm tempted to just treat them as global variables.
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: Structure: Best Practices? Subroutines vs. Main Program
The way I see it, highly-modular coding techniques are an advantage for large systems intended to serve multiple programmers. If it's your own system, you are certainly able to choose the level of modularity that makes you the most comfortable and productive. If it's someone else's system, you are more constrained.
I am in favor of keeping machine code size to a minimum, even if it means sacrificing modular techniques to do so. This includes, but is not limited to, multiple entry and exit points in code blocks, register and flag side-effects, global variable side-effects and re-use for unrelated activities, return address tricks, and fall-through from one code block to the next. I got most of that from studying the code of the great Woz, and doing it that way gives me the most satisfaction. However, it can certainly be argued that it's not the best way to build a maintainable project. I comment my spaghetti code liberally, and that helps both me and others to understand what I'm trying to do, but it isn't necessarily for everyone.
No matter what techniques you choose, you will be making trade-offs. The way I see it, it's your code and your choice, as long as you don't let critics of working code get under your skin ... the 6502 certainly doesn't make any judgments about the code it's executing, and it's the most important participant in my opinion.
Mike B.
I am in favor of keeping machine code size to a minimum, even if it means sacrificing modular techniques to do so. This includes, but is not limited to, multiple entry and exit points in code blocks, register and flag side-effects, global variable side-effects and re-use for unrelated activities, return address tricks, and fall-through from one code block to the next. I got most of that from studying the code of the great Woz, and doing it that way gives me the most satisfaction. However, it can certainly be argued that it's not the best way to build a maintainable project. I comment my spaghetti code liberally, and that helps both me and others to understand what I'm trying to do, but it isn't necessarily for everyone.
No matter what techniques you choose, you will be making trade-offs. The way I see it, it's your code and your choice, as long as you don't let critics of working code get under your skin ... the 6502 certainly doesn't make any judgments about the code it's executing, and it's the most important participant in my opinion.
Mike B.
-
DougBrunelle
- Posts: 4
- Joined: 04 May 2015
Re: Structure: Best Practices? Subroutines vs. Main Program
Yes, I think the challenge is to find a balance between extreme modularity and extreme monolithic code. For my subroutines, I think what I'll do for now is to have them only save registers that they modify- if only .X is changed, then why save .Y and the Accumulator on the stack? That way, my code is still relatively efficient, and the calling subroutine or program doesn't have to worry about registers being changed by subroutine calls.
- floobydust
- Posts: 1394
- Joined: 05 Mar 2013
Re: Structure: Best Practices? Subroutines vs. Main Program
With subroutines, it sorta depends, as in some cases registers are used to either pass parameters to the routine or contain the result of the routine's function, i.e., get a character from a buffer. In any case, preserving working registers in routines is good practice, sans those that are required for results.
What is a basic requirement is that any interrupt service routine saves and restores all registers (including the processor status register) when returning from the ISR (interrupt service routine). This makes it transparent to any code that is executing when the interrupt is generated, unless of course the routine is timing critical.
What is a basic requirement is that any interrupt service routine saves and restores all registers (including the processor status register) when returning from the ISR (interrupt service routine). This makes it transparent to any code that is executing when the interrupt is generated, unless of course the routine is timing critical.
Regards, KM
https://github.com/floobydust
https://github.com/floobydust
- GARTHWILSON
- Forum Moderator
- Posts: 8775
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Structure: Best Practices? Subroutines vs. Main Program
DougBrunelle wrote:
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this?
Quote:
In some cases, I have stored the registers in memory locations
To do that, every subroutine would need its own memory locations. That could end up requiring a lot of memory. It also prevents re-entrant code, which you might need someday.
Quote:
other times, I have pushed them onto the stack and popped them off the stack after subroutine calls.
If something really needs to be saved, this is usually the preferred way.
Quote:
A related question-- should it be the responsibility of the main program to save the registers, or should it be done by each subroutine? Seems to me that we should be able to jsr to any subroutine and assume that our registers will not be changed upon return from the subroutine.
The calling routine presumably knows what the subroutine it's calling does and needs. In most cases, the calling program won't be using A, X, and Y to store data past the duration of the subroutine; but if it does, it (or rather, the person writing the source code) will know if the subroutine changes something, and if so, if it's a desired change or an undesired one, and if anything needs to be done, either in saving something on the stack, or in modifying the subroutine so you don't have to do the same save-and-restore steps in every part of the code that calls that subroutine.
Quote:
2. How best to implement other variables? If, for example, I have a main program which uses a zero-page pointer "ScrPtr" at $fb, should the main program be the only code block which manipulates ScrPtr, or is it common practice for subroutines to work with this kind of variable (global)? In some cases, I have subroutines which manipulate pointers and other variables which the main program uses, but I'm not sure if this is a good idea. The thing is, zero-page memory locations are in short supply, so I'm tempted to just treat them as global variables.
If a subroutine can't change A, X, or Y, and can't change variables either, its usefulness is quite limited. It can output something on the I/O, or do a delay, but otherwise cannot process information and give back results except on a stack (either hardware or virtual). In many cases a stack works well for this; but you will lose a lot of function if you require everything to go through a stack. I think you're going to have to loosen up and let subroutines do what they're there for.
Quote:
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.
In most (not all) cases, the subroutine is for code that gets executed in many places, not just one, and the reason to make the subroutine is so you don't have to repeat that code everywhere it's needed. Again, there are exceptions. As for maintaining the integrity of variables (which can also be pointers—forget about C where they're separate), you can do local variables on the page-1 stack or even a virtual stack after you get going a little more. The '02 doesn't have the needed stack-relative addressing modes like the 65816 does, but stack-relative addressing can be synthesized using an index register and a little overhead which the '816 is free of. My 6502 stacks treatise has a lot on passing parameters, inlining data, local variables and environments, recursion, and a lot more.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- BigDumbDinosaur
- Posts: 9428
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Structure: Best Practices? Subroutines vs. Main Program
Others have answered, but as is my wont, I will throw in my two cents.
Generally speaking, pushing the registers to the stack is the best way to assure transparency. However, you should pick and choose which subroutines need to preserve the registers, as pushing and pulling registers takes time and of course, consumes stack space. You should survey your application to determine just how important register preservation before you go hog-wild and have every subroutine protect registers.
As Garth noted, subroutines are often of limited value if they can't pass results back through the registers. Also, don't waste time and code space pushing registers that won't be touched by the subroutine.
The eight bit members of the 6502 family don't leave you a lot of options with this. You don't have the luxury of stack relative addressing, so you either have to do some potentially convoluted stack acrobatics or you have to allocate memory. My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.
The C-64 is a bit of a pain when it comes to zero page, in that the BASIC interpreter and the "kernal" consume almost all of it, leaving (if memory correctly serves me) only six uncommitted addresses.
My general approach has always been to in-line a routine if it will only used at one location in the program. After all, in eight bit 6502 assembly language there are no "local" variables, which means use of subroutines does not accomplish anything as far as data isolation goes. However, if the same routine is used more than two times and that routine is fairly involved, the code shrink and generality that a subroutine could offer becomes compelling.
Ultimately, as Mike noted, if it's your code for your use, do what's makes you happy. Modularity in assembly language usually produces the most readable and easy-to-maintain code but can become serious busy-work for the programmer if many of the functions can be linearly executed.
DougBrunelle wrote:
1. If we want to keep the .A, .X, and .Y registers in the main program safe from possible destruction by subroutines, how best to structure this?
As Garth noted, subroutines are often of limited value if they can't pass results back through the registers. Also, don't waste time and code space pushing registers that won't be touched by the subroutine.
Quote:
2. How best to implement other variables?
The C-64 is a bit of a pain when it comes to zero page, in that the BASIC interpreter and the "kernal" consume almost all of it, leaving (if memory correctly serves me) only six uncommitted addresses.
Quote:
I have seen the advantages of highly-modular code, and using subroutines liberally, instead of trying to code a monolithic bock which does a lot of things. I have heard that, ideally, our Main code block should be just a list of subroutine calls. I have done this, but the questions still remain as to how to maintain the integrity of global variables, pointers, and the three registers.
Ultimately, as Mike noted, if it's your code for your use, do what's makes you happy. Modularity in assembly language usually produces the most readable and easy-to-maintain code but can become serious busy-work for the programmer if many of the functions can be linearly executed.
x86? We ain't got no x86. We don't NEED no stinking x86!
- commodorejohn
- Posts: 299
- Joined: 21 Jan 2016
- Location: Placerville, CA
- Contact:
Re: Structure: Best Practices? Subroutines vs. Main Program
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call. The other approach is to have the subroutine save only the registers that it uses - the advantage is less code space taken up overall, but the subroutine has no way to know whether it's wasting time saving values the calling routine won't need. The main thing is to pick one and stick with it.
Re: Structure: Best Practices? Subroutines vs. Main Program
Quote:
My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.
- GARTHWILSON
- Forum Moderator
- Posts: 8775
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Structure: Best Practices? Subroutines vs. Main Program
I took it to be because the C64 leaves you so little ZP space. I was surprised he said it was so little. I found a good home for all our C64 stuff a couple of years ago, someone who really valued it. One of our sons used the C64's a lot in his junior-high years (but not for games!), but I myself never looked into that detailed information which was in one of the books. I know Forth has been run on the C64 though, and I expect they still put the data stack in ZP, so I wonder if they found an area they could copy out into higher memory while Forth was running, like maybe a range of ZP memory that BASIC used.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- barrym95838
- Posts: 2056
- Joined: 30 Jun 2013
- Location: Sacramento, CA, USA
Re: Structure: Best Practices? Subroutines vs. Main Program
commodorejohn wrote:
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call.
Quote:
The other approach is to have the subroutine save only the registers that it uses - the advantage is less code space taken up overall, but the subroutine has no way to know whether it's wasting time saving values the calling routine won't need. The main thing is to pick one and stick with it.
Mike B.
Re: Structure: Best Practices? Subroutines vs. Main Program
barrym95838 wrote:
commodorejohn wrote:
There are two main approaches you can take to saving registers. One is to have the calling routine save registers - the advantage to this technique is that the caller doesn't need to save any registers that it doesn't care about having clobbered, but the disadvantage is that that's extra code around every subroutine call.
Therefore non-leaf routines, and also leaf routines, will use memory for their state, generally zero page where they can and always zero page when necessary for pointers, and so again planning and coordination is needed to decide which locations are to be used by which routines. It's not uncommon to have some shared locations, used by different routines at different times.
It's storage management from the world of the 1960s, which predates the storage management we're used to now.
Or, you can use more modern techniques, but the result will probably be larger, or slower, or not be so recognisably 6502 idiom.
- BigDumbDinosaur
- Posts: 9428
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Structure: Best Practices? Subroutines vs. Main Program
Arlet wrote:
Quote:
My advice is to use zero page for variables only if absolutely necessary. Unless the value being saved is a pointer to data or an operation to be done on that value can only be done with a zero page addressing mode it's likely you don't need to use zero page at all, unless performance is a big concern.
GARTHWILSON wrote:
I know Forth has been run on the C64 though, and I expect they still put the data stack in ZP, so I wonder if they found an area they could copy out into higher memory while Forth was running, like maybe a range of ZP memory that BASIC used.
x86? We ain't got no x86. We don't NEED no stinking x86!
Re: Structure: Best Practices? Subroutines vs. Main Program
I didn't realize you were talking about the C64.
Re: Structure: Best Practices? Subroutines vs. Main Program
Sounds like the C64, like Acorn's Beeb, allows you to use a healthy 128 bytes or so if your program only interacts with the OS and is not planning to return to Basic.
That's an example of planning and coordination - separation of the two major users of the 6502. (Acorn's OS has the concept of 'current language' which allows an alternate environment - application or programming language - to claim, use, and then ultimately free the resources it needs.)
That's an example of planning and coordination - separation of the two major users of the 6502. (Acorn's OS has the concept of 'current language' which allows an alternate environment - application or programming language - to claim, use, and then ultimately free the resources it needs.)
Re: Structure: Best Practices? Subroutines vs. Main Program
Quote:
Sounds like the C64, like Acorn's Beeb, allows you to use a healthy 128 bytes or so if your program only interacts with the OS and is not planning to return to Basic.