diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 88 |
1 files changed, 17 insertions, 71 deletions
@@ -367,19 +367,19 @@ Since you need to be as optimal as possible, sometimes using just registers will suffice, while other times using the memory might be needed. In any case: 1. Be consistent on your calling convention. -2. **Documentation is important**. Don't be afraid to - clearly denote which registers or memory addresses might be touched after - calling this function +2. **Documentation is important**. Don't be afraid to clearly denote which + registers or memory addresses might be touched after calling a given function 3. Do not be afraid to hint on the function's name how you can call it. This is better explained in the following sections. -## Registers are to be saved by the caller +## Don't expect resources to be preserved across calls -As a general rule, as a caller don't expect values on registers to survive to a -function call. If there is important information to be kept or updated across -calls, make sure to shadow it on memory addresses which are guaranteed to not be -touched (on that check on the function's documentation!). Another option is to -use the stack, but do so with caution as stack overflows are a real danger here. +As a general rule, as a caller don't expect values on registers or memory +regions to survive to a function call. If there is important information to be +kept or updated across calls, make sure to shadow it on memory addresses which +are guaranteed to not be touched (on that check on the function's +documentation!). Another option is to use the stack, but do so with caution as +stack overflows are a real danger here. ## Reserve return values and arguments into zeropage @@ -390,60 +390,14 @@ example, one might reserve `$00-$04` to variables named like `zp_arg0` to 1. It's up to the caller to save values from these addresses if they are to be preserved across calls. The called function might change these values during its execution. -2. The called function will save to `zp_arg0` the returned value, and also to - `zp_arg1` if the returned value is 16 bit (little endian). -3. From 1. and 2.; you can infer that if you are passing things through - `zp_argX`, everything is done in memory: you cannot mix registers and memory - arguments. - -As for the parameters being accepted, one might follow the same convention as -it's done in places like the Linux kernel, in which equivalent C signatures are -given as documentation for an assembly function. Moreover, it could also be a -good idea to embed this information into the function's name. This can come at -the cost of a darn ugly name, but at least there won't be any misunderstandings -when using this function. So all in all, you could end up with something like: +2. The called function can save to `zp_arg0` the returned value, and also to + `zp_arg1` if the returned value is 16 bit (little endian). In any case, and + as always, read the documentation for it. -``` assembly -;; Get a thing given the passed argument. It expects the value on memory and it -;; will return a byte. You can assume this function to have the equivalent C -;; signature: -;; -;; uint8_t get_thing_from_arg(uint8_t value) -;; -;; NOTE: `a` register is modified. -.proc get_thing_from_arg - lda zp_arg0 - ;; Do something with it - - sta zp_arg0 - rts -.endproc -``` - -## Register-only strategies - -Sometimes just passing a value on the register is enough. Consider the following -patterns. +## Using the `x` or `y` registers for code that has to "select" -### Using the `a` register as both input and output - -``` assembly -lda #m_value -jsr compute_next_value -clc -adc #m_base -``` - -Here `compute_next_value` expects only one argument, to be set in the `a` -register. The result is also left in `a`. This is useful on pure functions that -will not overwrite on other registers or on many memory addresses. That being -said, the function should always state if it's touching other registers or other -memory addresses. Moreover, in assembly the function signature is tightly -coupled to how it will be called. Hence, consider how code elsewhere is calling -this function as pulling data into the `a` register just to call a function can -defeat the purpose of this optimization. - -### Using the `x` or `y` registers for code that has to "select" +One handy convention is to use the `x` or `y` registers as selectors. Refer to +something like this: ``` assembly ldx #0 @@ -458,16 +412,8 @@ for example, as a way to index the bank to be selected. This can be further documented by adding a suffix into the function's name, as it's done in the above example. -## Memory arguments first, optimize with registers later - -In a similar fashion than the principle of "don't optimize early": prefer the -calling convention through memory addresses first if you are not sure how a -given function is going to be used in the end. You can later optimize to the -register-only strategies if you are sure that you can make this optimization. -This allows for the flexibility of memory addresses at first without the -possible gotchas from register-only calls. Moreover, if you anticipate that a -given function will be a core library one, it will probably be a good idea to -stick with memory arguments, as they fit a more generic approach. +In this example, adding a suffix like `_x` is already telling the caller that +the `x` register is involved on this scenario. # Flow of control |
