Retrocomputing Stack Exchange is a question and answer site for vintage-computer hobbyists interested in restoring, preserving, and using the classic computer and gaming systems of yesteryear. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm studying the 65c816 assembly for the 1994 game, Super Metroid.

A hobbyist studied the game in-depth and created a RAM map. From it:

7E:0B56 - 7E:0B57    Moves Samus this distance horizontally, in subpixels.
7E:0B58 - 7E:0B59    Moves Samus this distance horizontally, in pixels.
7E:0B5A - 7E:0B5B    Moves Samus this distance vertically, in subpixels.
7E:0B5C - 7E:0B5D    Moves Samus this distance vertically, in pixels.

I'm new to assembly, but as I understand it, the game stores Samus's movement in RAM, to be picked up later by some code that performs the actual moving (the "function"). But why do things this way? Why not simply push the above "parameters" onto the stack and pop them from within the "function"? Is storing the parameters in static addresses somehow more efficient? Is it to save space on the stack? Is it for better code organization?

share|improve this question
5  
This feels like it would be a better fit on a programming SE. Even if this was a technique used on a game from the 90s, it probably isn't otherwise exclusive to such systems. That is, one could expect similar techniques to be used in any modern design around this chip, which is still being produced. It will probably get more love over on an exclusive programming site. – jdv yesterday
1  
There are so many SE's these days, I didn't even know there was a Game Development one. I was debating between Stack Overflow and Computer Science, but in the former, "Why?" / "subjective" questions tend to get downvoted to hell, and the latter seemed too academic, when Retro caught my eye and seemed perfect, with questions about Atari on the frontpage. I do agree with @jdv that it would have gotten more "love" and formal answers elsewhere, but I just felt like it belonged on retro :-) – Andrew Cheong 21 hours ago
1  
We are still in beta here and trying to figure out exactly what belongs and what doesn't, so if nothing else questions like these are useful to help us figure that out. As for the Game Development SE, I have no idea how receptive they are to retro programming questions. Maybe have a poke around and there and see if any similar questions have gotten good answers there? – mnem 21 hours ago
1  
@jdv Is the question really relevant to modern programming though? I'm not a programmer, but isn't the technique described something that would only be used in a retro context and would be frowned upon in modern coding (even in a bare-metal time/space constrained context)? I'm honestly not sure if it should, or should not be considered on topic here, I just think it might be worth asking if it should be or not. – mnem 19 hours ago
1  
A question may be on-topic on more than one site. Or none at all. In the former case, it's a decision based on the target audience and the kinds of answers you are interested in. I encounter this a lot with Open Source software licensing questions, which are on-topic on (a least) Programmers, Open Source, and Law. These questions can be asked at any of those three, but the kinds of answers (and their quality!!!) will differ wildly. – Jörg W Mittag 19 hours ago
up vote 22 down vote accepted

Off the top of my head I can think of two reasons, there are probably more.

The first reason is that these variables may be set by a routine each frame, and then a lot of code uses them during the time of the whole frame. Every interrupt routine that fires during that frame may want to read out the current direction.

The second reason is that, in a real-time embedded environment1 like a console game, it's important to know that you have enough resources to handle the worst-case conditions. One resource is time; you want to make sure that everything you want to perform can be handled in one frame update. Another resource, and relevant in this case, is memory. Whatever happens, you can't run out of memory2. There's no swap space, you can't pop up a requester, or kill of something else. Add to this the fact that embedded platforms like this do not have any form of memory protection, so if you write too much on the stack - game over, man.

The combination of these two makes it reasonable to reserve RAM for variables like this.


1. These platforms still exist today, think control systems for robots, your fridge, basically every "hidden" microcontroller.

2. I've recently worked with an operating system designed for high reliability. It had every type of dynamic allocations removed. No malloc() for you.

share|improve this answer
1  
A vote for the first part. If these parameters are at fixed locations, an ISR can work on them without needing to first locate them. All the timing critical display updates would likely be interrupt driven. – Sean Houlihane yesterday
5  
@2: I work with a system that has high-reliability parts. They all have static arrays for storage; even stacks and queues are pre-allocated and tightly constrained - e.g. if there is stuff in the queue still but new must be added, first a cancellation mechanism for the old is triggered; the queue will never overflow as only a fixed number of elements can be stored. The non-critical parts can still allocate memory dynamically, but they MUST fail gracefully - they are not allowed to pull the whole system down. – SF. 22 hours ago

As mentioned previously the timing issue is the cause not to waste time in pushing up parameters, access them with cost-intensive addressing modes and pull them finally from stack. Too much action if this occurs in a tight, time-critical frame building routine. In a games of a certain size usually all could be handled with global variables. Some state of the game has to be held into globals anyway. It's a just a waste of time to copy the global state into local parameters all the time. There is no advantage or gain to use high-level language structures like parameter passing to functions or subroutines. Local variable protection, opaque variables, a common calling interface convention and so on are simply not necessary in a closed world of a game. An important exception is the calling interface to a surrounding operating system, to attach a game to the system environment and using its resources or gain access to libraries with offers graphical or sound support and so on (3D engines, sound players, ...).

