git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

docs/pages/04_coding_guidelines.md

      1 @page docs_coding_guidelines Coding Guidelines
      2 
      3 # Learning C / C fundamentals
      4 Writing games and other programs with GBDK will be much easier with a basic understanding of the C language. In particular, understanding how to use C on "Embedded Platforms" (small computing systems, such as the Game Boy) can help you write better code (smaller, faster, less error prone) and avoid common pitfalls.
      5 
      6 
      7 @anchor docs_c_tutorials
      8 ## General C tutorials
      9   - https://www.learn-c.org/
     10   - https://www.tutorialspoint.com/cprogramming/index.htm
     11   - https://www.chiark.greenend.org.uk/~sgtatham/cdescent/
     12 
     13 
     14 ## Embedded C introductions
     15 
     16   - http://dsp-book.narod.ru/CPES.pdf
     17   - https://www.phaedsys.com/principals/bytecraft/bytecraftdata/bcfirststeps.pdf
     18 
     19 ## Game Boy games in C
     20 
     21   - https://gbdev.io/resources.html#c
     22 
     23 # Understanding the hardware
     24 In addition to understanding the C language it's important to learn how the Game Boy hardware works. What it is capable of doing, what it isn't able to do, and what resources are available to work with. A good way to do this is by reading the @ref Pandocs and checking out the @ref awesome_gb list.
     25 
     26 
     27 # Writing optimal C code for the Game Boy and SDCC
     28 The following guidelines can result in better code for the Game Boy, even though some of the guidance may be contrary to typical advice for general purpose computers that have more resources and speed.
     29 
     30 
     31 ## Tools
     32 
     33 @anchor const_gbtd_gbmb
     34 ### GBTD / GBMB, Arrays and the "const" keyword
     35 __Important__: The old @ref gbtd_gbmb "GBTD/GBMB" fails to include the `const` keyword when exporting to C source files for GBDK. That causes arrays to be created in RAM instead of ROM, which wastes RAM, uses a lot of ROM to initialize the RAM arrays and slows the compiler down a lot.
     36 
     37 __Use of @ref toxa_gbtd_gbmb "toxa's updated GBTD/GBMB" is highly recommended.__
     38 
     39 If you wish to use the original tools, you must add the `const` keyword every time the graphics are re-exported to C source files.
     40 
     41 @anchor best_practice_dont_read_vram
     42 ## Avoid Reading from VRAM
     43 In general avoid reading from VRAM since that memory is not accessible at all times. If GBDK a API function which reads from VRAM (such as @ref get_bkg_tile_xy()) is called during a video mode when VRAM is not accessible, then that function call will delay until VRAM becomes accessible again. This can cause unnecessary slowdowns when running programs on the Game Boy. It is also not supported by GBDK on the NES platform.
     44 
     45 Instead it is better to store things such as map data in general purpose RAM which does not have video mode access limitations.
     46 
     47 For more information about video modes and VRAM access see the pan docs:
     48 
     49 https://gbdev.io/pandocs/STAT.html#stat-modes
     50 
     51 
     52 
     53 ## Variables
     54   - Use 8-bit values as much as possible. They will be much more efficient and compact than 16 and 32 bit types.
     55 
     56   - Prefer unsigned variables to signed ones: the code generated will be generally more efficient, especially when comparing two values.
     57 
     58   - Use explicit types so you always know the size of your variables. `int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t` and `bool`.
     59   These are standard types defined in `stdint.h` (`#include <stdint.h>`) and `stdbool.h` (`#include <stdbool.h>`).
     60 
     61   - Global and local static variables are generally more efficient than local non-static variables (which go on the stack and are slower and can result in slower code).
     62     - An exception to this when there are a small number of local variables (one or two) and the code is not complex. Then the compiler may allocate those variables to CPU registers instead which may be faster.
     63     - Functions which use global or static local variables will loose re-entrancy. In most cases it is not a problem, but important to keep in mind.
     64     - In particular avoid putting big arrays on the stack, consider static local or global.
     65 
     66   - Keep the number of arguments passed to functions small (ideally one or two arguments at most). When there are a large number of arguments they get pushed onto the stack and result in more overhead for function calls. See the Calling Conventions in the SDCC compiler manual for details.
     67 
     68   - @anchor const_array_data
     69   `const` keyword: use const for arrays, structs and variables with read-only (constant) data. It will reduce ROM, RAM and CPU usage significantly. Non-`const` values are loaded from ROM into RAM inefficiently, and there is no benefit in loading them into the limited available RAM if they aren't going to be changed.
     70 
     71   - Here is how to declare `const` pointers and variables:
     72     - non-const pointer to a const variable: `const uint8_t * some_pointer;`
     73     - const pointer to a non-const variable: `uint8_t * const some_pointer;`
     74     - const pointer to a const variable: `const uint8_t * const some_pointer;`
     75     - https://codeforwin.org/2017/11/constant-pointer-and-pointer-to-constant-in-c.html
     76     - https://stackoverflow.com/questions/21476869/constant-pointer-vs-pointer-to-constant
     77 
     78   - For calculated values that don't change, pre-compute results once and store the result. Using lookup-tables and similar approaches can improve speed and reduce code size. Macros can sometimes help. It may be beneficial to do the calculations with an outside tool and then include the result as C code in a const array.
     79 
     80   - Use an advancing pointer (`someStruct->var = x; someStruct++`) to loop through arrays of structs instead of using indexing each time in the loop `someStruct[i].var = x`.
     81 
     82   - When modifying variables that are also changed in an Interrupt Service Routine (ISR), wrap them the relevant code block in a `__critical { }` block. See http://sdcc.sourceforge.net/doc/sdccman.pdf#section.3.9
     83 
     84   - When using constants and literals the `U`, `L` and `UL` postfixes can be used.
     85     - `U` specifies that the constant is unsigned
     86     - `L` specifies that the constant is long.
     87 
     88     - NOTE: In SDCC 3.6.0, the default for char changed from signed to unsigned. The manual says to use `--fsigned-char` for the old behavior, this option flag is included by default when compiling through @ref lcc. 
     89 
     90     @anchor fixed_point_type
     91   - A fixed point type (`fixed`) is included with GBDK when precision greater than whole numbers is required for 8 bit range values (since floating point is not included in GBDK).
     92 
     93   See the "Simple Physics" sub-pixel example project.
     94 
     95   Code example:
     96     
     97         fixed player[2];
     98         ...
     99         // Modify player position using its 16 bit representation
    100         player[0].w += player_speed_x;
    101         player[1].w += player_speed_y;
    102         ...
    103         // Use only the upper 8 bits for setting the sprite position
    104         move_sprite(0, player[0].h ,player[1].h);
    105 
    106 
    107 ## Code structure
    108 
    109   - Do not `#include` `.c` source files into other `.c` source files. Instead create `.h` header files for them and include those.
    110     https://www.tutorialspoint.com/cprogramming/c_header_files.htm
    111 
    112   - Instead of using a blocking @ref delay() for things such as sprite animations/etc (which can prevent the rest of the game from continuing) many times it's better to use a counter which performs an action once every N frames. @ref sys_time may be useful in these cases.
    113 
    114   - When processing for a given frame is done and it is time to wait before starting the next frame, @ref vsync() can be used. It uses HALT to put the CPU into a low power state until processing resumes. The CPU will wake up and resume processing at the end of the current frame when the Vertical Blanking interrupt is triggered.
    115 
    116   - Minimize use of multiplication, modulo with non-powers of 2, and division with non-powers of 2. These operations have no corresponding CPU instructions (software functions), and hence are time costly.
    117       - SDCC has some optimizations for:
    118         - Division by powers of 2. For example `n /= 4u` will be optimized to `n >>= 2`.
    119         - Modulo by powers of 2. For example: `(n % 8)` will be optimized to `(n & 0x7)`.
    120       - If you need decimal numbers to count or display a score, you can use the GBDK BCD ([binary coded decimal](https://en.wikipedia.org/wiki/Binary-coded_decimal)) number functions. See: @ref bcd.h and the `BCD` example project included with GBDK.
    121 
    122   - Avoid long lists of function parameters. Passing many parameters can add overhead, especially if the function is called often. Globals and local static vars can be used instead when applicable.
    123 
    124   - Use inline functions if the function is short (with the `inline` keyword, such as `inline uint8_t myFunction() { ... }`).
    125 
    126   - Do not use recursive functions.
    127 <!-- This entry needs re-work. Signed vs unsigned, current SDCC optimizations...
    128 
    129   - Prefer `==` and `!=` comparison operators to `<`, `<=`, `>`, and `>=`. The code will be shorter and quicker.
    130 
    131     It is even faster to check if a variable is 0 than if it is equal to some other value, so looping from _N down to zero_ is faster than looping _from zero up to N_.
    132 
    133   For instance:
    134 
    135         for(i = 0; i < 10; i++)
    136 
    137   is less efficient than:
    138 
    139         for(i = 0; i != 10; i++)
    140 
    141   and if possible, even better:
    142 
    143         for(i = 10; i != 0; i--)
    144 -->
    145 
    146 ## GBDK API/Library
    147 
    148   - stdio.h: If you have other ways of printing text, avoid including @ref stdio.h and using functions such as @ref printf(). Including it will use a large number of the background tiles for font characters. If stdio.h is not included then that space will be available for use with other tiles instead.
    149 
    150   - drawing.h: The Game Boy graphics hardware is not well suited to frame-buffer style graphics such as the kind provided in @ref drawing.h. Due to that, most drawing functions (rectangles, circles, etc) will be slow . When possible it's much faster and more efficient to work with the tiles and tile maps that the Game Boy hardware is built around.
    151 
    152   - @ref waitpad() and @ref waitpadup check for input in a loop that doesn't HALT at all, so the CPU will be maxed out until it returns. One alternative is to write a function with a loop that checks input with @ref joypad() and then waits a frame using @ref vsync() (which idles the CPU while waiting) before checking input again.
    153 
    154   - @ref joypad(): When testing for multiple different buttons, it's best to read the joypad state *once* into a variable and then test using that variable (instead of making multiple calls).
    155 
    156 
    157 ## Toolchain
    158 
    159   - See SDCC optimizations: http://sdcc.sourceforge.net/doc/sdccman.pdf#section.8.1
    160 
    161   - For details about default Compiler data types, see the SDCC Manual (follow links and scroll down 1 page)
    162     - https://sdcc.sourceforge.net/doc/sdccman.pdf#section.1.1
    163     - Note: by default GBDK enables `--fsigned-char` (via lcc) for SDCC
    164 
    165   - Use profiling. Look at the ASM generated by the compiler, write several versions of a function, compare them and choose the faster one.
    166 
    167   - Use the SDCC `--max-allocs-per-node` flag with large values, such as `50000`. `--opt-code-speed` has a much smaller effect.
    168     - GBDK-2020 (after v4.0.1) compiles the library with `--max-allocs-per-node 50000`, but it must be turned on for your own code.  
    169     (example: `lcc ... -Wf--max-allocs-per-node50000` or `sdcc ... --max-allocs-per-node 50000`).
    170 
    171     - The other code/speed flags are `--opt-code-speed` or `--opt-code-size `.
    172 
    173   - Use current SDCC builds from http://sdcc.sourceforge.net/snap.php  
    174     The minimum required version of SDCC will depend on the GBDK-2020 release. See @ref docs_releases
    175 
    176   - Learn some ASM and inspect the compiler output to understand what the compiler is doing and how your code gets translated. This can help with writing better C code and with debugging.
    177 
    178 
    179 @anchor docs_constant_signedness
    180 ## Constants, Signed-ness and Overflows
    181 There are a some scenarios where the compiler will warn about overflows with constants. They often have to do with mixed signedness between constants and variables. To avoid problems use care about whether or not constants are explicitly defined as unsigned and what type of variables they are used with.
    182 
    183 `WARNING: overflow in implicit constant conversion`
    184 
    185 - A constant can be used where the the value is too high (or low) for the storage medium causing an value overflow.
    186   -  For example this constant value is too high since the max value for a signed 8 bit char is `127`.
    187 
    188           #define TOO_LARGE_CONST 255
    189           int8_t signed_var = TOO_LARGE_CONST;
    190 
    191 - This can also happen when constants are not explicitly declared as unsigned (and so may get treated by the compiler as signed) and then added such that the resulting value exceeds the signed maximum. 
    192   - For example, this results in an warning even though the sum total is `254` which is less than the `255`, the max value for a unsigned 8 bit char variable.
    193 
    194           #define CONST_UNSIGNED 127u
    195           #define CONST_SIGNED 127
    196           uint8_t unsigned_var = (CONST_SIGNED + CONST_UNSIGNED);
    197 
    198   - It can be avoided by always using the unsigned `u` when the constant is intended for unsigned operations.
    199 
    200           #define CONST_UNSIGNED 127u
    201           #define CONST_ALSO_UNSIGNED 127u  // <-- Added "u", now no warning
    202           uint8_t unsigned_var = (CONST_UNSIGNED + CONST_ALSO_UNSIGNED);
    203 
    204 
    205 
    206 
    207 @anchor docs_chars_varargs
    208 ## Chars and vararg functions
    209 
    210 Parameters (chars, ints, etc) to @ref printf / @ref sprintf should always be explicitly cast to avoid type related parameter passing issues.
    211 
    212 For example, below will result in the likely unintended output:
    213 ```{.c}
    214 printf(str_temp, "%u, %d, %x\n", UINT16_MAX, INT16_MIN, UINT16_MAX);
    215 
    216 // Will output: "65535, 0, 8000"
    217 ```
    218 Instead this will give the intended output:
    219 ```{.c}
    220 printf(str_temp, "%u, %d, %x\n", (uint16_t)UINT16_MAX, (int16_t)INT16_MIN, (uint16_t)UINT16_MAX);
    221 
    222 // Will output: "65535, -32768, FFFF"
    223 ```
    224 
    225 ### Chars
    226 In standard C when `chars` are passed to a function with variadic arguments (varargs, those declared with `...` as a parameter), such as @ref printf(), those `chars` get automatically promoted to `ints`. For an 8 bit CPU such as the Game Boy's, this is not as efficient or desirable in most cases. So the default SDCC behavior, which GBDK-2020 expects, is that chars will remain chars and _not_ get promoted to ints when **explicitly cast as chars while calling a varargs function**.
    227 
    228   - They must be explicitly re-cast when passing them to a varargs function, even though they are already declared as chars.
    229 
    230   - Discussion in SDCC manual:  
    231     http://sdcc.sourceforge.net/doc/sdccman.pdf#section.1.5  
    232     http://sdcc.sourceforge.net/doc/sdccman.pdf#subsection.3.5.10
    233 
    234   - If SDCC is invoked with -std-cxx (--std-c89, --std-c99, --std-c11, etc) then it will conform to standard C behavior and calling functions such as @ref printf() with chars may not work as expected.
    235 
    236 For example:
    237 
    238 ```{.c}
    239 unsigned char i = 0x5A;
    240 
    241 // NO:
    242 // The char will get promoted to an int, producing incorrect printf output
    243 // The output will be: 5A 00
    244 printf("%hx %hx", i, i);
    245 
    246 // YES:
    247 // The char will remain a char and printf output will be as expected
    248 // The output will be: 5A 5A
    249 printf("%hx %hx", (unsigned char)i, (unsigned char)i);
    250 ```
    251 
    252 Some functions that accept varargs:
    253  - @ref EMU_printf, @ref gprintf(), @ref printf(), @ref sprintf()
    254 
    255 Also See:
    256  - Other cases of char to int promotion: http://sdcc.sourceforge.net/doc/sdccman.pdf#chapter.6
    257 
    258 
    259 #  When C isn't fast enough
    260 For many applications C is fast enough but in intensive functions are sometimes better written in assembly. This section deals with interfacing your core C program with fast assembly sub routines.
    261 
    262 ## Reusable Local Labels and Inline ASM
    263 
    264 When functions are written assembly it's generally better to not mix the inline ASM with C code and instead write the whole function in assembly.
    265 
    266 If they are mixed then descriptive named labels should not be used for inline ASM. This is due to descriptive labels interfering with the expected scope of the reusable local labels generated from the compiled C code. The compiler will not detect this problem and the resulting code may fail to execute correctly without warning.
    267 
    268 Instead use reusable local symbols/labels (for example `1$:`). To learn more about them check the SDAS manual section "1.3.3  Reusable Symbols"
    269 
    270 
    271 ## Variables and registers
    272 <!-- C normally expects registers to be preserved across a function call. However in the case above as DE is used as the return value and HL is used for anything, only BC needs to be preserved. -->
    273 
    274 Getting at C variables is slightly tricky due to how local variables are allocated on the stack. However you shouldn't be using the local variables of a calling function in any case. Global variables can be accessed by name by adding an underscore. 
    275 
    276 
    277 ## Segments / Areas
    278 The use of segments/areas for code, data and variables is more noticeable in assembler. GBDK and SDCC define a number of default ones. The order they are linked is determined by crt0.s and is currently as follows for the Game Boy and related clones.
    279 
    280   - ROM (in this order)
    281     - `_HEADER`: For the Game Boy header
    282     - `_CODE`: CODE is specified as after BASE, but is placed before it due to how the linker works.
    283     - `_HOME`
    284     - `_BASE`
    285     - `_CODE_0`
    286     - `_INITIALIZER`: Constant data used to init RAM data
    287     - `_LIT`
    288     - `_GSINIT`: Code used to init RAM data
    289     - `_GSFINAL`
    290 
    291   - Banked ROM
    292     - `_CODE_x` Places code in ROM other than Bank `0`, where x is the 16kB bank number.
    293   
    294   - WRAM (in this order)
    295     - `_DATA`: Uninitialized RAM data
    296     - `_BSS`
    297     - `_INITIALIZED`: Initialized RAM data
    298     - `_HEAP`: placed after `_INITIALIZED` so that all spare memory is available for the malloc routines.
    299     - `STACK`: at the end of WRAM
    300 
    301 @anchor sdcc_calling_convention
    302 ## Calling convention
    303 _The following is primarily oriented toward the Game Boy and related clones (sm83 devices), other targets such as sms/gg may vary._
    304 
    305 SDCC in common with almost all C compilers prepends a `_` to any function names. For example the function `printf(...)` begins at the label `_printf::.` Note that all functions are declared global.
    306 
    307 Functions can be marked with `OLDCALL` which will cause them to use the `__sdcccall(0)` calling convention (the format used prior to SDCC 4.2 & GBDK-2020 4.1.0).
    308 
    309 Starting with SDCC 4.2 and GBDK-2020 4.1.0 the new default calling convention is`__sdcccall(1)`.
    310 
    311 For additional details about the calling convetions, see sections `SM83 calling conventions` and `Z80, Z180 and Z80N calling conventions` in the SDCC manual.
    312   - http://sdcc.sourceforge.net/doc/sdccman.pdf
    313   - Section 4.3.9 isn't specific about it, but `gbz80`/`sm83` generally share this subheading with `z80` (Game Boy is partially a sub-port of z80 in SDCC). https://sdcc.sourceforge.net/doc/sdccman.pdf#subsection.4.3.9
    314 
    315 
    316 @anchor banked_calling_convention
    317 ### Banked Calling Convention
    318 _The following is primarily oriented toward the Game Boy and related clones (sm83 devices), other targets such as sms/gg may vary._
    319 
    320 Key Points:
    321   - Function arguments (if present) are always placed on the stack, right to left without particular alignment
    322   - A fixed stack offset (sm83:+4, z80:+3) is added by the `Callee` (to skip the pushed `Caller` Bank and additional `Trampoline` Return Address)
    323   - Return values follow the calling convention (`__sdcccall(1)`, or `__sdcccall(0)` for `OLDCALL`)
    324 
    325 Terminology:
    326 - `Caller`: the code which is calling the requested function
    327 - `Callee`: the function to be called  (declared as `BANKED` or `__banked`)
    328 - `Trampoline`: The intermediary which performs the bank switching and does hand-off between `Caller` and `Callee` during the call and then return.
    329 
    330 Banked Call Trampoline
    331   - Banked calls are performed via a trampoline in the non-banked region 0000-3ffff
    332   - The `__sdcc_bcall_ehl` trampoline is used by default
    333     - With it both calling conventions are supported:  `__sdcccall(1)` (default) or `__sdcccall(0)` for `OLDCALL`
    334   - If `--legacy-banking` is specified to SDCC the `__sdcc_bcall` trampoline is used.
    335     - This may only be used with `__sdcccall(0)`
    336 
    337 Process for a banked call (`using __sdcc_bcall_ehl`, the default)
    338 1. The Caller
    339    - Function arguments (if present) are always placed on the stack, right to left without particular alignment
    340    - The Bank of Callee function is placed into register E
    341    - The Address of Callee function is placed in HL
    342    - Calls the bank switch Trampoline (which adds Caller return address to the stack)
    343 2. The Trampoline
    344    - Saves the Current Bank onto the stack (pushed as AF, so 16 bits)
    345    - Switches to the Bank of Callee function (in register E)
    346    - Calls the Callee function address in HL (which adds Trampoline return address to the stack)
    347 3. The Callee Function
    348    - SDCC will use an offset to skip the first N bytes of the stack
    349      - For `sm83` (GB/AP/DUCK): skip first 4 bytes
    350      - For `z80` (GG/SMS/etc): skip first 3 bytes
    351    - Return values follow the calling convention (`__sdcccall(1)`, or `__sdcccall(0)` for `OLDCALL`)
    352    - Executes a return to Trampoline
    353 4. The Trampoline
    354    - Switches to the Bank of the Caller saved on the stack (and moves Stack Pointer past it)
    355    - Executes a return to Caller
    356 5. The Caller
    357    - Cleans up the stack and uses return value (if present)

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.