Nested subroutines nightmare.

Talk about anything else you want

Moderator: BigEvilCorporation

Post Reply
Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Nested subroutines nightmare.

Post by Count SymphoniC » Wed Sep 24, 2014 9:06 pm

So far so good guys. But I have a question.

I'm using a lot of nested subroutines, and I'm at the point to where my code is starting to become very messy, and stressful to wade through. So how does one go about turning a set of nested subroutines into a more all purpose subroutine???

Alot of my graphics drawing subroutines uses the same code but with different specific parameters. I need to take these parameters out of the sub's themselves and somehow pass these parameters to the subroutines before the first jsr or jmp. What's the best way to do this? Nice and clean, efficient code is what I want to do.

1) Do I need to use RAM for this?
2) can I get away with stuffing the required parameters in data registers?
3) Can I use the stack for this purpose maybe?

Still a noob here, go easy on me.

twosixonetwo
Very interested
Posts: 58
Joined: Tue Feb 25, 2014 3:38 pm

Re: Nested subroutines nightmare.

Post by twosixonetwo » Thu Sep 25, 2014 8:36 am

Count SymphoniC wrote:1) Do I need to use RAM for this?
2) can I get away with stuffing the required parameters in data registers?
3) Can I use the stack for this purpose maybe?
Using the stack and using RAM is pretty much the same, as the stack lies inside the ram. The only difference between using the stack and having variables in fixed position is that you can't address the variables on the stack absolutely. Using the stack is a reasonable solution for what you are trying to achieve. Pushing to the stack takes a little bit time though, so you might be better of using fixed positions in ram for some variables that are often used (however this seems more reasonable for global variables like player x/y position, then for local argument passing) Using data registers is also possible and might be important if you have to code time critical things like hblank interrupt routines. If you have parameters for drawing subroutines it sounds likely that these parameters are already in the data registers? If so, you might aswell just keep them there and call the subroutine (which uses them then), since this is faster than writing the values to ram first.
You always have to keep track of which subroutine expects which values at which register (or at which point in the stack) though.

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Thu Sep 25, 2014 8:59 pm

I like passing on the stack the best but I develop with both assembly and C combined, where stack passing is the way to call an assembly subroutine from C.

Stuff is moved onto the stack by decrementing it, so:

move.l d0,(-sp) would be putting it on the stack and
move.l (sp+),d0 would be putting it back in the register.

You can also use the PEA instruction to push arbitrary values onto the stack. It's intended for pushing addresses onto the stack but like a lot of stuff in assembly language, you don't have to literally use it as intended.

pea $1 would result in 1 being on the stack, where you can pick it off the same as you would a register pushed to the stack.

You can do something like from within the subroutine being called

movea.l 4(sp),a0
move.l 8(sp),d1
move.l 12(sp),d2