Larger developments or projects tend to use a high-level language which implies the usage of such techniques. On assembler level you have only to bother with the high-level calling interface if you want to embed such routines for (mostly) time-critical reasons. It can simply seen as a connection of some assembly with the rest of the project in high-level.

share|improve this answer
    
This was a very good answer—imagining "global variables" I was able to understand the predicament of local variables. Unfortunately I could only accept one answer, but thank you. – Andrew Cheong 21 hours ago

The 8 bit 6502 family doesn't have any stack-relative addressing modes that would make it easy to use the stack for variable storage. One can access values on the stack with a sequence such as TSX; LDA &102, X, but that's slower, clobbers X, and uses more memory (both in code size and stack usage) than a global variable.

The 65C816 adds stack-relative instructions such as LDA 2, S, so it is now plausible to use local variables on the stack. But global variables were idiomatic on the 6502 and old habits die hard…

share|improve this answer
    
Yup, believe it or not, there were major architectures, as well as micros, without stack addressing modes. E.g., System 360. – davidbak 14 hours ago
1  
This is the correct answer. – Gaius 3 hours ago

It's also worth pointing out that the intricacies of maintaining variables on the stack can result in slower code. And of course there are limits to how big the stack can be; even with the more expansive stack on the 65816, you're still limited to a fraction of bank $00 (so 64K minus the direct page(s) minus any other stacks you have around minus any I/O space or ROM or the like you have. So preserving bank zero space for use by other code that actually has to use it is pretty critical.

As has been pointed out, the '816 does have stack relative addressing. But it's a little awkward and is very easy to screw up. If you make any changes to the configuration of the stack, you can mess up all kinds of stuff with things getting misaligned. Especially if you pass parameters on the stack.

So it's just so much easier to not use the stack that way unless you're either (a) really sure of what you're doing or (b) really sure it will make a huge difference.

share|improve this answer

Another example in the tightly constrained world of games - especially those with a real-time loop (e.g., for updating the display on a fixed schedule): there was no space for a "task queue" and/or no time for a "rendezvous" mechanism that would enable data to be passed from one thread to another in one of the "structured" methods that would be more common today. Instead, the programmer passed data via global variables and was responsible for analyzing (and preventing or mitigating) data races and other concurrency hazards. These solutions are fast and cheap at runtime, slow and expensive at coding time ...

BTW, when looking at older code for very resource-constrained systems you'll frequently find coding techniques that are generally shunned today - for good reason! The most egregious would be self-modifying code which was much more common on earlier and/or smaller architectures with less choice of addressing modes, fewer registers, etc. Look at those mechanisms and marvel how we were able to do so much with so little ... and in such a way that it nearly always worked!

share|improve this answer
    
"self-modifying code" – Well, today, that's called "Dependency Injection" and is all the rage :-D (More seriously: reflective metaprogramming is a powerful technique, and is used in some communities to great effect, e.g. Lisp and Smalltalk, but also Java bytecode rewriting is an example, e.g. Kilim.) – Jörg W Mittag 19 hours ago
    
@JörgWMittag - I know you're kidding but of course by "self-modifying code" I'm referring to what has now been dressed up and formalized as "run time code generation" or "dynamic code generation". When it isn't so fancy: you just overwrite your own machine code right before you execute it. For example, I did this on a minicomputer that lacked a variable shift instruction (where the shift count comes out of a register). I just created a fixed shift instruction with the right count in its immediate field and wrote it into the code right before it executed ... – davidbak 18 hours ago
2  
Hey, when I wanted to save a register, I would store the value into a location that was the immediate value of a Load instruction later on where I needed the value again. – JDługosz 15 hours ago
1  
Yes, it was meant as a joke. I know Linux does this in a few occasions. Two I know of: when a kernel that has been compiled for a multi-CPU system boots on a singe-CPU system, it overwrites its spin lock implementation with NOPs, there's no point in protecting against concurrent access if there's no concurrency. And there are "hybrid" kernels that can run natively as well as accelerated-paravirtualized (e.g. on Xen or HyperV), and they patch in the correct implementations of certain interrupt routines and low-level hardware access functions at runtime (boot time, to be precise). – Jörg W Mittag 14 hours ago
    
And of course kexec, the ability to replace the running kernel image with a newer one without rebooting the system is basically a giant multi-100-kByte runtime patch. Linux doesn't guarantee stable ABIs inside the kernel, so kexec has to patch and update all internal kernel data structures, e.g. process descriptors (including for currently executing processes), internal filesystem driver data (including for currently mounted active filesystems with open files) and so on. – Jörg W Mittag 14 hours ago

Code written in high-level languages do a lot of stack-relative operations, because compilers are good at keeping track of which stack offset refers to which variable in the current context.

In hand-written assembly code it was often more common to store things as 'globals' in well-known memory locations, just because it's easier for humans to think about memory layout in that way. If recursion or concurrent instances are not needed in a particular function, then global storage is a possible solution.

I worked on Z80 and 8086 assembly code software systems back in the day, and even though both of these processors have index registers that would allow stack-relative storage of variables, it was common practice to store variables as globals.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.