sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

README.md (5225B)


      1 # asm-processor
      2 
      3 Pre-process .c files and post-process .o files to enable embedding MIPS assembly into IDO-compiled C.
      4 
      5 ## Usage
      6 
      7 Let's say you have a file compiled with `-g` on the IDO compiler, that looks like this:
      8 ```c
      9 float func4(void) {
     10     "func4";
     11     return 0.2f;
     12 }
     13 ```
     14 
     15 This script enables replacing it by:
     16 ```asm
     17 GLOBAL_ASM(
     18 .rdata
     19 .word 0x66756e63 # func
     20 .word 0x34000000 # 4\0\0\0
     21 
     22 .late_rodata
     23 glabel rv
     24 .word 0x3e4ccccd # 0.2f
     25 
     26 .text
     27 glabel func4
     28 lui     $at, %hi(rv)
     29 jr      $ra
     30 lwc1    $f0, %lo(rv)($at)
     31 jr      $ra
     32 nop
     33 jr      $ra
     34 nop
     35 )
     36 ```
     37 
     38 To compile the file, run `python3 build.py $CC -- $AS $ASFLAGS -- $CFLAGS -o out.o in.c`, where $CC points to an IDO binary (5.3/7.1 and recomp/qemu all supported), $AS is e.g. `mips-linux-gnu-as`, $ASFLAGS e.g. `-march=vr4300 -mabi=32` and $CFLAGS e.g. `-Wab,-r4300_mul -non_shared -G 0 -Xcpluscomm -g`. build.py may be customized as needed.
     39 
     40 In addition to an .o file, build.py also generates a .d file with Makefile dependencies for .s files referenced by the input .c file.
     41 This functionality may be removed if not needed.
     42 
     43 Reading assembly from file is also supported, by either `GLOBAL_ASM("file.s")` or `#pragma GLOBAL_ASM("file.s")`.
     44 
     45 ### What is supported?
     46 
     47 `.text`, `.data`, `.bss` and `.rodata` sections, `.word`/`.incbin`, `.ascii`/`.asciz`, and `-g`, `-g3`, `-O1`, `-O2`, `-framepointer` and `-mips1`/`-mips2` flags to the IDO compiler.
     48 
     49 ### What is not supported?
     50 
     51 * complicated assembly (.ifdef, macro declarations/calls other than `glabel`, pseudo-instructions that expand to several real instructions)
     52 * non-IDO compilers
     53 * `-O3` (due to function reordering)
     54 
     55 C `#ifdef`s only work outside of `GLOBAL_ASM` calls, but is otherwise able to replace `.ifdef`.
     56 
     57 ### What's up with "late rodata"?
     58 
     59 The IDO compiler emits rodata in two passes: first array/string contents, then large literals/switch jump tables.
     60 
     61 Data declared within `.rdata`/`.section .rodata` will end up in the first half, and `.late_rodata`/`.section .late_rodata` in the second half.
     62 
     63 ### How does it work?
     64 
     65 It's a bit of a hack!
     66 The basic idea is replace `GLOBAL_ASM` blocks with dummy C functions/global vars of the same sections sizes as the assembly.
     67 Then the C file gets compiled, and the dummy contents overwritten with the injected assembly.
     68 
     69 To accomplish this, asm-processor has logic for guessing the size of assembly contents
     70 (which assumes the assembly isn't too complicated, e.g. no macros),
     71 and for emitting C code of exact sizes for a bunch of different IDO compiler flags.
     72 
     73 The assembler code is padded with nops to line it up with its correct position in the C;
     74 this allows C and asm ELF files to be merged easily without having to fix up e.g. symbol addresses.
     75 
     76 The most difficult part is `late_rodata`, which is hard to create programmatically.
     77 asm-processor does that by emitting code that uses dummy float literals/double literals/jump tables,
     78 assembles the late_rodata at another location of the .rodata section, then overwrites the dummy rodata.
     79 This does require some movement of symbols and relocations, and quite a bit of care in what code to
     80 emit and how to preserve .double alignment.
     81 
     82 It's worth noting some alternative ways in which asm-processor would have been implemented:
     83 - One idea to get rid of the C/asm size estimations is to emit arbitrary code, and then move code,
     84 symbols and relocations to the correct place after the sizes are known.
     85 Given the machinery for `late_rodata` this wouldn't have been too difficult, and it would have the upside of improved portability.
     86 There is a big downside, however: using dummy code of incorrect size throws off alignment and can introduce unintended padding.
     87 Fixing this would require running multiple passes of asm-processor, with one compile per `ASM_GLOBAL`.
     88 - Another idea is to run the compiler with -S to emit assembly, modify the emitted assembly, then run the assembler
     89 (which in IDO's case may perform additional instruction reordering etc.).
     90 This option has not been investigated in much detail, and would perhaps be superior to the current implementation.
     91 It does have a few unknowns to it, e.g. instruction encoding differences between GNU `as` and IDO's assembler,
     92 how to avoid reordering the injected assembly, and how .rodata/.late_rodata are implemented.
     93 
     94 ### Testing
     95 
     96 There are a few tests to ensure you don't break anything when hacking on asm-processor: `./run-tests.sh` should exit without output if they pass, or else output a diff from previous to new version.
     97 
     98 Tests need the environment variable `MIPS_CC` set to point to the IDO 7.1 compiler, with Pascal support enabled.
     99 
    100 For example if asm-processor is cloned in the same directory as [ido static recomp](https://github.com/decompals/ido-static-recomp) and the working directory is asm-processor, tests can be run using:
    101 
    102 ```sh
    103 MIPS_CC=../ido-static-recomp/build/7.1/out/cc ./run-tests.sh
    104 ```
    105 
    106 Or using [qemu-irix](https://github.com/zeldaret/oot/releases/tag/0.1q) (don't forget `chmod u+x qemu-irix`) to emulate IDO:
    107 
    108 ```sh
    109 MIPS_CC='./qemu-irix -silent -L ../ido-static-recomp/ido/7.1/ ../ido-static-recomp/ido/7.1/usr/bin/cc' ./run-tests.sh
    110 ```
    111 
    112 To skip running Pascal tests, remove the `tests/*.p` glob from `run-tests.sh`.