and offset 4 would be argument 1--the first thing pushed onto the stack, offset 8 would be the 2nd and so on. (You don't use 0(sp) because that is the return address of where you jumped from.)

so if you did

(arguments 1 and 2)
pea $1
pea $2

(call your subroutine)
move.l 4(sp),d0
move.l 8(sp),d1

d0 and d1 would contain your 2 arguments, 1 and 2.

There are also schemes where you determine what registers will be used for what and there are some popular layouts, like using certain registers as counters, using certain address registers as a fixed address and then indexing from that address and so on but stack passing is easier and is fine for things where you don't need your code going at maximum speed.

That's just my 2 cents and I'm sure some of the older hands here could be more helpful than I.

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Fri Sep 26, 2014 3:21 am

I found both replies to be very informative. So one thing I'm a bit worried about, is the stack overwriting important variables I have stored in RAM. Wasn't there somewhere in the init code that specified the stack starting location?

On another note, I tried playing with
move.l d0,(-sp)
move.l (sp+),d0
and my assembler complained, am I supposed to omit the + and - from these instructions? I did that and it didn't complain anymore, I just don't know if the code did anything because I didn't test it. Also, can't I use the stack to escape from problem subroutines? (see thread posted in the megadrive section about vertical cursor movement for details, not sure if the return addresses are my problem there or not)

6 days into assembly, and I've made some good progress so far, I solve at least one major problem a day. But I've learned how to interact with Genesis hardware, get stuff to show up on screen, read and write data arrays to and from memory, loops, compares and branching, add/subtract/multiply/divide... I'm still a noob but I never as a kid would've dreamed programming one of my favorite consoles. I need to get a MD and flash cart soon so I can start testing this stuff on real hardware!

One last thing, I've been a bit curious about the z80 language... is it more difficult than 68k? I get the impression that it is. I'm not planning on learning it any time soon, but I can't help but wonder...

tryphon
Very interested
Posts: 316
Joined: Sat Aug 17, 2013 9:38 pm
Location: France

Post by tryphon » Fri Sep 26, 2014 5:31 am

try -(sp) and (sp)+ (operand outside parenthesis)

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Fri Sep 26, 2014 6:40 pm

Yeah I don't know why I wrote it that way, that's wrong. lol. The signs go outside the brackets.

As far as stack overwriting RAM variables it could happen if your stack gets big enough but that's part of the reason the stack is traditionally at the end of RAM. You would need to both nearly fill the 68K ram AND have a very large nested call stack for them to collide though. Remember, when you do the + and - on the sp, what you're actually doing is moving the location of a pointer, so if you're doing it right the sp "shrinks back" at the end of your subroutine call. (If you haven't, read about predecrement and postincrement addressing modes, they're super useful.) So you'd need a very large "function calls function calls function..." You could probably leave a kilobyte on the end, under normal conditions it would be pretty unlikely for your stack to ever get that big unless you're doing huge nesting or passing TONS of arguments on the stack. Think of it as "last in first out" storage, or a literal stack of something like trays. You always add one to the top, and when you take it off you remove from the top. Your program flow will almost certainly be adding and removing items.

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Fri Sep 26, 2014 6:48 pm

Also as far as z80 I wouldn't say it's really more difficult but I don't like it very much... I don't like little endian, I don't like the right to left destination/source syntax, I don't like having registers you can count on one hand. It's also a different kind of thinking than 68k because it's 8 bit. The instructions are less "powerful" and you end up writing a lot of code to juggle registers and stack, and if you're doing anything more than 8 bit integer math it gets more complex (for instance, you don't get any multiply or divide instructions). It is not quite as bad in the context of running a sound engine on the Genesis because mostly it's just writing ports, but I'm not really a fan.

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Fri Sep 26, 2014 6:51 pm

Yeah I've already ran into my stack corrupting my Ram last night, with jmp's and jsr's... trying to solve a ridiculous problem. But still, what a learning experience this is! As complex as it can be, asm is actually really fun for me. So far things are still making sense, which is a good sign. You guys have all been really helpful too. I probably wouldn't have gotten this far even without help.

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Post by HardWareMan » Fri Sep 26, 2014 9:35 pm

I'm not recommend always move data into stack. Much easier once put data (or structures) into RAM and pass to subroutine address pointer in Ax register (except A7 because it is SP). Code safe, data safe. No hazard of buffer overflow in stack. Everyone should get free from x86 frames of programming model (for example, stack nested variables) in case of Motorola M68K (which have big enough count of registers).

powerofrecall
Very interested
Posts: 237
Joined: Fri Apr 17, 2009 7:35 pm
Location: USA

Post by powerofrecall » Fri Sep 26, 2014 11:42 pm

Count SymphoniC wrote:Yeah I've already ran into my stack corrupting my Ram last night, with jmp's and jsr's... trying to solve a ridiculous problem. But still, what a learning experience this is! As complex as it can be, asm is actually really fun for me. So far things are still making sense, which is a good sign. You guys have all been really helpful too. I probably wouldn't have gotten this far even without help.
Make sure to use jsr/bsr when calling subroutines, plain branching without accounting for it can cause you trouble. Also keep in mind that one big advantage of using assembly is you can store true/false flags in 1 bit easily--using BTST and BSET you can store 8 flags in 1 byte of memory. Take advantage of the .b .w .l instructions too for when you don't need all 32 bits of a register to represent something. Or if you need to pass two 16 bit values, you can do it in one data register easily with the SWAP instruction to swap the upper and lower words. Try looking at some Amiga or Atari ST assembly stuff if you get the chance, those people were always doing clever things.

HardWareMan is right too, especially for larger sets of data when you're programming in 68k asm only. I'm just used to passing on the stack because I need to be compatible with my C compiler's argument passing, but you don't have that limitation. By all means don't take any one person's advice as the best or "right" way of doing something, either. There's as many opinions and styles as there are coders.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sat Sep 27, 2014 1:02 am

You have to be EXTREMELY careful of memory usage when using the stack. There's no way to track stack usage, so you can overflow the stack easily. Using global variables means you never have to worry about overflowing memory as the linker tells you when you do so. The stack isn't magic - it uses ram. The more you put on the stack, the more ram is used, and the greater your chance of colliding with data/code in ram.

Even people on devices with (relatively) lots of ram forget that the stack isn't magic storage. One program I helped a person debug, he was using almost 1MB of stack space when the default was 64KB of space for the stack. Once I had him bump the stack up enough to cover the amount of ram he was using, the program worked fine.

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Post by HardWareMan » Sat Sep 27, 2014 4:17 pm

Chilly Willy wrote:One program I helped a person debug, he was using almost 1MB of stack space when the default was 64KB of space for the stack. Once I had him bump the stack up enough to cover the amount of ram he was using, the program worked fine.
Not a problem. Any recursive program with deep enough calling and big enough data set may eat whole RAM. Worse if it can't exit.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sun Sep 28, 2014 12:46 am

HardWareMan wrote:
Chilly Willy wrote:One program I helped a person debug, he was using almost 1MB of stack space when the default was 64KB of space for the stack. Once I had him bump the stack up enough to cover the amount of ram he was using, the program worked fine.
Not a problem. Any recursive program with deep enough calling and big enough data set may eat whole RAM. Worse if it can't exit.
Doesn't even need to be recursive. The program I mentioned merely allocated space on the stack for an arbitrary number of objects... which happened to be several thousand in practice, each one consuming a few hundred bytes. This happened in the physics library - libraries often put everything on the stack so that they are reentrant. Since you're simply calling a library with a list of objects, it's often easy to lose track of how much memory will be used, or where that memory comes from. It's why I don't advocate using "black box" libraries. If you can't see the source, you have no idea what can happen. How much stack the physics library used was NOT part of the documentation. If I hadn't gone through the source and SEEN where the memory was going, he'd have never figured it out. He'd probably just assumed it was a bug and not stack overflow.

If your library needs to be reentrant, to be SAFE, it needs to allocate the memory using the standard heap allocators (or even custom allocation routines) and check for a failure to allocate the memory. It can't just assume the stack will have the space needed. Because so many libraries do just that, modern OSes often use the MMU mapping and exceptions to grow the stack to accommodate these badly written libs. Such a thing isn't possible on the Genesis, so you really need to understand stack usage by EVERY PIECE of the code to avoid colliding with the heap/data/code.

Count SymphoniC
Very interested
Posts: 149
Joined: Sat Nov 17, 2012 3:58 am

Post by Count SymphoniC » Tue Sep 30, 2014 6:05 am

I've learned to keep a very close eye on the ram output in Regen, and an extra close eye on my stack. I'm trying to plan ahead for how much ram I need for all the data tables for the Song/Chain/NoteEntry/Instrument etc screens. It's possible my current method for arrays/tables might be a little convoluted, but until I come up with something better I need as much ram as I can get.

So far I'm using a longword format for each element of the arrays, one word for the element ID (I use this to calculate offsets and where on screen these tables are to be drawn/edited, and the other for the values ( data to be edited).
There's a total of E5/229 elements in the first array for the Song screen. Each of the values in this array point to the chains, which are stored in another array which has 4FF/1,279 entries. Chains are groups of phrases, and although only a total of FF phrases can be used in the Chain editor, each row on the Chain editor needs to be visually accounted for and they can be reused for patterns... Since only a maximum of FF phrases can be stored, there will FF/255 elements of the Note entry array... but this is where the dynamics change, my longword format won't work anymore because I now need values for not only musical notes, but also velocity/volume, instruments, and tracker commands... and I don't yet know if tracker commands is going to be longword/2 longwords. I still need to know more about the Genesis/MD capabilites. So long story short, I need to prepare ahead of time because my arrays are about to grow exponentially and the stack and the ram are my main priorities besides the tracker itself.

Post Reply