From 0e86c76739618dd92f8644cf431e51799881904f Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Sat, 22 Feb 2025 17:05:03 -0500 Subject: [PATCH 01/12] abi and 64-bit prototype --- src/c/easy/abi_basics.c | 32 ++++++ src/c/easy/integers_64_bit.c | 62 ++++++++++ src_template/c/easy/abi_basics.c | 113 ++++++++++++++++++ src_template/c/easy/integers_64_bit.c | 159 ++++++++++++++++++++++++++ 4 files changed, 366 insertions(+) create mode 100644 src/c/easy/abi_basics.c create mode 100644 src/c/easy/integers_64_bit.c create mode 100644 src_template/c/easy/abi_basics.c create mode 100644 src_template/c/easy/integers_64_bit.c diff --git a/src/c/easy/abi_basics.c b/src/c/easy/abi_basics.c new file mode 100644 index 0000000..53e6b24 --- /dev/null +++ b/src/c/easy/abi_basics.c @@ -0,0 +1,32 @@ + +int abi_function_1(int a, int b) { + return a + b; +} + +int abi_function_2(int a, int b) { + return a + b; +} + +int abi_function_3(int a) { + some_func(); + return a; +} + +void abi_function_4() { + some_func(); +} + +// TODO(fox): Put this in header +typedef struct { + float x; + float y; + float z; +} Vec3; + +void abi_function_5(float a) { + Vec3 pos; + pos.x = a; + pos.y = a; + pos.z = a; + some_func(&pos); +} diff --git a/src/c/easy/integers_64_bit.c b/src/c/easy/integers_64_bit.c new file mode 100644 index 0000000..f81f01b --- /dev/null +++ b/src/c/easy/integers_64_bit.c @@ -0,0 +1,62 @@ + +// TODO(fox): Should the typedefs be used for long longs? + +u64 add_64(u64 a, u64 b) { + return a + b; +} + +u64 aligned_64(u32 a, u64 b) { + return b + 5; +} + +u64 add_64(u64 a, u64 b) { + return a + b; +} + +u32 add_64_downcast(u64 a, u64 b) { + return a + b; +} + +u64 sub_64(u64 a, u64 b) { + return a - b; +} + +u32 sub_64_downcast(u64 a, u64 b) { + return a - b; +} + +u64 mul_64(u64 a, u64 b) { + return a * b; +} + +u32 mul_64_downcast(u64 a, u64 b) { + return a * b; +} + +u64 div_64(u64 a, u64 b) { + return a / b; +} + +u64 mod_64(u64 a, u64 b) { + return a % b; +} + +u64 shl_64(u64 a, u64 b) { + return a << b; +} + +u64 shr_64(u64 a, u64 b) { + return a >> b; +} + +u64 and_64(u64 a, u64 b) { + return a & b; +} + +u64 or_64(u64 a, u64 b) { + return a | b; +} + +u64 xor_64(u64 a, u64 b) { + return a ^ b; +} diff --git a/src_template/c/easy/abi_basics.c b/src_template/c/easy/abi_basics.c new file mode 100644 index 0000000..b6f2e99 --- /dev/null +++ b/src_template/c/easy/abi_basics.c @@ -0,0 +1,113 @@ +/* ================================================================ * + * + **** ABI BASICS + * + * In order for a piece of software to be able to run on + * a given piece of hardware and/or interoperate with other pieces of + * software on a system, there has to be a general agreement + * as to how the software will interact with various aspects of + * the hardware. This is known as an *application binary interface*, or + * ABI. For example, every library and function on the Gamecube/Wii + * "agrees" that it will use a specific register to return values if + * the function returns something, which you'll see in the first training + * function. + * + * An ABI close to the what the GC/Wii uses that is useful to + * reference is the System V PowerPC ABI, which can be found here: + * + * http://refspecs.linux-foundation.org/elf/elfspec_ppc.pdf + * + * In addition, since the GC/Wii is an embedded system, its CPU + * also obeys a superset of the ABI known as an *embedded* application + * binary interface*, or EABI, which can be viewed here: + * + * https://files.decomp.dev/E500ABIUG.pdf + * + * References to the general ABI will use the notation (ABI page #-#), + * and the EABI will use (EABI page #-#), + * + * ================================================================ */ + +/* ================================================================ * + * + * Registers and parameters (Function Calling Sequence, ABI page 3-14) + * + * As you may be familiar with, the CPU has to move any data it wants + * to operate on from main memory (RAM) into its own internal registers + * to be able to access it. Thus, an optimization most ABIs utilize + * to reduce the amount of times a given function has to access RAM is + * to allow registers themselves to be used as both function arguments + * and return values. + * + * In the case of non-float values, up to eight general-purpose registers + * can used to pass arguments to a function, starting at GPR3 and ending + * at GPR10. Additionally, GPR3 acts a return register that a callsite of + * the function can read from. + * + * For example, to write a function add() that adds two inputted integers + * together and returns the result, you simply have to add r3 and r4 + * and store the result in r3. + * + * ================================================================ */ + +int abi_function_1(int a, int b) { +} + +/* ================================================================ * + * + * For floats, up to eight floating-point registers can be used, + * starting at FPR1 and ending at FPR8. FPR1 also acts as the return + * register. + * + * ================================================================ */ + +int abi_function_2(int a, int b) { +} + +/* ================================================================ * + * + * Volatile and non-volatile registers (Registers, ABI page 3-14) + * + * The sets of registers used for function passing (r3-r8, f1-f8) are + * also known as *volatile* registers, because whenever you branch into + * a new function, the ABI allows the values in those registers to get + * overwritten. In other words, any time you step over a "b some_func", + * you must assume that all volatile registers have effectively been + * destroyed (or modified in the case of return registers). Thus + * the concept of "non-volatile" registers become useful, which allow + * us to preserve data between registers that cross callsites without + * having to read/write from main memory. + * + * The simplest example of this behavior can be seen below, where + * r3 is moved to the non-volatile register r31, since r3 can get + * overwritten by some_func, which is then passed back to r3 to + * be used as the return register. The other instructions, which are + * related to the stack, will be explained next. + * + * ================================================================ */ + +int abi_function_3(int a) { +} + +/* ================================================================ * + * + * Function prologues/epilogues and the Link Register + * (Function Prologue and Epilogue, ABI page 3-34) + * + * TODO(fox): write this + * + * ================================================================ */ + +void abi_function_4(float a) { +} + +/* ================================================================ * + * + * The stack (The Stack Frame, ABI page 3-17) + * + * TODO(fox): write this + * + * ================================================================ */ + +void abi_function_5(float a) { +} diff --git a/src_template/c/easy/integers_64_bit.c b/src_template/c/easy/integers_64_bit.c new file mode 100644 index 0000000..eb7e1df --- /dev/null +++ b/src_template/c/easy/integers_64_bit.c @@ -0,0 +1,159 @@ +/* ================================================================ * + * + **** 64-BIT INTEGERS + * + * Integers of type "long long," aka an integer that takes up 8 bytes, + * or 64 bits, have to be implemented a bit specially since GPRs on + * the GC/Wii are only 32 bits long: + * + * ================================================================ */ + +/* ================================================================ * + * + * Register usage + * + * Each 64-bit value occupies two general-purpose registers. When + * passed into a function, it simply occupies two continuous registers. + * Additionally, r4 can be used in conjunction with r3 as a return value + * to return a 64-bit value without needing to use the stack. + * + * ================================================================ */ + +u64 add_64(u64 a, u64 b) { +} + +/* ================================================================ * + * + * The ABI requires the compiler to "align" the two GPRs belonging to + * a 64-bit integer to an odd-numbered register (PPC 3-19), meaning + * that in the example below, the register order looks like this: + * + * r3 - a + * r4 - empty + * r5, r6 - b + * + * Presumably this is to decrease register chain dependencies; if the + * 64-bit argument was allowed to be placed in r4 and r5 instead, then + * this example would compile to this and require an extra register + * move to free up r4: + * + * addi r6, r4, 0x0 + * li r0, 0x5 + * addc r4, r5, r0 + * li r0, 0x0 + * adde r3, r6, r0 + * blr + * + * ================================================================ */ + +u64 aligned_64(u32 a, u64 b) { +} + +/* ================================================================ * + * + **** DETERMINING 64-BIT USAGE + * + * The best way to confirm whether a given variable or struct/class + * member is 64-bit or not is to inspect its operations, as most of + * them are distinct from their 32-bit or less counterparts. + * + * An important optimization the compiler can do to watch out for is + * in the case where the rvalue is 64-bit but the lvalue is 32-bit, + * which I'll refer to as a "downcast." Look at the examples below to + * see what I mean: + * + * ================================================================ */ + +/* ================================================================ * + * + * Addition and subtraction + * + * Addition and subtraction use a pair of carry and extend instructions + * to pass the carry bit to the higher-value register. Interstingly on + * all versions of MWCC, the compiler will still emit the carry part of + * the instruction instead of the normal variant when downcasting to 32-bit + * even with optimizations, so this could theoretically be used to + * identify a 64-bit value. ProDG emits the normal variant which causes + * it to be indistinguishable like multiplication. + * + * ================================================================ */ + +u64 add_64(u64 a, u64 b) { +} + +u32 add_64_downcast(u64 a, u64 b) { +} + +u64 sub_64(u64 a, u64 b) { +} + +u32 sub_64_downcast(u64 a, u64 b) { +} + +/* ================================================================ * + * + * Multiplication + * + * Multiplication has a long and recognizable 64-bit pattern, but a + * 32-bit downcast is indistinguishable from multiplying two 32-bit numbers. + * + * ================================================================ */ + +u64 mul_64(u64 a, u64 b) { +} + +u32 mul_64_downcast(u64 a, u64 b) { +} + +/* ================================================================ * + * + * Compiler intrinsics + * + * Division, modulo, and bit shifts call compiler intrinsics on both + * MWCC and ProDG, which are unaffected by downcasts. + * + * ================================================================ */ + +u64 div_64(u64 a, u64 b) { +} + +u64 mod_64(u64 a, u64 b) { +} + +u64 shl_64(u64 a, u64 b) { +} + +u64 shr_64(u64 a, u64 b) { +} + +/* ================================================================ * + * + * Boolean operations + * + * 64-bit AND, OR, and XOR are indistinguishable from two back-to-back + * 32-bit operations, so you can only know for sure that 64-bit is being + * used if the inputs/results are passed into other known 64-bit + * operations, like a comparison. + * + * ================================================================ */ + +u64 and_64(u64 a, u64 b) { +} + +u64 or_64(u64 a, u64 b) { +} + +u64 xor_64(u64 a, u64 b) { +} + +/* ================================================================ * + * + * Comparisons + * + * Both branched and branchless 64-bit comparisons are unambiguous + * and are unaffected by downcasting, since the result of a comparison + * is already a single bit (true/false). + * + * ================================================================ */ + +// TODO(fox): implement From 693be641784b25c04f3bc7593e93cc9e8293b56d Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Wed, 26 Feb 2025 11:48:19 -0500 Subject: [PATCH 02/12] LR explanations --- src/c/easy/abi_basics.c | 38 ++++++++++++- src_template/c/easy/abi_basics.c | 95 ++++++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 6 deletions(-) diff --git a/src/c/easy/abi_basics.c b/src/c/easy/abi_basics.c index 53e6b24..55561d8 100644 --- a/src/c/easy/abi_basics.c +++ b/src/c/easy/abi_basics.c @@ -16,7 +16,8 @@ void abi_function_4() { some_func(); } -// TODO(fox): Put this in header +// TODO(fox): Put this in header, or use from one that +// exists in math header typedef struct { float x; float y; @@ -30,3 +31,38 @@ void abi_function_5(float a) { pos.z = a; some_func(&pos); } + +// TODO(fox): This should probably be combined with the branching section + +int abi_function_6(int a) { + switch (a) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 5; + } + return 0; +} + +int abi_function_7(int a) { + some_func(); + switch (a) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 5; + } + return 0; +} diff --git a/src_template/c/easy/abi_basics.c b/src_template/c/easy/abi_basics.c index b6f2e99..ce3df8c 100644 --- a/src_template/c/easy/abi_basics.c +++ b/src_template/c/easy/abi_basics.c @@ -91,23 +91,108 @@ int abi_function_3(int a) { /* ================================================================ * * - * Function prologues/epilogues and the Link Register + * Function prologues/epilogues, the stack, and the link register * (Function Prologue and Epilogue, ABI page 3-34) + * (The Stack Frame, ABI page 3-17) * - * TODO(fox): write this + * You may have noticed a pattern among the functions listed so far in + * that they all end in the "blr" opcode, and many of them contain a + * "mflr" towards the top and a "mtlr" towards the bottom. These + * opcodes all concern a special register called the *link register*, + * which holds the memory address of the previous function that called + * the function you're currently in. To explain it briefly: + + * TODO(fox): the final file could have these be real addresses + * + * | // some_func + * | 0x80103F14 | add r3, r3, 0x5 + * | 0x80103F18 | blr + * + * | // a bunch of code + * | + * | // abi_function_4 + * | 0x802D8910 | mflr r0 + * | 0x802D8914 | stw r0, 0x4(r1) + * | 0x802D8918 | stwu r1, -0x8(r1) + * | 0x802D891C | bl 0x80103F14 // address of some_func + * | 0x802D8920 | lwz r0, 0xc(r1) + * | 0x802D8924 | addi r1, r1, 0x8 + * | 0x802D8918 | mtlr r0 + * | 0x802D891C | blr + * + * When the "bl 0x80103F14" instruction gets executed, the link + * register (LR) is automatically set with the address of the next + * instruction at 0x802D8920, which is "lwz r0, 0xc(r1)". Then once it + * hits the blr in "some_func" at 0x80103F18, it branches to the value + * at the LR (0x802D8920) and resumes where it left off in + * "abi_function_4." + * + * However you may be wondering, if the LR gets overwritten by the "bl + * 0x80103F14" in "abi_function_4," then what will happen to the LR + * that "abi_function_4" is currently holding? It won't be able to + * return to the function that's calling itself when it executes its + * "blr" if its LR gets overwritten! Luckily, that problem is exactly + * what all of the other instructions are addressing, which are a part + * of what is known as the "prologue" and "epilogue" that are executed + * at the beginning and end of a function respectively. + + * In the prologue, "mflr" moves the address in the LR to a register + * r0, which is then stored in what is known as the "stack" in the + * "stw". Note that r1 is a special register which holds the stack + * pointer, and its current value is required to be decremented and + * placed on the stack (in the "stwu"), which you can read more about + * on 3-34 of the ABI doc. Then in the epilogue, that address is + * loaded out from the stack back into r0 and moved back into the LR + * in the "lwz" and "mtlr" instructions, which allows the "blr" to + * successfully return to "abi_function_4"'s caller. + * + * The reason "mflr" and "mtlr" don't show up in every function, as + * you may be able to guess, is that a function doesn't need to save + * and restore the LR unless it actually needs to (i.e. it calls a + * function), which is why some_func doesn't have them. * * ================================================================ */ void abi_function_4(float a) { } +// TODO(fox): This should probably be combined with the branching +// section. This is a more complicated example than using a single +// conditional, but I think the multiple branches illustrates the +// point better. + /* ================================================================ * * - * The stack (The Stack Frame, ABI page 3-17) - * - * TODO(fox): write this + * Typically when a function has early returns, such as the cases in + * this switch statement in abi_function_6, they will branch to the + * epilogue. However if a function doesn't use the stack, the epilogue + * effectively turns into a single blr instruction. In this case, the + * compiler can perform an optimization where it replaces those + * epilogue branches with the epilogue itself and create multiple blrs + * in a single function. + * + * ================================================================ */ + +void abi_function_6(int a) { +} + +void abi_function_7(int a) { +} + +/* ================================================================ * + * + * Besides saving/restoring the LR in the prologue/epilogue, the stack + * will also get used by normal code for various reasons when + * registers aren't enough to represent the data. Normally when you + * declare variables on "the stack" in C, the compiler will try to + * avoid actually implementing a stack and simply use registers. + * However one case where the compiler won't do that is if you declare + * a struct on the stack and you pass its address to a function, in + * which case it always implements a real stack. Typically you'll see + * this with math-related structs like Vec3 or Vec4 or Mtx. * * ================================================================ */ void abi_function_5(float a) { } + From 933746a7d10da16a2a987a033cadf5ec21756206 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Tue, 13 May 2025 17:09:56 -0400 Subject: [PATCH 03/12] chapter 0 start --- chapters_template/00_basic_assembly_and_isa.c | 165 ++++++++++++++++++ .../01_abi_basics.c | 113 ++++++------ .../integers_64_bit.c | 4 +- .../00_basic_assembly_and_isa.c | 43 +++++ .../01_abi_basics.c} | 2 +- .../integers_64_bit.c | 5 +- .../00_basic_assembly_and_isa.h | 17 ++ src/runtime/functions.c | 52 ++++++ src/runtime/functions.h | 1 + src/runtime/main.cpp | 5 + src/runtime/platform.h | 39 +++++ 11 files changed, 392 insertions(+), 54 deletions(-) create mode 100644 chapters_template/00_basic_assembly_and_isa.c rename src_template/c/easy/abi_basics.c => chapters_template/01_abi_basics.c (64%) rename {src_template/c/easy => chapters_template}/integers_64_bit.c (98%) create mode 100644 src/chapters_answers/00_basic_assembly_and_isa.c rename src/{c/easy/abi_basics.c => chapters_answers/01_abi_basics.c} (96%) rename src/{c/easy => chapters_answers}/integers_64_bit.c (89%) create mode 100644 src/chapters_include/00_basic_assembly_and_isa.h create mode 100644 src/runtime/functions.c create mode 100644 src/runtime/functions.h create mode 100644 src/runtime/platform.h diff --git a/chapters_template/00_basic_assembly_and_isa.c b/chapters_template/00_basic_assembly_and_isa.c new file mode 100644 index 0000000..ac722c8 --- /dev/null +++ b/chapters_template/00_basic_assembly_and_isa.c @@ -0,0 +1,165 @@ + +/* ================================================================ * + * + * Welcome to decomp! + * + * This series of example files is designed to teach you the basics of + * decompilation for the Gamecube and Wii. It is recommended to read the + * accompanying article on the wiki before getting started: + + * https://wiki.decomp.dev/en/resources/decomp-intro + * + * ================================================================ */ + +/* ================================================================ * + + * **** CHAPTER 0 - BASIC ASSEMBLY AND ISA OPERATIONS + + * The first place you should look when you encounter an instruction + * you are unfamiliar with is here: + + * https://files.decomp.dev/ppc_isa.pdf + + * This is a specification of what is known as an instruction set + * architecture (ISA), which defines various fundamental properties of + * a CPU like what instructions exist, how they behave, and what + * registers are available. IBM additionally added SIMD-type + * instructions for floating-point registers on the GC/Wii's + * processors known as "paired singles," which can be viewed here: + + * https://wiibrew.org/wiki/Paired_single + + * The implementations of each function in this section are already + * filled out, as jumping straight into writing functions will be a + * bit confusing without learning about how functions are defined + * (covered in the next section). For now simply observe the assembly, + * and consider looking up an instruction in the ISA document to get a + * feel for how they are defined. + + * ================================================================ */ + +/* ================================================================ * + * + * The simplest instruction to consider is `add RT, RA, RB`, as all it + * does is add two registers `RA` and `RB` together and place the + * result in `RT`. Most instructions use general-purpose registers + * (r0-r31), which hold 32 bits of data each on 32-bit PowerPC + * processors and 64 bits on 64-bit processors. The Gamecube and Wii + * both are 32-bit. + * + * ================================================================ */ + +int addition(int a, int b) { + return a + b; +} + +/* ================================================================ * + * + * Data can also be embedded in instructions themselves, such as + * `addi`: + * + * ================================================================ */ + +int addition_with_immediate(int a) { + return a + 7; +} + +/* ================================================================ * + * + * If you look up `li`, you'll notice it doesn't actually + * have its own instruction entry, instead being labeled as an + * "extended mnemonic" of addi. These mnemonics exist for developer + * convenience and don't change the functionality of the instruction, + * so `li, r3, 7` is equivalent to `addi, r3, 0, 7`. + * + * Using addi as the example for this is a bit confusing because addi + * also has a property where if you plug in 0 for the second register + * argument, it's designed to interpret that as a literal 0 instead of + * the register r0, which is why it's possible for it to double as a + * "load immediate" instruction. Note how the mnemonic table writes + * it as `addi, r3, 0, 7` instead of `addi, r3, r0, 7`. + * + * ================================================================ */ + +int load() { + return 7; +} + +/* ================================================================ * + * + * If you're a bit shaky on how pointers work, now is a good time to + * refresh yourself. The contents of r0, which is the number 7, is + * being written to the memory address which is derived from the + * contents of r3, plus an optional offset. + * + * ================================================================ */ + +int store(int *a) { + *a = 7; +} + +int store_offset(int *a) { + a[1] = 7; +} + +/* ================================================================ * + * + * Floating point operations use bespoke floating-point registers + * (f0-f31). They hold 64 bits each regardless of the processor being + * 32-bit or 64-bit, so that both processors can use double-precision + * floats. + * + * In fact, every floating point instruction always operates + * on FPRs as if they were doubles (ISA section 4.2.1), minus the + * aforementioned "paired single" instructions which treat an FPR + * register as two single-precision floats. The way this is achieved + * is that any time a single-precision float has to be loaded from + * memory or stored to memory, it implicitly does a conversion to and + * from double precision respectively, which you can verify by looking + * up "lfs" and "stfs" in the ISA. + * + * You don't have to worry about this in decomp since there still has + * to be separate instructions for single versus double operations + * (adds vs add), which allows you to easily identify the intended + * precision for any instruction. Just remember FPRs are always 64 + * bits long. + * + * ================================================================ */ + +float addition_float(float a, float b) { + return a + b; +} + +void addition_float_load_store(float *a, float *b, float *c) { + *c = *a + *b; +} + +double addition_double(double a, double b) { + return a + b; +} + +void addition_double_load_store(double *a, double *b, double *c) { + *c = *a + *b; +} + +/* ================================================================ * + * + * This is an example of the use of *symbols* for a data load, which + * is the same concept as how it was used to refer to a function in + * `bl adder` in the "Compiling and linking" section of the decomp + * intro. More specifics on the different data section types will be + * covered later. + * + * ================================================================ */ + +int some_int = 21; + +int load_int() { + return some_int; +} + +/* ================================================================ * + * + * End of chapter 0. + * + * ================================================================ */ diff --git a/src_template/c/easy/abi_basics.c b/chapters_template/01_abi_basics.c similarity index 64% rename from src_template/c/easy/abi_basics.c rename to chapters_template/01_abi_basics.c index ce3df8c..bb6983b 100644 --- a/src_template/c/easy/abi_basics.c +++ b/chapters_template/01_abi_basics.c @@ -1,25 +1,39 @@ /* ================================================================ * * - **** ABI BASICS - * - * In order for a piece of software to be able to run on - * a given piece of hardware and/or interoperate with other pieces of - * software on a system, there has to be a general agreement - * as to how the software will interact with various aspects of - * the hardware. This is known as an *application binary interface*, or - * ABI. For example, every library and function on the Gamecube/Wii - * "agrees" that it will use a specific register to return values if - * the function returns something, which you'll see in the first training - * function. - * - * An ABI close to the what the GC/Wii uses that is useful to + **** CHATPER 1 - ABI BASICS + * + * This section will likely be dense and boring, but it will clear up + * a lot of uncertainties as to what is going on when you look at a + * block of assembly. The function implemenetations won't be provided + * this time (though many of the functions are identical to what + * you've already seen), so try and start writing them out to get it + * to match. + * + * While the ISA describes many of the fundamental features and + * aspects of a target architecture, it doesn't perscribe how a given + * program should behave on a given target operating system. That + * instead is delegated to a specification called the *application + * binary interface* (ABI), which theoretically allows a program to + * interoperate with other programs on the system and be able to run + * on any computer with the OS and hardware it was compiled to run on. + * The compiler ultimately gets to define the "final ABI" that the + * program compiled on it uses, often being layered on top of a more + * general OS ABI, so you could hear phrases like "the Metrowerks C++ + * ABI" or "the Linux ABI." + * + * For example, every library and function compiled with a given + * compiler for the GC/Wii "agrees" that it will use a specific + * register to return values if the function returns something, which + * you'll see in the first training function. + * + * An ABI similar to the what Metrowerks uses that is useful to * reference is the System V PowerPC ABI, which can be found here: * * http://refspecs.linux-foundation.org/elf/elfspec_ppc.pdf * - * In addition, since the GC/Wii is an embedded system, its CPU - * also obeys a superset of the ABI known as an *embedded* application - * binary interface*, or EABI, which can be viewed here: + * In addition, since the GC/Wii is an embedded system, it also obeys + * a superset of the ABI known as an *embedded* application binary + * interface*, or EABI, which can be viewed here: * * https://files.decomp.dev/E500ABIUG.pdf * @@ -32,21 +46,21 @@ * * Registers and parameters (Function Calling Sequence, ABI page 3-14) * - * As you may be familiar with, the CPU has to move any data it wants - * to operate on from main memory (RAM) into its own internal registers - * to be able to access it. Thus, an optimization most ABIs utilize - * to reduce the amount of times a given function has to access RAM is - * to allow registers themselves to be used as both function arguments - * and return values. - * - * In the case of non-float values, up to eight general-purpose registers - * can used to pass arguments to a function, starting at GPR3 and ending - * at GPR10. Additionally, GPR3 acts a return register that a callsite of - * the function can read from. - * - * For example, to write a function add() that adds two inputted integers - * together and returns the result, you simply have to add r3 and r4 - * and store the result in r3. + * As you may be familiar with from a computer architecture or + * assembly class, the CPU has to move any data it wants to operate on + * from main memory (RAM) into its own registers to be able to access + * it. Thus, an optimization most ABIs utilize to reduce the amount of + * times a given function has to access RAM is to allow registers + * themselves to be used as both function arguments and return values. + * + * In the case of non-float values, up to eight general-purpose + * registers can used to pass arguments to a function, starting at + * r3 and ending at r10. Additionally, r3 acts a return register + * that a callsite of the function can read from. + * + * For example, to write a function add() that adds two integer + * arguments together and returns the result, you simply have to add + * r3 and r4 and store the result in r3. * * ================================================================ */ @@ -56,12 +70,11 @@ int abi_function_1(int a, int b) { /* ================================================================ * * * For floats, up to eight floating-point registers can be used, - * starting at FPR1 and ending at FPR8. FPR1 also acts as the return - * register. + * starting at f1 and ending at f8. f1 also acts as the return register. * * ================================================================ */ -int abi_function_2(int a, int b) { +float abi_function_2(float a, float b) { } /* ================================================================ * @@ -69,19 +82,19 @@ int abi_function_2(int a, int b) { * Volatile and non-volatile registers (Registers, ABI page 3-14) * * The sets of registers used for function passing (r3-r8, f1-f8) are - * also known as *volatile* registers, because whenever you branch into - * a new function, the ABI allows the values in those registers to get - * overwritten. In other words, any time you step over a "b some_func", - * you must assume that all volatile registers have effectively been - * destroyed (or modified in the case of return registers). Thus - * the concept of "non-volatile" registers become useful, which allow - * us to preserve data between registers that cross callsites without - * having to read/write from main memory. - * - * The simplest example of this behavior can be seen below, where - * r3 is moved to the non-volatile register r31, since r3 can get - * overwritten by some_func, which is then passed back to r3 to - * be used as the return register. The other instructions, which are + * also known as *volatile* registers, because whenever you branch + * into a new function, the ABI allows the values in those registers + * to get overwritten. In other words, any time you step over a "b + * some_func", you must assume that all volatile registers have + * effectively been destroyed (or modified in the case of return + * registers). Thus the concept of "non-volatile" registers become + * useful, which allow us to preserve data between registers that + * cross callsites without having to read/write from main memory. + * + * The simplest example of this behavior can be seen below, where r3 + * is moved to the non-volatile register r31, since r3 can get + * overwritten by some_func, which is then passed back to r3 to be + * used as the return register. The other instructions, which are * related to the stack, will be explained next. * * ================================================================ */ @@ -100,7 +113,9 @@ int abi_function_3(int a) { * "mflr" towards the top and a "mtlr" towards the bottom. These * opcodes all concern a special register called the *link register*, * which holds the memory address of the previous function that called - * the function you're currently in. To explain it briefly: + * the function you're currently in. This was also explained in the + * intro doc, but to reiterate again to help make it stick in your + * brain more: * TODO(fox): the final file could have these be real addresses * @@ -189,7 +204,7 @@ void abi_function_7(int a) { * However one case where the compiler won't do that is if you declare * a struct on the stack and you pass its address to a function, in * which case it always implements a real stack. Typically you'll see - * this with math-related structs like Vec3 or Vec4 or Mtx. + * this with math-related structs like a Vec3 or Vec4 or Matrix struct. * * ================================================================ */ diff --git a/src_template/c/easy/integers_64_bit.c b/chapters_template/integers_64_bit.c similarity index 98% rename from src_template/c/easy/integers_64_bit.c rename to chapters_template/integers_64_bit.c index eb7e1df..fa7788a 100644 --- a/src_template/c/easy/integers_64_bit.c +++ b/chapters_template/integers_64_bit.c @@ -8,6 +8,8 @@ * * ================================================================ */ +#include "src/runtime/platform.h" + /* ================================================================ * * * Register usage @@ -19,7 +21,7 @@ * * ================================================================ */ -u64 add_64(u64 a, u64 b) { +u64 example_64(u64 a, u64 b) { } /* ================================================================ * diff --git a/src/chapters_answers/00_basic_assembly_and_isa.c b/src/chapters_answers/00_basic_assembly_and_isa.c new file mode 100644 index 0000000..25b569b --- /dev/null +++ b/src/chapters_answers/00_basic_assembly_and_isa.c @@ -0,0 +1,43 @@ +#include "src/chapters_include/00_basic_assembly_and_isa.h" + +int addition(int a, int b) { + return a + b; +} + +int addition_with_immediate(int a) { + return a + 7; +} + +int load() { + return 7; +} + +int store(int *a) { + *a = 7; +} + +int store_offset(int *a) { + a[1] = 7; +} + +float addition_float(float a, float b) { + return a + b; +} + +void addition_float_load_store(float *a, float *b, float *c) { + *c = *a + *b; +} + +double addition_double(double a, double b) { + return a + b; +} + +void addition_double_load_store(double *a, double *b, double *c) { + *c = *a + *b; +} + +int some_int = 21; + +int load_int() { + return some_int; +} diff --git a/src/c/easy/abi_basics.c b/src/chapters_answers/01_abi_basics.c similarity index 96% rename from src/c/easy/abi_basics.c rename to src/chapters_answers/01_abi_basics.c index 55561d8..bef7dda 100644 --- a/src/c/easy/abi_basics.c +++ b/src/chapters_answers/01_abi_basics.c @@ -3,7 +3,7 @@ int abi_function_1(int a, int b) { return a + b; } -int abi_function_2(int a, int b) { +float abi_function_2(float a, float b) { return a + b; } diff --git a/src/c/easy/integers_64_bit.c b/src/chapters_answers/integers_64_bit.c similarity index 89% rename from src/c/easy/integers_64_bit.c rename to src/chapters_answers/integers_64_bit.c index f81f01b..716023c 100644 --- a/src/c/easy/integers_64_bit.c +++ b/src/chapters_answers/integers_64_bit.c @@ -1,7 +1,6 @@ +#include "src/runtime/platform.h" -// TODO(fox): Should the typedefs be used for long longs? - -u64 add_64(u64 a, u64 b) { +u64 example_64(u64 a, u64 b) { return a + b; } diff --git a/src/chapters_include/00_basic_assembly_and_isa.h b/src/chapters_include/00_basic_assembly_and_isa.h new file mode 100644 index 0000000..f096119 --- /dev/null +++ b/src/chapters_include/00_basic_assembly_and_isa.h @@ -0,0 +1,17 @@ +#ifndef CHAPTER_0_H +#define CHAPTER_0_H + +int addition(int a, int b); +int addition_with_immediate(int a); +int load(); +int store(int *a); +int store_offset(int *a); +float addition_float(float a, float b); +void addition_float_load_store(float *a, float *b, float *c); +double addition_double(double a, double b); +void addition_double_load_store(double *a, double *b, double *c); +int load_int(); + +extern int some_int; + +#endif diff --git a/src/runtime/functions.c b/src/runtime/functions.c new file mode 100644 index 0000000..1fef39a --- /dev/null +++ b/src/runtime/functions.c @@ -0,0 +1,52 @@ +#include "src/chapters_include/00_basic_assembly_and_isa.h" + +void chapter_0() { + int a = 1; + int b = 1; + float a_f = 1.0F; + float b_f = 1.0F; + float c_f = 1.0F; + double a_d = 1.0F; + double b_d = 1.0F; + double c_d = 1.0F; + + addition(a, b); + addition_with_immediate(a); + load(); + store(&a); + store_offset(&a); + addition_float(a_f, b_f); + addition_float_load_store(&a_f, &b_f, &c_f); + addition_double(a_d, b_d); + addition_double_load_store(&a_d, &b_d, &c_d); + load_int(); +} + +#if 0 +void integers_64() { + u64 a_64 = 1; + u64 b_64 = 1; + u32 a_32 = 1; + + example_64(a_64, b_64); + aligned_64(a_32, b_64); + add_64(a_64, b_64); + add_64_downcast(a_64, b_64); + sub_64(a_64, b_64); + sub_64_downcast(a_64, b_64); + mul_64(a_64, b_64); + mul_64_downcast(a_64, b_64); + div_64(a_64, b_64); + mod_64(a_64, b_64); + shl_64(a_64, b_64); + shr_64(a_64, b_64); + and_64(a_64, b_64); + or_64(a_64, b_64); + xor_64(a_64, b_64); +} +#endif + +void sample_funcs() { + chapter_0(); + // integers_64(); +} diff --git a/src/runtime/functions.h b/src/runtime/functions.h new file mode 100644 index 0000000..84cea21 --- /dev/null +++ b/src/runtime/functions.h @@ -0,0 +1 @@ +void sample_funcs(); diff --git a/src/runtime/main.cpp b/src/runtime/main.cpp index 90ba06c..cc3c9ba 100644 --- a/src/runtime/main.cpp +++ b/src/runtime/main.cpp @@ -1,4 +1,8 @@ #include +extern "C" +{ + #include "src/runtime/functions.h" +} void *operator new( size_t size ) { @@ -20,5 +24,6 @@ static __Sample __sample; int main( void ) { __sample._0 = 0xf3; + sample_funcs(); return 0; } diff --git a/src/runtime/platform.h b/src/runtime/platform.h new file mode 100644 index 0000000..dd4234b --- /dev/null +++ b/src/runtime/platform.h @@ -0,0 +1,39 @@ +#ifndef RUNTIME_PLATFORM_H +#define RUNTIME_PLATFORM_H + +/* + It's not required to use these everywhere, these are mainly for sections + like 64-bit integers. +*/ + +/// A signed 8-bit integer +typedef signed char s8; + +/// A signed 16-bit integer +typedef signed short s16; + +/// A signed 32-bit integer +typedef signed long s32; + +/// A signed 64-bit integer +typedef signed long long s64; + +/// An unsigned 8-bit integer +typedef unsigned char u8; + +/// An unsigned 16-bit integer +typedef unsigned short u16; + +/// An unsigned 32-bit integer +typedef unsigned long u32; + +/// An unsigned 64-bit integer +typedef unsigned long long u64; + +/// A 32-bit floating-point number +typedef float f32; + +/// A 64-bit floating-point number +typedef double f64; + +#endif From 7c1baf28175f8cf3b7ecff376e515dfcb7ff465b Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Tue, 13 May 2025 21:18:52 -0400 Subject: [PATCH 04/12] chapter 1 --- chapters_template/00_basic_assembly_and_isa.c | 18 +++- chapters_template/00_basic_assembly_and_isa.h | 19 ++++ chapters_template/01_abi_basics.c | 99 ++++++++++++------- chapters_template/01_abi_basics.h | 14 +++ chapters_template/integers_64_bit.c | 38 ++++++- .../00_basic_assembly_and_isa.c | 2 +- .../00_basic_assembly_and_isa.h | 0 src/chapters_answers/01_abi_basics.c | 57 +++-------- src/chapters_answers/01_abi_basics.h | 12 +++ src/runtime/functions.c | 16 ++- src/runtime/stuff.c | 10 ++ src/runtime/stuff.h | 13 +++ .../integers_64_bit.c | 37 +++++++ 13 files changed, 242 insertions(+), 93 deletions(-) create mode 100644 chapters_template/00_basic_assembly_and_isa.h create mode 100644 chapters_template/01_abi_basics.h rename src/{chapters_include => chapters_answers}/00_basic_assembly_and_isa.h (100%) create mode 100644 src/chapters_answers/01_abi_basics.h create mode 100644 src/runtime/stuff.c create mode 100644 src/runtime/stuff.h rename {src/chapters_answers => wip}/integers_64_bit.c (55%) diff --git a/chapters_template/00_basic_assembly_and_isa.c b/chapters_template/00_basic_assembly_and_isa.c index ac722c8..6f01265 100644 --- a/chapters_template/00_basic_assembly_and_isa.c +++ b/chapters_template/00_basic_assembly_and_isa.c @@ -49,6 +49,8 @@ * * ================================================================ */ +#include "src/chapters_answers/00_basic_assembly_and_isa.h" + int addition(int a, int b) { return a + b; } @@ -87,10 +89,18 @@ int load() { /* ================================================================ * * - * If you're a bit shaky on how pointers work, now is a good time to - * refresh yourself. The contents of r0, which is the number 7, is - * being written to the memory address which is derived from the - * contents of r3, plus an optional offset. + * The first thing you should do if you get disoriented when looking + * at general-purpose register and you don't know what it's doing is + * to verify whether it's a pointer or not. If you're a bit shaky on + * how pointers work, now is a good time to watch a video or something + * to refresh yourself. + * + * Basically, if you see a register that appears to the right of a + * series of parenthesis in a store or load instruction, like the + * 0x0(r3) and 0x4(r3) here, that register is guaranteed to be a + * pointer. The contents of r0, which is the number 7, is being + * written to the memory address which is derived from the contents of + * r3, plus an optional offset. * * ================================================================ */ diff --git a/chapters_template/00_basic_assembly_and_isa.h b/chapters_template/00_basic_assembly_and_isa.h new file mode 100644 index 0000000..83afe5f --- /dev/null +++ b/chapters_template/00_basic_assembly_and_isa.h @@ -0,0 +1,19 @@ +// No problems require you to modify this header. + +#ifndef CHAPTER_0_H +#define CHAPTER_0_H + +int addition(int a, int b); +int addition_with_immediate(int a); +int load(); +int store(int *a); +int store_offset(int *a); +float addition_float(float a, float b); +void addition_float_load_store(float *a, float *b, float *c); +double addition_double(double a, double b); +void addition_double_load_store(double *a, double *b, double *c); +int load_int(); + +extern int some_int; + +#endif diff --git a/chapters_template/01_abi_basics.c b/chapters_template/01_abi_basics.c index bb6983b..f56ee40 100644 --- a/chapters_template/01_abi_basics.c +++ b/chapters_template/01_abi_basics.c @@ -4,10 +4,9 @@ * * This section will likely be dense and boring, but it will clear up * a lot of uncertainties as to what is going on when you look at a - * block of assembly. The function implemenetations won't be provided - * this time (though many of the functions are identical to what - * you've already seen), so try and start writing them out to get it - * to match. + * block of assembly. I'll start to not provide the solutions on some + * of the functions, so try to start matching the functions when + * they're empty. * * While the ISA describes many of the fundamental features and * aspects of a target architecture, it doesn't perscribe how a given @@ -33,7 +32,7 @@ * * In addition, since the GC/Wii is an embedded system, it also obeys * a superset of the ABI known as an *embedded* application binary - * interface*, or EABI, which can be viewed here: + * interface, or EABI, which can be viewed here: * * https://files.decomp.dev/E500ABIUG.pdf * @@ -42,6 +41,8 @@ * * ================================================================ */ +#include "src/runtime/stuff.h" + /* ================================================================ * * * Registers and parameters (Function Calling Sequence, ABI page 3-14) @@ -64,7 +65,7 @@ * * ================================================================ */ -int abi_function_1(int a, int b) { +int abi_parameters(int a, int b) { } /* ================================================================ * @@ -74,7 +75,7 @@ int abi_function_1(int a, int b) { * * ================================================================ */ -float abi_function_2(float a, float b) { +float abi_float_parameters(float a, float b) { } /* ================================================================ * @@ -97,9 +98,14 @@ float abi_function_2(float a, float b) { * used as the return register. The other instructions, which are * related to the stack, will be explained next. * + * (note: some_func is defined outside this TU, its implementation is + * not important) + * * ================================================================ */ -int abi_function_3(int a) { +int abi_volatile_nonvolatile(int a) { + some_func(); + return a; } /* ================================================================ * @@ -116,13 +122,10 @@ int abi_function_3(int a) { * the function you're currently in. This was also explained in the * intro doc, but to reiterate again to help make it stick in your * brain more: - - * TODO(fox): the final file could have these be real addresses * * | // some_func - * | 0x80103F14 | add r3, r3, 0x5 * | 0x80103F18 | blr - * + * | * | // a bunch of code * | * | // abi_function_4 @@ -135,6 +138,8 @@ int abi_function_3(int a) { * | 0x802D8918 | mtlr r0 * | 0x802D891C | blr * + * (note: addresses may be different from the actual executable) + * * When the "bl 0x80103F14" instruction gets executed, the link * register (LR) is automatically set with the address of the next * instruction at 0x802D8920, which is "lwz r0, 0xc(r1)". Then once it @@ -168,30 +173,8 @@ int abi_function_3(int a) { * * ================================================================ */ -void abi_function_4(float a) { -} - -// TODO(fox): This should probably be combined with the branching -// section. This is a more complicated example than using a single -// conditional, but I think the multiple branches illustrates the -// point better. - -/* ================================================================ * - * - * Typically when a function has early returns, such as the cases in - * this switch statement in abi_function_6, they will branch to the - * epilogue. However if a function doesn't use the stack, the epilogue - * effectively turns into a single blr instruction. In this case, the - * compiler can perform an optimization where it replaces those - * epilogue branches with the epilogue itself and create multiple blrs - * in a single function. - * - * ================================================================ */ - -void abi_function_6(int a) { -} - -void abi_function_7(int a) { +void abi_func_call(float a) { + some_func(); } /* ================================================================ * @@ -208,6 +191,48 @@ void abi_function_7(int a) { * * ================================================================ */ -void abi_function_5(float a) { +void typical_stack_usage(float a) { + Vec3 pos; + pos.x = a; + pos.y = a; + pos.z = a; + some_func_vec3(&pos); +} + +/* ================================================================ * + * + * Here is your first "real" problem without an explanation and + * without the return or input types provided (you'll have to modify + * the accompanying .h file as well). It's a bit tricky, but with the + * knowledge you now have, you should be equipped to tackle and + * understand what at first glance looks like a strange peculiarity. + * Uncomment the functions and pretend that "weird_func" is in another + * TU; don't remove the pragma statements (it's the same trick from + * the intro article to make it not automatically get inlined by the + * compiler). + * + * View the solution here if you get stuck or figure it out; it + * contains an important explanation as well: + * + * https://wiki.decomp.dev/en/resources/decomp-training-answers/chapter_01 + * + * ================================================================ */ + + +/* +??? weird_func(???) { + ??? } +??? call_weird_func(???) { + ??? +} +*/ + +/* ================================================================ * + * + * End of chapter 1. + * + * ================================================================ */ + + diff --git a/chapters_template/01_abi_basics.h b/chapters_template/01_abi_basics.h new file mode 100644 index 0000000..d8023c9 --- /dev/null +++ b/chapters_template/01_abi_basics.h @@ -0,0 +1,14 @@ +// Some problems require you to modify this header. + +#ifndef CHAPTER_1_H +#define CHAPTER_1_H + +int abi_parameters(int a, int b) { +float abi_float_parameters(float a, float b) { +int abi_volatile_nonvolatile(int a) { +void abi_func_call() { +void typical_stack_usage(float a) { +// ??? weird_func(???); +// ??? call_weird_func(???); + +#endif diff --git a/chapters_template/integers_64_bit.c b/chapters_template/integers_64_bit.c index fa7788a..40afd63 100644 --- a/chapters_template/integers_64_bit.c +++ b/chapters_template/integers_64_bit.c @@ -1,10 +1,6 @@ /* ================================================================ * * - **** 64-BIT INTEGERS - * - * Integers of type "long long," aka an integer that takes up 8 bytes, - * or 64 bits, have to be implemented a bit specially since GPRs on - * the GC/Wii are only 32 bits long: + **** 64-BIT INTEGERS (WIP) * * ================================================================ */ @@ -14,6 +10,10 @@ * * Register usage * + * Integers of type "long long," aka an integer that takes up 8 bytes, + * or 64 bits, have to be implemented a bit specially since GPRs on + * the GC/Wii are only 32 bits long. + * * Each 64-bit value occupies two general-purpose registers. When * passed into a function, it simply occupies two continuous registers. * Additionally, r4 can be used in conjunction with r3 as a return value @@ -159,3 +159,31 @@ u64 xor_64(u64 a, u64 b) { * ================================================================ */ // TODO(fox): implement + + + +// MISC + +// TODO(fox): This should probably be combined with the branching +// section. This is a more complicated example than using a single +// conditional, but I think the multiple branches illustrates the +// point better. + +/* ================================================================ * + * + * Typically when a function has early returns, such as the cases in + * this switch statement in abi_function_6, they will branch to the + * epilogue. However if a function doesn't use the stack, the epilogue + * effectively turns into a single blr instruction. In this case, the + * compiler can perform an optimization where it replaces those + * epilogue branches with the epilogue itself and create multiple blrs + * in a single function. + * + * ================================================================ */ + +void abi_function_6(int a) { +} + +void abi_function_7(int a) { +} + diff --git a/src/chapters_answers/00_basic_assembly_and_isa.c b/src/chapters_answers/00_basic_assembly_and_isa.c index 25b569b..2d6a4a0 100644 --- a/src/chapters_answers/00_basic_assembly_and_isa.c +++ b/src/chapters_answers/00_basic_assembly_and_isa.c @@ -1,4 +1,4 @@ -#include "src/chapters_include/00_basic_assembly_and_isa.h" +#include "src/chapters_answers/00_basic_assembly_and_isa.h" int addition(int a, int b) { return a + b; diff --git a/src/chapters_include/00_basic_assembly_and_isa.h b/src/chapters_answers/00_basic_assembly_and_isa.h similarity index 100% rename from src/chapters_include/00_basic_assembly_and_isa.h rename to src/chapters_answers/00_basic_assembly_and_isa.h diff --git a/src/chapters_answers/01_abi_basics.c b/src/chapters_answers/01_abi_basics.c index bef7dda..c247705 100644 --- a/src/chapters_answers/01_abi_basics.c +++ b/src/chapters_answers/01_abi_basics.c @@ -1,68 +1,35 @@ +#include "src/runtime/stuff.h" -int abi_function_1(int a, int b) { +int abi_parameters(int a, int b) { return a + b; } -float abi_function_2(float a, float b) { +float abi_float_parameters(float a, float b) { return a + b; } -int abi_function_3(int a) { +int abi_volatile_nonvolatile(int a) { some_func(); return a; } -void abi_function_4() { +void abi_func_call() { some_func(); } -// TODO(fox): Put this in header, or use from one that -// exists in math header -typedef struct { - float x; - float y; - float z; -} Vec3; - -void abi_function_5(float a) { +void typical_stack_usage(float a) { Vec3 pos; pos.x = a; pos.y = a; pos.z = a; - some_func(&pos); + some_func_vec3(&pos); } -// TODO(fox): This should probably be combined with the branching section - -int abi_function_6(int a) { - switch (a) { - case 1: - return 1; - case 2: - return 2; - case 3: - return 3; - case 4: - return 4; - case 5: - return 5; - } - return 0; +int weird_func(int a) { + return a; } -int abi_function_7(int a) { - some_func(); - switch (a) { - case 1: - return 1; - case 2: - return 2; - case 3: - return 3; - case 4: - return 4; - case 5: - return 5; - } - return 0; +void call_weird_func(int a, int *b) { + *b = weird_func(a); } + diff --git a/src/chapters_answers/01_abi_basics.h b/src/chapters_answers/01_abi_basics.h new file mode 100644 index 0000000..34ce225 --- /dev/null +++ b/src/chapters_answers/01_abi_basics.h @@ -0,0 +1,12 @@ +#ifndef CHAPTER_1_H +#define CHAPTER_1_H + +int abi_parameters(int a, int b); +float abi_float_parameters(float a, float b); +int abi_volatile_nonvolatile(int a); +void abi_func_call(); +void typical_stack_usage(float a); +int weird_func(int a); +void call_weird_func(int a, int *b); + +#endif diff --git a/src/runtime/functions.c b/src/runtime/functions.c index 1fef39a..cfce2c5 100644 --- a/src/runtime/functions.c +++ b/src/runtime/functions.c @@ -1,4 +1,5 @@ -#include "src/chapters_include/00_basic_assembly_and_isa.h" +#include "src/chapters_answers/00_basic_assembly_and_isa.h" +#include "src/chapters_answers/01_abi_basics.h" void chapter_0() { int a = 1; @@ -22,6 +23,18 @@ void chapter_0() { load_int(); } +void chapter_1() { + int b = 3; + + abi_parameters(1, 2); + abi_float_parameters(0.1F, 0.2F); + abi_volatile_nonvolatile(4); + abi_func_call(); + typical_stack_usage(2.0F); + weird_func(5); + call_weird_func(2, &b); +} + #if 0 void integers_64() { u64 a_64 = 1; @@ -48,5 +61,6 @@ void integers_64() { void sample_funcs() { chapter_0(); + chapter_1(); // integers_64(); } diff --git a/src/runtime/stuff.c b/src/runtime/stuff.c new file mode 100644 index 0000000..9326244 --- /dev/null +++ b/src/runtime/stuff.c @@ -0,0 +1,10 @@ +// Don't know what to call this yet, basically put stuff you want outside a +// chapter's TU in here + +#include "src/runtime/stuff.h" + +void some_func() { +} + +void some_func_vec3(Vec3 *vec) { +} diff --git a/src/runtime/stuff.h b/src/runtime/stuff.h new file mode 100644 index 0000000..da18ee2 --- /dev/null +++ b/src/runtime/stuff.h @@ -0,0 +1,13 @@ +#ifndef STUFF_H +#define STUFF_H + +typedef struct { + float x; + float y; + float z; +} Vec3; + +void some_func(); +void some_func_vec3(Vec3 *); + +#endif diff --git a/src/chapters_answers/integers_64_bit.c b/wip/integers_64_bit.c similarity index 55% rename from src/chapters_answers/integers_64_bit.c rename to wip/integers_64_bit.c index 716023c..40949fa 100644 --- a/src/chapters_answers/integers_64_bit.c +++ b/wip/integers_64_bit.c @@ -59,3 +59,40 @@ u64 or_64(u64 a, u64 b) { u64 xor_64(u64 a, u64 b) { return a ^ b; } + +#if 0 +// TODO(fox): This should probably be combined with the branching section + +int abi_function_6(int a) { + switch (a) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 5; + } + return 0; +} + +int abi_function_7(int a) { + some_func(); + switch (a) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + case 4: + return 4; + case 5: + return 5; + } + return 0; +} +#endif From e5ab69268a32180c7e32a3db7a10f95d78527896 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 16 May 2025 12:54:30 -0400 Subject: [PATCH 05/12] set up with objdiff --- .gitignore | 1 + chapters_template/01_abi_basics.h | 14 -- configure.py | 209 +++++++++++++++--- {include => src/runtime}/Common.h | 0 src/runtime/main.cpp | 2 +- src/runtime/platform.h | 39 ---- src/runtime/runtime_core.c | 4 +- {include => src}/runtime/runtime_core.h | 0 src/runtime/runtime_exception.c | 2 +- {include => src}/runtime/runtime_exception.h | 0 .../functions.c => shared/sample_functions.c} | 10 +- .../functions.h => shared/sample_functions.h} | 0 src/{runtime => shared}/stuff.c | 2 +- src/{runtime => shared}/stuff.h | 0 .../main_content}/00_basic_assembly_and_isa.c | 14 +- .../main_content}/00_basic_assembly_and_isa.h | 0 .../main_content}/01_abi_basics.c | 2 +- .../main_content}/01_abi_basics.h | 0 .../main_content}/00_basic_assembly_and_isa.c | 43 +++- .../main_content}/00_basic_assembly_and_isa.h | 4 +- .../main_content}/01_abi_basics.c | 18 +- .../main_content/01_abi_basics.h | 14 ++ .../t_integers_64_bit.c | 0 23 files changed, 263 insertions(+), 115 deletions(-) delete mode 100644 chapters_template/01_abi_basics.h rename {include => src/runtime}/Common.h (100%) delete mode 100644 src/runtime/platform.h rename {include => src}/runtime/runtime_core.h (100%) rename {include => src}/runtime/runtime_exception.h (100%) rename src/{runtime/functions.c => shared/sample_functions.c} (89%) rename src/{runtime/functions.h => shared/sample_functions.h} (100%) rename src/{runtime => shared}/stuff.c (83%) rename src/{runtime => shared}/stuff.h (100%) rename src/{chapters_answers => training_answers/main_content}/00_basic_assembly_and_isa.c (74%) rename src/{chapters_answers => training_answers/main_content}/00_basic_assembly_and_isa.h (100%) rename src/{chapters_answers => training_answers/main_content}/01_abi_basics.c (93%) rename src/{chapters_answers => training_answers/main_content}/01_abi_basics.h (100%) rename {chapters_template => src/training_template/main_content}/00_basic_assembly_and_isa.c (90%) rename {chapters_template => src/training_template/main_content}/00_basic_assembly_and_isa.h (83%) rename {chapters_template => src/training_template/main_content}/01_abi_basics.c (98%) create mode 100644 src/training_template/main_content/01_abi_basics.h rename chapters_template/integers_64_bit.c => wip/t_integers_64_bit.c (100%) diff --git a/.gitignore b/.gitignore index 01b8a80..559ce20 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__/ /out /.vscode build.ninja +objdiff.json diff --git a/chapters_template/01_abi_basics.h b/chapters_template/01_abi_basics.h deleted file mode 100644 index d8023c9..0000000 --- a/chapters_template/01_abi_basics.h +++ /dev/null @@ -1,14 +0,0 @@ -// Some problems require you to modify this header. - -#ifndef CHAPTER_1_H -#define CHAPTER_1_H - -int abi_parameters(int a, int b) { -float abi_float_parameters(float a, float b) { -int abi_volatile_nonvolatile(int a) { -void abi_func_call() { -void typical_stack_usage(float a) { -// ??? weird_func(???); -// ??? call_weird_func(???); - -#endif diff --git a/configure.py b/configure.py index 25545d6..fdd2f3d 100644 --- a/configure.py +++ b/configure.py @@ -2,8 +2,20 @@ import glob import io import os +import json from vendor.ninja_syntax import Writer +from typing import Any +target_src_dir = "training_answers" +base_src_dir = "training_template" + +builddir = "build" +outdir = "build" + +target_builddir = os.path.join(builddir, "target") +target_outdir = os.path.join(outdir, "target") +base_builddir = os.path.join(builddir, "base") +base_outdir = os.path.join(outdir, "base") # TODO: Debug? RELEASE_MWCC_FLAGS = [ @@ -21,7 +33,18 @@ "-opt all", "-inline auto", # User - "-i include", + "-i src/runtime", + "-Isrc/shared", +] + +TARGET_MWCC_FLAGS = \ + RELEASE_MWCC_FLAGS + [ + f"-Isrc/{target_src_dir}/main_content", +] + +BASE_MWCC_FLAGS = \ + RELEASE_MWCC_FLAGS + [ + f"-Isrc/{base_src_dir}/main_content", ] # TODO: Debug? @@ -33,15 +56,139 @@ "-lcf " + os.path.join("$builddir", "ldscript.lcf"), ] +class BuildObject: + def __init__(self, path: str, should_diff: bool, **options: Any) -> None: + self.name = os.path.splitext(path)[0] + self.file_path = path + self.should_diff = should_diff + if should_diff: + self.target_path = os.path.join(target_src_dir, path) + self.base_path = os.path.join(base_src_dir, path) + self.target_obj = os.path.join(target_src_dir, self.name + ".o") + self.base_obj = os.path.join(base_src_dir, self.name + ".o") + else: + self.target_path = path + self.base_path = path + self.target_obj = self.name + ".o" + self.base_obj = self.name + ".o" + self.options: Dict[str, Any] = { + "mw_version": "GC/3.0a5.2", + } + +build_objects = [ + BuildObject('main_content/00_basic_assembly_and_isa.c', True), + BuildObject('main_content/01_abi_basics.c', True), + BuildObject('runtime/runtime_core.c', False), + BuildObject('runtime/runtime_exception.c', False), + BuildObject('runtime/main.cpp', False), + BuildObject('shared/stuff.c', False), + BuildObject('shared/sample_functions.c', False), +] + +def write_objdiff(build_objects: list) -> None: + + objdiff_config: Dict[str, Any] = { + "min_version": "2.0.0-beta.5", + "custom_make": "ninja", + "build_target": False, + "watch_patterns": [ + "*.c", + "*.cp", + "*.cpp", + "*.h", + "*.hpp", + "*.inc", + "*.py", + "*.yml", + "*.txt", + "*.json", + ], + "units": [], + "progress_categories": [], + } + + # decomp.me compiler name mapping + COMPILER_MAP = { + "GC/1.0": "mwcc_233_144", + "GC/1.1": "mwcc_233_159", + "GC/1.2.5": "mwcc_233_163", + "GC/1.2.5e": "mwcc_233_163e", + "GC/1.2.5n": "mwcc_233_163n", + "GC/1.3": "mwcc_242_53", + "GC/1.3.2": "mwcc_242_81", + "GC/1.3.2r": "mwcc_242_81r", + "GC/2.0": "mwcc_247_92", + "GC/2.5": "mwcc_247_105", + "GC/2.6": "mwcc_247_107", + "GC/2.7": "mwcc_247_108", + "GC/3.0a3": "mwcc_41_51213", + "GC/3.0a3.2": "mwcc_41_60126", + "GC/3.0a3.3": "mwcc_41_60209", + "GC/3.0a3.4": "mwcc_42_60308", + "GC/3.0a5": "mwcc_42_60422", + "GC/3.0a5.2": "mwcc_41_60831", + "GC/3.0": "mwcc_41_60831", + "Wii/1.0RC1": "mwcc_42_140", + "Wii/0x4201_127": "mwcc_42_142", + "Wii/1.0a": "mwcc_42_142", + "Wii/1.0": "mwcc_43_145", + "Wii/1.1": "mwcc_43_151", + "Wii/1.3": "mwcc_43_172", + "Wii/1.5": "mwcc_43_188", + "Wii/1.6": "mwcc_43_202", + "Wii/1.7": "mwcc_43_213", + } + + for build_object in build_objects: + if build_object.should_diff: + compiler_version = COMPILER_MAP.get(build_object.options["mw_version"]) + if compiler_version is None: + print(f"Missing scratch compiler mapping for {build_object.options['mw_version']}") + else: + unit_config = { + "name": build_object.file_path, + "target_path": os.path.join(target_builddir, build_object.target_obj), + "base_path": os.path.join(base_builddir, build_object.base_obj), + "scratch": { + "platform": "gc_wii", + "compiler": compiler_version, + "c_flags": " ".join(BASE_MWCC_FLAGS), + "ctx_path": os.path.join(base_builddir, build_object.target_obj), + "build_ctx": True + }, + "metadata": { + "complete": False, + "reverse_fn_order": False, + "source_path": os.path.join("src", build_object.target_path), + "auto_generated": False + } + } + objdiff_config["units"].append(unit_config) + + # Write objdiff.json + with open("objdiff.json", "w", encoding="utf-8") as w: + + def unix_path(input: Any) -> str: + return str(input).replace(os.sep, "/") if input else "" + + json.dump(objdiff_config, w, indent=4, default=unix_path) + + out_buf = io.StringIO() n = Writer(out_buf) n.variable("ninja_required_version", "1.3") n.newline() -n.variable("builddir", "build") -n.variable("outdir", "out") +n.variable("builddir", builddir) +n.variable("outdir", outdir) n.newline() +n.variable("target_builddir", target_builddir) +n.variable("target_outdir", target_outdir) +n.variable("base_builddir", base_builddir) +n.variable("base_outdir", base_outdir) +n.newline() + # TODO: The non-Windows people aren't gonna be happy about this one # NOTE: Perhaps DDD has the answer to this @@ -66,39 +213,45 @@ ) n.rule("dol", command="$dtk elf2dol $in $out", description="DOL $out") -# TODO: Fix dependencies on both "src" and "cpp" -code_in_files = [ - *sorted(glob.glob("src/**/*.c", recursive=True)), - *sorted(glob.glob("src/**/*.cpp", recursive=True)), -] -code_out_files = [] - -for in_file in code_in_files: - code_out_file = os.path.join("$builddir", os.path.splitext(in_file)[0] + ".o") - code_out_files.append(code_out_file) +def write_build_object(out_files: list, in_file: str, input_builddir: str, mwcc_flags: list): + out_file = os.path.join(f"${input_builddir}", os.path.splitext(in_file)[0] + ".o") + out_files.append(out_file) n.build( - outputs=code_out_file, + outputs=out_file, rule="mwcc", - inputs=in_file, + inputs=os.path.join("src", in_file), variables={ - "cflags": " ".join(RELEASE_MWCC_FLAGS), - "basedir": os.path.join("$builddir", os.path.dirname(in_file)), + "cflags": " ".join(mwcc_flags), + "basedir": os.path.join(f"${input_builddir}", os.path.dirname(in_file)), }, ) -n.build( - outputs=os.path.join("$outdir", "main.elf"), - rule="mwld", - inputs=code_out_files, - variables={"ldflags": " ".join(RELEASE_MWLD_FLAGS)}, -) +def write_link(out_files: list, input_outdir: str): + n.build( + outputs=os.path.join(f"${input_outdir}", "main.elf"), + rule="mwld", + inputs=out_files, + variables={"ldflags": " ".join(RELEASE_MWLD_FLAGS)}, + ) + + n.build( + outputs=os.path.join(f"${input_outdir}", "main.dol"), + rule="dol", + inputs=os.path.join(f"${input_outdir}", "main.elf"), + ) -n.build( - outputs=os.path.join("$outdir", "main.dol"), - rule="dol", - inputs=os.path.join("$outdir", "main.elf"), -) +target_out_files = [] +base_out_files = [] + +for build_object in build_objects: + write_build_object(target_out_files, build_object.target_path, "target_builddir", TARGET_MWCC_FLAGS) + write_build_object(base_out_files, build_object.base_path, "base_builddir", BASE_MWCC_FLAGS) + +write_link(target_out_files, "target_outdir") +write_link(base_out_files, "base_outdir") + +write_objdiff(build_objects) with open("build.ninja", "w") as out_file: out_file.write(out_buf.getvalue()) diff --git a/include/Common.h b/src/runtime/Common.h similarity index 100% rename from include/Common.h rename to src/runtime/Common.h diff --git a/src/runtime/main.cpp b/src/runtime/main.cpp index cc3c9ba..edc41df 100644 --- a/src/runtime/main.cpp +++ b/src/runtime/main.cpp @@ -1,7 +1,7 @@ #include extern "C" { - #include "src/runtime/functions.h" + #include "sample_functions.h" } void *operator new( size_t size ) diff --git a/src/runtime/platform.h b/src/runtime/platform.h deleted file mode 100644 index dd4234b..0000000 --- a/src/runtime/platform.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef RUNTIME_PLATFORM_H -#define RUNTIME_PLATFORM_H - -/* - It's not required to use these everywhere, these are mainly for sections - like 64-bit integers. -*/ - -/// A signed 8-bit integer -typedef signed char s8; - -/// A signed 16-bit integer -typedef signed short s16; - -/// A signed 32-bit integer -typedef signed long s32; - -/// A signed 64-bit integer -typedef signed long long s64; - -/// An unsigned 8-bit integer -typedef unsigned char u8; - -/// An unsigned 16-bit integer -typedef unsigned short u16; - -/// An unsigned 32-bit integer -typedef unsigned long u32; - -/// An unsigned 64-bit integer -typedef unsigned long long u64; - -/// A 32-bit floating-point number -typedef float f32; - -/// A 64-bit floating-point number -typedef double f64; - -#endif diff --git a/src/runtime/runtime_core.c b/src/runtime/runtime_core.c index 94f77fb..113df8d 100644 --- a/src/runtime/runtime_core.c +++ b/src/runtime/runtime_core.c @@ -1,5 +1,5 @@ -#include -#include +#include +#include int main( int argc, char **argv ); diff --git a/include/runtime/runtime_core.h b/src/runtime/runtime_core.h similarity index 100% rename from include/runtime/runtime_core.h rename to src/runtime/runtime_core.h diff --git a/src/runtime/runtime_exception.c b/src/runtime/runtime_exception.c index 44d1a4f..8ed54a2 100644 --- a/src/runtime/runtime_exception.c +++ b/src/runtime/runtime_exception.c @@ -1,4 +1,4 @@ -#include +#include /* ================================ * * global_destructor_chain.c diff --git a/include/runtime/runtime_exception.h b/src/runtime/runtime_exception.h similarity index 100% rename from include/runtime/runtime_exception.h rename to src/runtime/runtime_exception.h diff --git a/src/runtime/functions.c b/src/shared/sample_functions.c similarity index 89% rename from src/runtime/functions.c rename to src/shared/sample_functions.c index cfce2c5..1a6602d 100644 --- a/src/runtime/functions.c +++ b/src/shared/sample_functions.c @@ -1,6 +1,7 @@ -#include "src/chapters_answers/00_basic_assembly_and_isa.h" -#include "src/chapters_answers/01_abi_basics.h" +#include "00_basic_assembly_and_isa.h" +#include "01_abi_basics.h" +#if 0 void chapter_0() { int a = 1; int b = 1; @@ -35,7 +36,6 @@ void chapter_1() { call_weird_func(2, &b); } -#if 0 void integers_64() { u64 a_64 = 1; u64 b_64 = 1; @@ -60,7 +60,7 @@ void integers_64() { #endif void sample_funcs() { - chapter_0(); - chapter_1(); + // chapter_0(); + // chapter_1(); // integers_64(); } diff --git a/src/runtime/functions.h b/src/shared/sample_functions.h similarity index 100% rename from src/runtime/functions.h rename to src/shared/sample_functions.h diff --git a/src/runtime/stuff.c b/src/shared/stuff.c similarity index 83% rename from src/runtime/stuff.c rename to src/shared/stuff.c index 9326244..be08bcc 100644 --- a/src/runtime/stuff.c +++ b/src/shared/stuff.c @@ -1,7 +1,7 @@ // Don't know what to call this yet, basically put stuff you want outside a // chapter's TU in here -#include "src/runtime/stuff.h" +#include "stuff.h" void some_func() { } diff --git a/src/runtime/stuff.h b/src/shared/stuff.h similarity index 100% rename from src/runtime/stuff.h rename to src/shared/stuff.h diff --git a/src/chapters_answers/00_basic_assembly_and_isa.c b/src/training_answers/main_content/00_basic_assembly_and_isa.c similarity index 74% rename from src/chapters_answers/00_basic_assembly_and_isa.c rename to src/training_answers/main_content/00_basic_assembly_and_isa.c index 2d6a4a0..e6d83a2 100644 --- a/src/chapters_answers/00_basic_assembly_and_isa.c +++ b/src/training_answers/main_content/00_basic_assembly_and_isa.c @@ -1,4 +1,4 @@ -#include "src/chapters_answers/00_basic_assembly_and_isa.h" +#include "00_basic_assembly_and_isa.h" int addition(int a, int b) { return a + b; @@ -8,6 +8,18 @@ int addition_with_immediate(int a) { return a + 7; } +int subtraction(int a, int b) { + return a - b; +} + +int multiplication(int a, int b) { + return a * b; +} + +int division(int a, int b) { + return a / b; +} + int load() { return 7; } diff --git a/src/chapters_answers/00_basic_assembly_and_isa.h b/src/training_answers/main_content/00_basic_assembly_and_isa.h similarity index 100% rename from src/chapters_answers/00_basic_assembly_and_isa.h rename to src/training_answers/main_content/00_basic_assembly_and_isa.h diff --git a/src/chapters_answers/01_abi_basics.c b/src/training_answers/main_content/01_abi_basics.c similarity index 93% rename from src/chapters_answers/01_abi_basics.c rename to src/training_answers/main_content/01_abi_basics.c index c247705..dae4a4d 100644 --- a/src/chapters_answers/01_abi_basics.c +++ b/src/training_answers/main_content/01_abi_basics.c @@ -1,4 +1,4 @@ -#include "src/runtime/stuff.h" +#include "stuff.h" int abi_parameters(int a, int b) { return a + b; diff --git a/src/chapters_answers/01_abi_basics.h b/src/training_answers/main_content/01_abi_basics.h similarity index 100% rename from src/chapters_answers/01_abi_basics.h rename to src/training_answers/main_content/01_abi_basics.h diff --git a/chapters_template/00_basic_assembly_and_isa.c b/src/training_template/main_content/00_basic_assembly_and_isa.c similarity index 90% rename from chapters_template/00_basic_assembly_and_isa.c rename to src/training_template/main_content/00_basic_assembly_and_isa.c index 6f01265..730c093 100644 --- a/chapters_template/00_basic_assembly_and_isa.c +++ b/src/training_template/main_content/00_basic_assembly_and_isa.c @@ -38,6 +38,8 @@ * ================================================================ */ +#include "00_basic_assembly_and_isa.h" + /* ================================================================ * * * The simplest instruction to consider is `add RT, RA, RB`, as all it @@ -49,10 +51,8 @@ * * ================================================================ */ -#include "src/chapters_answers/00_basic_assembly_and_isa.h" - int addition(int a, int b) { - return a + b; + // return a + b; } /* ================================================================ * @@ -63,7 +63,26 @@ int addition(int a, int b) { * ================================================================ */ int addition_with_immediate(int a) { - return a + 7; + // return a + 7; +} + +/* ================================================================ * + * + * Here are some other arithmetic operations that compile to + * simple assembly: + * + * ================================================================ */ + +int subtraction(int a, int b) { + // return a - b; +} + +int multiplication(int a, int b) { + // return a * b; +} + +int division(int a, int b) { + // return a / b; } /* ================================================================ * @@ -84,7 +103,7 @@ int addition_with_immediate(int a) { * ================================================================ */ int load() { - return 7; + // return 7; } /* ================================================================ * @@ -105,11 +124,11 @@ int load() { * ================================================================ */ int store(int *a) { - *a = 7; + // *a = 7; } int store_offset(int *a) { - a[1] = 7; + // a[1] = 7; } /* ================================================================ * @@ -137,19 +156,19 @@ int store_offset(int *a) { * ================================================================ */ float addition_float(float a, float b) { - return a + b; + // return a + b; } void addition_float_load_store(float *a, float *b, float *c) { - *c = *a + *b; + // *c = *a + *b; } double addition_double(double a, double b) { - return a + b; + // return a + b; } void addition_double_load_store(double *a, double *b, double *c) { - *c = *a + *b; + // *c = *a + *b; } /* ================================================================ * @@ -165,7 +184,7 @@ void addition_double_load_store(double *a, double *b, double *c) { int some_int = 21; int load_int() { - return some_int; + // return some_int; } /* ================================================================ * diff --git a/chapters_template/00_basic_assembly_and_isa.h b/src/training_template/main_content/00_basic_assembly_and_isa.h similarity index 83% rename from chapters_template/00_basic_assembly_and_isa.h rename to src/training_template/main_content/00_basic_assembly_and_isa.h index 83afe5f..82ec68a 100644 --- a/chapters_template/00_basic_assembly_and_isa.h +++ b/src/training_template/main_content/00_basic_assembly_and_isa.h @@ -5,7 +5,9 @@ int addition(int a, int b); int addition_with_immediate(int a); -int load(); +int subtraction(int a, int b); +int multiplication(int a, int b); +int division(int a, int b); int store(int *a); int store_offset(int *a); float addition_float(float a, float b); diff --git a/chapters_template/01_abi_basics.c b/src/training_template/main_content/01_abi_basics.c similarity index 98% rename from chapters_template/01_abi_basics.c rename to src/training_template/main_content/01_abi_basics.c index f56ee40..0489416 100644 --- a/chapters_template/01_abi_basics.c +++ b/src/training_template/main_content/01_abi_basics.c @@ -41,7 +41,7 @@ * * ================================================================ */ -#include "src/runtime/stuff.h" +#include "stuff.h" /* ================================================================ * * @@ -104,8 +104,8 @@ float abi_float_parameters(float a, float b) { * ================================================================ */ int abi_volatile_nonvolatile(int a) { - some_func(); - return a; + // some_func(); + // return a; } /* ================================================================ * @@ -174,7 +174,7 @@ int abi_volatile_nonvolatile(int a) { * ================================================================ */ void abi_func_call(float a) { - some_func(); + // some_func(); } /* ================================================================ * @@ -192,11 +192,11 @@ void abi_func_call(float a) { * ================================================================ */ void typical_stack_usage(float a) { - Vec3 pos; - pos.x = a; - pos.y = a; - pos.z = a; - some_func_vec3(&pos); + // Vec3 pos; + // pos.x = a; + // pos.y = a; + // pos.z = a; + // some_func_vec3(&pos); } /* ================================================================ * diff --git a/src/training_template/main_content/01_abi_basics.h b/src/training_template/main_content/01_abi_basics.h new file mode 100644 index 0000000..d9f6520 --- /dev/null +++ b/src/training_template/main_content/01_abi_basics.h @@ -0,0 +1,14 @@ +// Some problems require you to modify this header. + +#ifndef CHAPTER_1_H +#define CHAPTER_1_H + +int abi_parameters(int a, int b); +float abi_float_parameters(float a, float b); +int abi_volatile_nonvolatile(int a); +void abi_func_call(); +void typical_stack_usage(float a); +// ??? weird_func(???); +// ??? call_weird_func(???); + +#endif diff --git a/chapters_template/integers_64_bit.c b/wip/t_integers_64_bit.c similarity index 100% rename from chapters_template/integers_64_bit.c rename to wip/t_integers_64_bit.c From c74e889138e0ca4ce2861792312013a506f50d06 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 16 May 2025 16:10:25 -0400 Subject: [PATCH 06/12] add auto compiler download + wibo linux --- .gitignore | 1 + configure.py | 224 ++++++++++++++++++++++++++++++------ tools/download_tool.py | 125 ++++++++++++++++++++ tools/ninja_syntax.py | 254 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 566 insertions(+), 38 deletions(-) create mode 100755 tools/download_tool.py create mode 100644 tools/ninja_syntax.py diff --git a/.gitignore b/.gitignore index 559ce20..daeef79 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ /.vscode build.ninja objdiff.json +.ninja_* diff --git a/configure.py b/configure.py index fdd2f3d..41e3d11 100644 --- a/configure.py +++ b/configure.py @@ -3,19 +3,40 @@ import io import os import json -from vendor.ninja_syntax import Writer -from typing import Any +import sys +import platform +from tools.ninja_syntax import Writer, serialize_path +from pathlib import Path +from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast + +binutils_tag = "2.42-1" +compilers_tag = "20250513" +dtk_tag = "v1.0.0" # currently unused? +objdiff_tag = "v2.7.1" +sjiswrap_tag = "v1.1.1" +wibo_tag = "0.6.11" + +linker_version = "GC/3.0a5.2" target_src_dir = "training_answers" base_src_dir = "training_template" -builddir = "build" -outdir = "build" +tools_dir = Path("tools") +build_dir = Path("build") +out_dir = "build" + +target_build_dir = os.path.join(build_dir, "src", "target") +target_out_dir = os.path.join(out_dir, "src", "target") +base_build_dir = os.path.join(build_dir, "src", "base") +base_out_dir = os.path.join(out_dir, "src", "base") -target_builddir = os.path.join(builddir, "target") -target_outdir = os.path.join(outdir, "target") -base_builddir = os.path.join(builddir, "base") -base_outdir = os.path.join(outdir, "base") +def is_windows() -> bool: + return os.name == "nt" + +# On Windows, we need this to use && in commands +CHAIN = "cmd /c " if is_windows() else "" +# Native executable extension +EXE = ".exe" if is_windows() else "" # TODO: Debug? RELEASE_MWCC_FLAGS = [ @@ -53,7 +74,7 @@ "-nodefaults", "-mapunused", "-listclosure", - "-lcf " + os.path.join("$builddir", "ldscript.lcf"), + "-lcf " + os.path.join("$build_dir", "ldscript.lcf"), ] class BuildObject: @@ -147,13 +168,13 @@ def write_objdiff(build_objects: list) -> None: else: unit_config = { "name": build_object.file_path, - "target_path": os.path.join(target_builddir, build_object.target_obj), - "base_path": os.path.join(base_builddir, build_object.base_obj), + "target_path": os.path.join(target_build_dir, build_object.target_obj), + "base_path": os.path.join(base_build_dir, build_object.base_obj), "scratch": { "platform": "gc_wii", "compiler": compiler_version, "c_flags": " ".join(BASE_MWCC_FLAGS), - "ctx_path": os.path.join(base_builddir, build_object.target_obj), + "ctx_path": os.path.join(base_build_dir, build_object.target_obj), "build_ctx": True }, "metadata": { @@ -180,41 +201,168 @@ def unix_path(input: Any) -> str: n.variable("ninja_required_version", "1.3") n.newline() -n.variable("builddir", builddir) -n.variable("outdir", outdir) +n.variable("build_dir", build_dir) +n.variable("out_dir", out_dir) n.newline() -n.variable("target_builddir", target_builddir) -n.variable("target_outdir", target_outdir) -n.variable("base_builddir", base_builddir) -n.variable("base_outdir", base_outdir) +n.variable("target_build_dir", target_build_dir) +n.variable("target_out_dir", target_out_dir) +n.variable("base_build_dir", base_build_dir) +n.variable("base_out_dir", base_out_dir) n.newline() +n.variable("mw_version", Path(linker_version)) + +# The command line args and the ability to pass in an executable/compiler +# folder could be added if needed. + +### +# Tooling +### +n.comment("Tooling") + +build_tools_path = build_dir / "tools" + +download_tool = tools_dir / "download_tool.py" +n.rule( + name="download_tool", + command=f"$python {download_tool} $tool $out --tag $tag", + description="TOOL $out", +) + +dtk = build_tools_path / f"dtk{EXE}" +n.build( + outputs=dtk, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "dtk", + "tag": dtk_tag, + }, +) + +objdiff = build_tools_path / f"objdiff-cli{EXE}" +n.build( + outputs=objdiff, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "objdiff-cli", + "tag": objdiff_tag, + }, +) + +sjiswrap = build_tools_path / "sjiswrap.exe" +n.build( + outputs=sjiswrap, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "sjiswrap", + "tag": sjiswrap_tag, + }, +) + +# Only add an implicit dependency on wibo if we download it +wrapper = None +wrapper_implicit: Optional[Path] = None +if ( + wibo_tag is not None + and sys.platform == "linux" + and platform.machine() in ("i386", "x86_64") +): + wrapper = build_tools_path / "wibo" + wrapper_implicit = wrapper + n.build( + outputs=wrapper, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "wibo", + "tag": wibo_tag, + }, + ) +if not is_windows() and wrapper is None: + wrapper = Path("wine") +wrapper_cmd = f"{wrapper} " if wrapper else "" + +compilers = build_dir / "compilers" +compilers_implicit = compilers +n.build( + outputs=compilers, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "compilers", + "tag": compilers_tag, + }, +) + +binutils = build_dir / "binutils" +binutils_implicit = binutils +n.build( + outputs=binutils, + rule="download_tool", + implicit=download_tool, + variables={ + "tool": "binutils", + "tag": binutils_tag, + }, +) + +n.newline() + +### +# Helper rule for downloading all tools +### +n.comment("Download all tools") +n.build( + outputs="tools", + rule="phony", + inputs=[dtk, sjiswrap, wrapper, compilers, binutils, objdiff], +) +n.newline() + +### +# Build rules +### + +compiler_path = compilers / "$mw_version" + +# MWCC +mwcc = compiler_path / "mwcceppc.exe" +mwcc_cmd = f"{wrapper_cmd}{mwcc} $cflags -MMD -c $in -o $basedir" + +mwld = compiler_path / "mwldeppc.exe" +mwld_cmd = f"{wrapper_cmd}{mwld} $ldflags -o $out @$out.rsp" -# TODO: The non-Windows people aren't gonna be happy about this one -# NOTE: Perhaps DDD has the answer to this -n.variable("compiler", os.path.join("$builddir", "compiler", "mwcceppc.exe")) -n.variable("linker", os.path.join("$builddir", "compiler", "mwldeppc.exe")) -n.variable("dtk", os.path.join("$builddir", "dtk.exe")) n.newline() n.rule( "mwcc", - command="$compiler $cflags -MMD -c $in -o $basedir", + command=mwcc_cmd, description="MWCC $out", depfile="$out.d", deps="gcc", ) n.rule( "mwld", - command="$linker $ldflags -o $out @$out.rsp", + command=mwld_cmd, description="MWLD $out", rspfile="$out.rsp", rspfile_content="$in_newline", ) -n.rule("dol", command="$dtk elf2dol $in $out", description="DOL $out") -def write_build_object(out_files: list, in_file: str, input_builddir: str, mwcc_flags: list): - out_file = os.path.join(f"${input_builddir}", os.path.splitext(in_file)[0] + ".o") +n.comment("Generate DOL") +n.rule( + name="elf2dol", + command=f"{dtk} elf2dol $in $out", + description="DOL $out", +) +n.newline() + + +def write_build_object(out_files: list, in_file: str, input_build_dir: str, mwcc_flags: list): + out_file = os.path.join(f"${input_build_dir}", os.path.splitext(in_file)[0] + ".o") out_files.append(out_file) n.build( @@ -223,33 +371,33 @@ def write_build_object(out_files: list, in_file: str, input_builddir: str, mwcc_ inputs=os.path.join("src", in_file), variables={ "cflags": " ".join(mwcc_flags), - "basedir": os.path.join(f"${input_builddir}", os.path.dirname(in_file)), + "basedir": os.path.join(f"${input_build_dir}", os.path.dirname(in_file)), }, ) -def write_link(out_files: list, input_outdir: str): +def write_link(out_files: list, input_out_dir: str): n.build( - outputs=os.path.join(f"${input_outdir}", "main.elf"), + outputs=os.path.join(f"${input_out_dir}", "main.elf"), rule="mwld", inputs=out_files, variables={"ldflags": " ".join(RELEASE_MWLD_FLAGS)}, ) n.build( - outputs=os.path.join(f"${input_outdir}", "main.dol"), - rule="dol", - inputs=os.path.join(f"${input_outdir}", "main.elf"), + outputs=os.path.join(f"${input_out_dir}", "main.dol"), + rule="elf2dol", + inputs=os.path.join(f"${input_out_dir}", "main.elf"), ) target_out_files = [] base_out_files = [] for build_object in build_objects: - write_build_object(target_out_files, build_object.target_path, "target_builddir", TARGET_MWCC_FLAGS) - write_build_object(base_out_files, build_object.base_path, "base_builddir", BASE_MWCC_FLAGS) + write_build_object(target_out_files, build_object.target_path, "target_build_dir", TARGET_MWCC_FLAGS) + write_build_object(base_out_files, build_object.base_path, "base_build_dir", BASE_MWCC_FLAGS) -write_link(target_out_files, "target_outdir") -write_link(base_out_files, "base_outdir") +write_link(target_out_files, "target_out_dir") +write_link(base_out_files, "base_out_dir") write_objdiff(build_objects) diff --git a/tools/download_tool.py b/tools/download_tool.py new file mode 100755 index 0000000..f4512d0 --- /dev/null +++ b/tools/download_tool.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +### +# Downloads various tools from GitHub releases. +# +# Usage: +# python3 tools/download_tool.py wibo build/tools/wibo --tag 1.0.0 +# +# If changes are made, please submit a PR to +# https://github.com/encounter/dtk-template +### + +import argparse +import io +import os +import platform +import shutil +import stat +import urllib.request +import zipfile +from typing import Callable, Dict +from pathlib import Path + + +def binutils_url(tag): + uname = platform.uname() + system = uname.system.lower() + arch = uname.machine.lower() + if system == "darwin": + system = "macos" + arch = "universal" + elif arch == "amd64": + arch = "x86_64" + + repo = "https://github.com/encounter/gc-wii-binutils" + return f"{repo}/releases/download/{tag}/{system}-{arch}.zip" + + +def compilers_url(tag: str) -> str: + return f"https://files.decomp.dev/compilers_{tag}.zip" + + +def dtk_url(tag: str) -> str: + uname = platform.uname() + suffix = "" + system = uname.system.lower() + if system == "darwin": + system = "macos" + elif system == "windows": + suffix = ".exe" + arch = uname.machine.lower() + if arch == "amd64": + arch = "x86_64" + + repo = "https://github.com/encounter/decomp-toolkit" + return f"{repo}/releases/download/{tag}/dtk-{system}-{arch}{suffix}" + + +def objdiff_cli_url(tag: str) -> str: + uname = platform.uname() + suffix = "" + system = uname.system.lower() + if system == "darwin": + system = "macos" + elif system == "windows": + suffix = ".exe" + arch = uname.machine.lower() + if arch == "amd64": + arch = "x86_64" + + repo = "https://github.com/encounter/objdiff" + return f"{repo}/releases/download/{tag}/objdiff-cli-{system}-{arch}{suffix}" + + +def sjiswrap_url(tag: str) -> str: + repo = "https://github.com/encounter/sjiswrap" + return f"{repo}/releases/download/{tag}/sjiswrap-windows-x86.exe" + + +def wibo_url(tag: str) -> str: + repo = "https://github.com/decompals/wibo" + return f"{repo}/releases/download/{tag}/wibo" + + +TOOLS: Dict[str, Callable[[str], str]] = { + "binutils": binutils_url, + "compilers": compilers_url, + "dtk": dtk_url, + "objdiff-cli": objdiff_cli_url, + "sjiswrap": sjiswrap_url, + "wibo": wibo_url, +} + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("tool", help="Tool name") + parser.add_argument("output", type=Path, help="output file path") + parser.add_argument("--tag", help="GitHub tag", required=True) + args = parser.parse_args() + + url = TOOLS[args.tool](args.tag) + output = Path(args.output) + + print(f"Downloading {url} to {output}") + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req) as response: + if url.endswith(".zip"): + data = io.BytesIO(response.read()) + with zipfile.ZipFile(data) as f: + f.extractall(output) + # Make all files executable + for root, _, files in os.walk(output): + for name in files: + os.chmod(os.path.join(root, name), 0o755) + output.touch(mode=0o755) # Update dir modtime + else: + with open(output, "wb") as f: + shutil.copyfileobj(response, f) + st = os.stat(output) + os.chmod(output, st.st_mode | stat.S_IEXEC) + + +if __name__ == "__main__": + main() diff --git a/tools/ninja_syntax.py b/tools/ninja_syntax.py new file mode 100644 index 0000000..7306ee1 --- /dev/null +++ b/tools/ninja_syntax.py @@ -0,0 +1,254 @@ +# Copyright 2011 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python module for generating .ninja files. + +Note that this is emphatically not a required piece of Ninja; it's +just a helpful utility for build-file-generation systems that already +use Python. +""" + +import re +import textwrap +import os +from io import StringIO +from pathlib import Path +from typing import Dict, List, Match, Optional, Tuple, Union + +NinjaPath = Union[str, Path] +NinjaPaths = Union[ + List[str], + List[Path], + List[NinjaPath], + List[Optional[str]], + List[Optional[Path]], + List[Optional[NinjaPath]], +] +NinjaPathOrPaths = Union[NinjaPath, NinjaPaths] + + +def escape_path(word: str) -> str: + return word.replace("$ ", "$$ ").replace(" ", "$ ").replace(":", "$:") + + +class Writer(object): + def __init__(self, output: StringIO, width: int = 78) -> None: + self.output = output + self.width = width + + def newline(self) -> None: + self.output.write("\n") + + def comment(self, text: str) -> None: + for line in textwrap.wrap( + text, self.width - 2, break_long_words=False, break_on_hyphens=False + ): + self.output.write("# " + line + "\n") + + def variable( + self, + key: str, + value: Optional[NinjaPathOrPaths], + indent: int = 0, + ) -> None: + value = " ".join(serialize_paths(value)) + self._line("%s = %s" % (key, value), indent) + + def pool(self, name: str, depth: int) -> None: + self._line("pool %s" % name) + self.variable("depth", str(depth), indent=1) + + def rule( + self, + name: str, + command: str, + description: Optional[str] = None, + depfile: Optional[NinjaPath] = None, + generator: bool = False, + pool: Optional[str] = None, + restat: bool = False, + rspfile: Optional[NinjaPath] = None, + rspfile_content: Optional[NinjaPath] = None, + deps: Optional[NinjaPathOrPaths] = None, + ) -> None: + self._line("rule %s" % name) + self.variable("command", command, indent=1) + if description: + self.variable("description", description, indent=1) + if depfile: + self.variable("depfile", depfile, indent=1) + if generator: + self.variable("generator", "1", indent=1) + if pool: + self.variable("pool", pool, indent=1) + if restat: + self.variable("restat", "1", indent=1) + if rspfile: + self.variable("rspfile", rspfile, indent=1) + if rspfile_content: + self.variable("rspfile_content", rspfile_content, indent=1) + if deps: + self.variable("deps", deps, indent=1) + + def build( + self, + outputs: NinjaPathOrPaths, + rule: str, + inputs: Optional[NinjaPathOrPaths] = None, + implicit: Optional[NinjaPathOrPaths] = None, + order_only: Optional[NinjaPathOrPaths] = None, + variables: Optional[ + Union[ + List[Tuple[str, Optional[NinjaPathOrPaths]]], + Dict[str, Optional[NinjaPathOrPaths]], + ] + ] = None, + implicit_outputs: Optional[NinjaPathOrPaths] = None, + pool: Optional[str] = None, + dyndep: Optional[NinjaPath] = None, + ) -> List[str]: + outputs = serialize_paths(outputs) + out_outputs = [escape_path(x) for x in outputs] + all_inputs = [escape_path(x) for x in serialize_paths(inputs)] + + if implicit: + implicit = [escape_path(x) for x in serialize_paths(implicit)] + all_inputs.append("|") + all_inputs.extend(map(str, implicit)) + if order_only: + order_only = [escape_path(x) for x in serialize_paths(order_only)] + all_inputs.append("||") + all_inputs.extend(map(str, order_only)) + if implicit_outputs: + implicit_outputs = [ + escape_path(x) for x in serialize_paths(implicit_outputs) + ] + out_outputs.append("|") + out_outputs.extend(map(str, implicit_outputs)) + + self._line( + "build %s: %s" % (" ".join(out_outputs), " ".join([rule] + all_inputs)) + ) + if pool is not None: + self._line(" pool = %s" % pool) + if dyndep is not None: + self._line(" dyndep = %s" % serialize_path(dyndep)) + + if variables: + if isinstance(variables, dict): + iterator = iter(variables.items()) + else: + iterator = iter(variables) + + for key, val in iterator: + self.variable(key, val, indent=1) + + return outputs + + def include(self, path: str) -> None: + self._line("include %s" % path) + + def subninja(self, path: str) -> None: + self._line("subninja %s" % path) + + def default(self, paths: NinjaPathOrPaths) -> None: + self._line("default %s" % " ".join(serialize_paths(paths))) + + def _count_dollars_before_index(self, s: str, i: int) -> int: + """Returns the number of '$' characters right in front of s[i].""" + dollar_count = 0 + dollar_index = i - 1 + while dollar_index > 0 and s[dollar_index] == "$": + dollar_count += 1 + dollar_index -= 1 + return dollar_count + + def _line(self, text: str, indent: int = 0) -> None: + """Write 'text' word-wrapped at self.width characters.""" + leading_space = " " * indent + while len(leading_space) + len(text) > self.width: + # The text is too wide; wrap if possible. + + # Find the rightmost space that would obey our width constraint and + # that's not an escaped space. + available_space = self.width - len(leading_space) - len(" $") + space = available_space + while True: + space = text.rfind(" ", 0, space) + if space < 0 or self._count_dollars_before_index(text, space) % 2 == 0: + break + + if space < 0: + # No such space; just use the first unescaped space we can find. + space = available_space - 1 + while True: + space = text.find(" ", space + 1) + if ( + space < 0 + or self._count_dollars_before_index(text, space) % 2 == 0 + ): + break + if space < 0: + # Give up on breaking. + break + + self.output.write(leading_space + text[0:space] + " $\n") + text = text[space + 1 :] + + # Subsequent lines are continuations, so indent them. + leading_space = " " * (indent + 2) + + self.output.write(leading_space + text + "\n") + + def close(self) -> None: + self.output.close() + + +def serialize_path(input: Optional[NinjaPath]) -> str: + if not input: + return "" + if isinstance(input, Path): + return str(input).replace("/", os.sep) + else: + return str(input) + + +def serialize_paths(input: Optional[NinjaPathOrPaths]) -> List[str]: + if isinstance(input, list): + return [serialize_path(path) for path in input if path] + return [serialize_path(input)] if input else [] + + +def escape(string: str) -> str: + """Escape a string such that it can be embedded into a Ninja file without + further interpretation.""" + assert "\n" not in string, "Ninja syntax does not allow newlines" + # We only have one special metacharacter: '$'. + return string.replace("$", "$$") + + +def expand(string: str, vars: Dict[str, str], local_vars: Dict[str, str] = {}) -> str: + """Expand a string containing $vars as Ninja would. + + Note: doesn't handle the full Ninja variable syntax, but it's enough + to make configure.py's use of it work. + """ + + def exp(m: Match[str]) -> str: + var = m.group(1) + if var == "$": + return "$" + return local_vars.get(var, vars.get(var, "")) + + return re.sub(r"\$(\$|\w*)", exp, string) From b83e5e4fe70b03c2f96d66d4781368c9b670d9b4 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 16 May 2025 17:55:26 -0400 Subject: [PATCH 07/12] proofread --- .../main_content/01_abi_basics.c | 4 +- .../main_content/00_basic_assembly_and_isa.c | 83 +++++++++++-------- .../main_content/01_abi_basics.c | 42 ++++++---- 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/training_answers/main_content/01_abi_basics.c b/src/training_answers/main_content/01_abi_basics.c index dae4a4d..23f8328 100644 --- a/src/training_answers/main_content/01_abi_basics.c +++ b/src/training_answers/main_content/01_abi_basics.c @@ -25,6 +25,8 @@ void typical_stack_usage(float a) { some_func_vec3(&pos); } +#pragma push +#pragma dont_inline on int weird_func(int a) { return a; } @@ -32,4 +34,4 @@ int weird_func(int a) { void call_weird_func(int a, int *b) { *b = weird_func(a); } - +#pragma pop diff --git a/src/training_template/main_content/00_basic_assembly_and_isa.c b/src/training_template/main_content/00_basic_assembly_and_isa.c index 730c093..5ef26fd 100644 --- a/src/training_template/main_content/00_basic_assembly_and_isa.c +++ b/src/training_template/main_content/00_basic_assembly_and_isa.c @@ -4,7 +4,7 @@ * Welcome to decomp! * * This series of example files is designed to teach you the basics of - * decompilation for the Gamecube and Wii. It is recommended to read the + * decompilation for the Gamecube and Wii. It is recommended to read this * accompanying article on the wiki before getting started: * https://wiki.decomp.dev/en/resources/decomp-intro @@ -23,18 +23,23 @@ * This is a specification of what is known as an instruction set * architecture (ISA), which defines various fundamental properties of * a CPU like what instructions exist, how they behave, and what - * registers are available. IBM additionally added SIMD-type - * instructions for floating-point registers on the GC/Wii's - * processors known as "paired singles," which can be viewed here: - - * https://wiibrew.org/wiki/Paired_single + * registers are available [1]. * The implementations of each function in this section are already * filled out, as jumping straight into writing functions will be a * bit confusing without learning about how functions are defined - * (covered in the next section). For now simply observe the assembly, - * and consider looking up an instruction in the ISA document to get a - * feel for how they are defined. + * (covered in the next section). For now simply uncomment the code + * lines and observe the assembly; also consider looking up an + * instruction in the ISA document to get a feel for how the + * ISA definitions are written. + * + * [1] IBM additionally added SIMD-type instructions for + * floating-point registers on the GC/Wii's processors known as + * "paired singles" which can be viewed below, though you don't have + * to worry about them since they almost always generate from manual + * assembler usage. + + * https://wiibrew.org/wiki/Paired_single * ================================================================ */ @@ -87,18 +92,18 @@ int division(int a, int b) { /* ================================================================ * * - * If you look up `li`, you'll notice it doesn't actually - * have its own instruction entry, instead being labeled as an - * "extended mnemonic" of addi. These mnemonics exist for developer - * convenience and don't change the functionality of the instruction, - * so `li, r3, 7` is equivalent to `addi, r3, 0, 7`. + * If you look up `li` (or hover over it in objdiff) you'll notice it + * doesn't actually have its own instruction entry, instead being + * labeled as an "extended mnemonic" of addi. These mnemonics exist + * for developer convenience and don't change the functionality of the + * instruction, so `li, r3, 7` is equivalent to `addi, r3, 0, 7`. * * Using addi as the example for this is a bit confusing because addi * also has a property where if you plug in 0 for the second register * argument, it's designed to interpret that as a literal 0 instead of * the register r0, which is why it's possible for it to double as a - * "load immediate" instruction. Note how the mnemonic table writes - * it as `addi, r3, 0, 7` instead of `addi, r3, r0, 7`. + * "load immediate" instruction. Note how the mnemonic table and + * objdiff write it as `addi, r3, 0, 7` instead of `addi, r3, r0, 7`. * * ================================================================ */ @@ -109,17 +114,18 @@ int load() { /* ================================================================ * * * The first thing you should do if you get disoriented when looking - * at general-purpose register and you don't know what it's doing is + * at a general-purpose register and you aren't sure what it's doing is * to verify whether it's a pointer or not. If you're a bit shaky on * how pointers work, now is a good time to watch a video or something * to refresh yourself. * * Basically, if you see a register that appears to the right of a * series of parenthesis in a store or load instruction, like the - * 0x0(r3) and 0x4(r3) here, that register is guaranteed to be a - * pointer. The contents of r0, which is the number 7, is being - * written to the memory address which is derived from the contents of - * r3, plus an optional offset. + * 0x0(r3) and 0x4(r3) below, that register (until its data gets + * written over by something else) is guaranteed to be a pointer. The + * contents of r0, which is the number 7, is being written to the + * memory address which is derived from the contents of r3, plus an + * optional offset. * * ================================================================ */ @@ -138,20 +144,23 @@ int store_offset(int *a) { * 32-bit or 64-bit, so that both processors can use double-precision * floats. * - * In fact, every floating point instruction always operates - * on FPRs as if they were doubles (ISA section 4.2.1), minus the - * aforementioned "paired single" instructions which treat an FPR - * register as two single-precision floats. The way this is achieved - * is that any time a single-precision float has to be loaded from - * memory or stored to memory, it implicitly does a conversion to and - * from double precision respectively, which you can verify by looking - * up "lfs" and "stfs" in the ISA. + * In fact, every floating point instruction always operates on FPRs + * as if they were doubles [1], as written in section 4.2.1 of the ISA + * doc. The way this is achieved is that any time a single-precision + * float has to be loaded from memory or stored to memory, it + * implicitly does a conversion to and from double precision + * respectively, which you can verify by looking up "lfs" and "stfs" + * in the ISA. * * You don't have to worry about this in decomp since there still has * to be separate instructions for single versus double operations - * (adds vs add), which allows you to easily identify the intended - * precision for any instruction. Just remember FPRs are always 64 - * bits long. + * (adds vs add), so you can easily identify the intended precision + * for any floating-point related instruction. Just remember FPRs are + * always 64 bits long. + * + * [1] minus the aforementioned "paired single" instructions since + * they treat and operate on an FPR register as two single-precision + * floats, which is how they gain the benefit of SIMD. * * ================================================================ */ @@ -176,8 +185,14 @@ void addition_double_load_store(double *a, double *b, double *c) { * This is an example of the use of *symbols* for a data load, which * is the same concept as how it was used to refer to a function in * `bl adder` in the "Compiling and linking" section of the decomp - * intro. More specifics on the different data section types will be - * covered later. + * intro. The finalized address for `some_int` has yet to be decided + * until the linker is run, so the compiler inserts this symbol on the + * `lwz` instruction. + * + * If you're using objdiff, you'll also notice `some_int` appears + * below a tab labelled `.sdata`. This is the *data section* that the + * value resides in, and more specifics on the different data section + * types will be covered later. * * ================================================================ */ diff --git a/src/training_template/main_content/01_abi_basics.c b/src/training_template/main_content/01_abi_basics.c index 0489416..5a12eea 100644 --- a/src/training_template/main_content/01_abi_basics.c +++ b/src/training_template/main_content/01_abi_basics.c @@ -9,7 +9,7 @@ * they're empty. * * While the ISA describes many of the fundamental features and - * aspects of a target architecture, it doesn't perscribe how a given + * aspects of a target architecture, it doesn't prescribe how a given * program should behave on a given target operating system. That * instead is delegated to a specification called the *application * binary interface* (ABI), which theoretically allows a program to @@ -22,8 +22,8 @@ * * For example, every library and function compiled with a given * compiler for the GC/Wii "agrees" that it will use a specific - * register to return values if the function returns something, which - * you'll see in the first training function. + * register to return values if the function returns something (r3), + * which you'll see in the first training function. * * An ABI similar to the what Metrowerks uses that is useful to * reference is the System V PowerPC ABI, which can be found here: @@ -62,7 +62,7 @@ * For example, to write a function add() that adds two integer * arguments together and returns the result, you simply have to add * r3 and r4 and store the result in r3. - * + * * ================================================================ */ int abi_parameters(int a, int b) { @@ -88,18 +88,18 @@ float abi_float_parameters(float a, float b) { * to get overwritten. In other words, any time you step over a "b * some_func", you must assume that all volatile registers have * effectively been destroyed (or modified in the case of return - * registers). Thus the concept of "non-volatile" registers become - * useful, which allow us to preserve data between registers that + * registers). Thus the concept of "non-volatile" registers becomes + * useful, which allows us to preserve data between registers that * cross callsites without having to read/write from main memory. * - * The simplest example of this behavior can be seen below, where r3 - * is moved to the non-volatile register r31, since r3 can get - * overwritten by some_func, which is then passed back to r3 to be - * used as the return register. The other instructions, which are - * related to the stack, will be explained next. + * The simplest example of this behavior can be seen in the `mr r31, + * r3` below, where r3 is moved to the non-volatile register r31, + * since r3 can get overwritten by some_func [1]. r31 is then passed back + * to r3 to be used as the return register. The other instructions, + * which are related to the stack, will be explained next. * - * (note: some_func is defined outside this TU, its implementation is - * not important) + * [1] some_func is defined outside this TU, and its implementation is + * not important for this discussion. * * ================================================================ */ @@ -160,17 +160,19 @@ int abi_volatile_nonvolatile(int a) { * r0, which is then stored in what is known as the "stack" in the * "stw". Note that r1 is a special register which holds the stack * pointer, and its current value is required to be decremented and - * placed on the stack (in the "stwu"), which you can read more about - * on 3-34 of the ABI doc. Then in the epilogue, that address is - * loaded out from the stack back into r0 and moved back into the LR - * in the "lwz" and "mtlr" instructions, which allows the "blr" to - * successfully return to "abi_function_4"'s caller. + * placed on the stack (in the "stwu"). Then in the epilogue, that + * address is loaded out from the stack back into r0 and moved back + * into the LR in the "lwz" and "mtlr" instructions, which allows the + * "blr" to successfully return to "abi_function_4"'s caller. [1] * * The reason "mflr" and "mtlr" don't show up in every function, as * you may be able to guess, is that a function doesn't need to save * and restore the LR unless it actually needs to (i.e. it calls a * function), which is why some_func doesn't have them. * + * [1] You can read more about the stack setup on section 3-34 of the + * ABI doc. + * * ================================================================ */ void abi_func_call(float a) { @@ -218,6 +220,8 @@ void typical_stack_usage(float a) { * * ================================================================ */ +#pragma push +#pragma dont_inline on /* ??? weird_func(???) { @@ -229,6 +233,8 @@ void typical_stack_usage(float a) { } */ +#pragma pop + /* ================================================================ * * * End of chapter 1. From aa92de5245888ce01b44a2bca85a5fd80074a2d9 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 16 May 2025 18:39:50 -0400 Subject: [PATCH 08/12] update readme --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8cf5426..129b3f0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,25 @@ # Decomp Training -This repository builds a DOL with CodeWarrior for the purpose of loading it into analysis software. It is intended to be compiled with minimal runtime, and is not intended to play back on console. +Welcome to decomp! + +This series of (currently WIP) example files is designed to teach you the basics of decompilation for the Gamecube and Wii. It is recommended to read [this](https://wiki.decomp.dev/en/resources/decomp-intro) accompanying article on the wiki before getting started. + +To get started, follow the [build](#building) steps and download [objdiff](https://github.com/encounter/objdiff). After building, set the active project in objdiff by selecting your project directory in File -> Project -> Project directory. All lessons are contained in [src/training_template](src/training_template). Avoid looking at files in `src/training_answers` to prevent accidentally getting spoiled on the solutions. + +## Technical information + +This repository builds a DOL with CodeWarrior for the purpose of loading it into analysis software. It is intended to be compiled with minimal runtime, and is not intended to play back on console. The build script is fairly simple for now, but more features could be added in the future to support things like relocatables or splitting the executable with dtk to demonstrate linking behavior. + +## Planned content: + +see [here](https://github.com/thefoxcam/decomp-training/wiki) ## Dependencies - ninja 1.3 - Python 3.6 -- [dtk](https://github.com/encounter/decomp-toolkit) -- CodeWarrior v3.0a5.2 -Extract dtk to `build`, and extract CodeWarrior to `build/compiler`. +The configure script will pull all other dependencies. ## Building From a2b1b80b91894df44db2e7887a65355e26b00b76 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Sat, 17 May 2025 10:12:36 -0400 Subject: [PATCH 09/12] fix implicits to make tooling auto download work --- configure.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configure.py b/configure.py index 41e3d11..a742379 100644 --- a/configure.py +++ b/configure.py @@ -331,9 +331,11 @@ def unix_path(input: Any) -> str: # MWCC mwcc = compiler_path / "mwcceppc.exe" mwcc_cmd = f"{wrapper_cmd}{mwcc} $cflags -MMD -c $in -o $basedir" +mwcc_implicit: List[Optional[Path]] = [compilers_implicit or mwcc, wrapper_implicit] mwld = compiler_path / "mwldeppc.exe" mwld_cmd = f"{wrapper_cmd}{mwld} $ldflags -o $out @$out.rsp" +mwld_implicit: List[Optional[Path]] = [compilers_implicit or mwld, wrapper_implicit] n.newline() @@ -373,6 +375,7 @@ def write_build_object(out_files: list, in_file: str, input_build_dir: str, mwcc "cflags": " ".join(mwcc_flags), "basedir": os.path.join(f"${input_build_dir}", os.path.dirname(in_file)), }, + implicit=mwcc_implicit, ) def write_link(out_files: list, input_out_dir: str): @@ -381,12 +384,14 @@ def write_link(out_files: list, input_out_dir: str): rule="mwld", inputs=out_files, variables={"ldflags": " ".join(RELEASE_MWLD_FLAGS)}, + implicit=mwld_implicit, ) n.build( outputs=os.path.join(f"${input_out_dir}", "main.dol"), rule="elf2dol", inputs=os.path.join(f"${input_out_dir}", "main.elf"), + implicit=dtk, ) target_out_files = [] From 5e0c8590c58efae381655260f4aa069108edcd5b Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Sat, 17 May 2025 17:27:13 -0400 Subject: [PATCH 10/12] chapter 1 changes --- configure.py | 13 +++-- .../main_content/01_abi_basics.c | 47 ++++++++++--------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/configure.py b/configure.py index a742379..2fbad9b 100644 --- a/configure.py +++ b/configure.py @@ -46,11 +46,9 @@ def is_windows() -> bool: "-proc gekko", "-align powerpc", "-enum int", - "-enc SJIS", "-fp hardware", "-Cpp_exceptions off", '-pragma "cats off"', - "-ipa file", "-opt all", "-inline auto", # User @@ -93,7 +91,7 @@ def __init__(self, path: str, should_diff: bool, **options: Any) -> None: self.target_obj = self.name + ".o" self.base_obj = self.name + ".o" self.options: Dict[str, Any] = { - "mw_version": "GC/3.0a5.2", + "mw_version": "GC/1.2.5n", } build_objects = [ @@ -362,8 +360,8 @@ def unix_path(input: Any) -> str: ) n.newline() - -def write_build_object(out_files: list, in_file: str, input_build_dir: str, mwcc_flags: list): +# TODO: this signature is pretty bad +def write_build_object(out_files: list, in_file: str, input_build_dir: str, mwcc_flags: list, options: Dict[str, Any]): out_file = os.path.join(f"${input_build_dir}", os.path.splitext(in_file)[0] + ".o") out_files.append(out_file) @@ -374,6 +372,7 @@ def write_build_object(out_files: list, in_file: str, input_build_dir: str, mwcc variables={ "cflags": " ".join(mwcc_flags), "basedir": os.path.join(f"${input_build_dir}", os.path.dirname(in_file)), + "mw_version": options["mw_version"] }, implicit=mwcc_implicit, ) @@ -398,8 +397,8 @@ def write_link(out_files: list, input_out_dir: str): base_out_files = [] for build_object in build_objects: - write_build_object(target_out_files, build_object.target_path, "target_build_dir", TARGET_MWCC_FLAGS) - write_build_object(base_out_files, build_object.base_path, "base_build_dir", BASE_MWCC_FLAGS) + write_build_object(target_out_files, build_object.target_path, "target_build_dir", TARGET_MWCC_FLAGS, build_object.options) + write_build_object(base_out_files, build_object.base_path, "base_build_dir", BASE_MWCC_FLAGS, build_object.options) write_link(target_out_files, "target_out_dir") write_link(base_out_files, "base_out_dir") diff --git a/src/training_template/main_content/01_abi_basics.c b/src/training_template/main_content/01_abi_basics.c index 5a12eea..f9fe781 100644 --- a/src/training_template/main_content/01_abi_basics.c +++ b/src/training_template/main_content/01_abi_basics.c @@ -128,7 +128,7 @@ int abi_volatile_nonvolatile(int a) { * | * | // a bunch of code * | - * | // abi_function_4 + * | // abi_func_call * | 0x802D8910 | mflr r0 * | 0x802D8914 | stw r0, 0x4(r1) * | 0x802D8918 | stwu r1, -0x8(r1) @@ -145,11 +145,11 @@ int abi_volatile_nonvolatile(int a) { * instruction at 0x802D8920, which is "lwz r0, 0xc(r1)". Then once it * hits the blr in "some_func" at 0x80103F18, it branches to the value * at the LR (0x802D8920) and resumes where it left off in - * "abi_function_4." + * "abi_func_call." * * However you may be wondering, if the LR gets overwritten by the "bl - * 0x80103F14" in "abi_function_4," then what will happen to the LR - * that "abi_function_4" is currently holding? It won't be able to + * 0x80103F14" in "abi_func_call," then what will happen to the LR + * that "abi_func_call" is currently holding? It won't be able to * return to the function that's calling itself when it executes its * "blr" if its LR gets overwritten! Luckily, that problem is exactly * what all of the other instructions are addressing, which are a part @@ -163,7 +163,7 @@ int abi_volatile_nonvolatile(int a) { * placed on the stack (in the "stwu"). Then in the epilogue, that * address is loaded out from the stack back into r0 and moved back * into the LR in the "lwz" and "mtlr" instructions, which allows the - * "blr" to successfully return to "abi_function_4"'s caller. [1] + * "blr" to successfully return to "abi_func_call"'s caller. [1] * * The reason "mflr" and "mtlr" don't show up in every function, as * you may be able to guess, is that a function doesn't need to save @@ -175,7 +175,7 @@ int abi_volatile_nonvolatile(int a) { * * ================================================================ */ -void abi_func_call(float a) { +void abi_func_call(int a) { // some_func(); } @@ -203,18 +203,17 @@ void typical_stack_usage(float a) { /* ================================================================ * * - * Here is your first "real" problem without an explanation and - * without the return or input types provided (you'll have to modify - * the accompanying .h file as well). It's a bit tricky, but with the - * knowledge you now have, you should be equipped to tackle and - * understand what at first glance looks like a strange peculiarity. - * Uncomment the functions and pretend that "weird_func" is in another - * TU; don't remove the pragma statements (it's the same trick from - * the intro article to make it not automatically get inlined by the - * compiler). + * Here is your first "real" problem without an explanation. It's a + * bit tricky, but with the knowledge you now have, you should be + * equipped to tackle and understand what at first glance looks like a + * strange peculiarity. Pretend that "weird_func" is in another TU and + * figure out why the first two functions match but the second one + * doesn't. Don't remove the pragma statements (it's the same trick + * from the intro article to make it not automatically get inlined by + * the compiler). * - * View the solution here if you get stuck or figure it out; it - * contains an important explanation as well: + * View the solution here if you need hints, get stuck, or figure it + * out; it contains an important explanation as well: * * https://wiki.decomp.dev/en/resources/decomp-training-answers/chapter_01 * @@ -223,15 +222,17 @@ void typical_stack_usage(float a) { #pragma push #pragma dont_inline on -/* -??? weird_func(???) { - ??? +int weird_func(void) { } -??? call_weird_func(???) { - ??? +void call_weird_func(int a, int *b) { + *b = weird_func(); +} + +void call_weird_func_2(int a, int *b) { + a = a + 2; + *b = weird_func(); } -*/ #pragma pop From 7a09667b7caa45e387cad730377e2a73991690b8 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Wed, 30 Jul 2025 18:26:44 -0400 Subject: [PATCH 11/12] fix tools download for mac --- configure.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index 2fbad9b..5081e44 100644 --- a/configure.py +++ b/configure.py @@ -279,7 +279,9 @@ def unix_path(input: Any) -> str: "tag": wibo_tag, }, ) +using_wine = False if not is_windows() and wrapper is None: + using_wine = True wrapper = Path("wine") wrapper_cmd = f"{wrapper} " if wrapper else "" @@ -312,11 +314,15 @@ def unix_path(input: Any) -> str: ### # Helper rule for downloading all tools ### +tools_inputs = [dtk, sjiswrap, compilers, binutils, objdiff] +if using_wine is False: + tools_inputs.append(wrapper) + n.comment("Download all tools") n.build( outputs="tools", rule="phony", - inputs=[dtk, sjiswrap, wrapper, compilers, binutils, objdiff], + inputs=tools_inputs, ) n.newline() From d8eb2b629dcd82b7dc7d4ee6bbd338f65a8fe4a7 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 1 Aug 2025 20:53:03 -0400 Subject: [PATCH 12/12] fix objdiff metadata, add missing func in 01 --- configure.py | 2 +- src/training_answers/main_content/01_abi_basics.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index 5081e44..01eca01 100644 --- a/configure.py +++ b/configure.py @@ -178,7 +178,7 @@ def write_objdiff(build_objects: list) -> None: "metadata": { "complete": False, "reverse_fn_order": False, - "source_path": os.path.join("src", build_object.target_path), + "source_path": os.path.join("src", build_object.base_path), "auto_generated": False } } diff --git a/src/training_answers/main_content/01_abi_basics.c b/src/training_answers/main_content/01_abi_basics.c index 23f8328..896b20b 100644 --- a/src/training_answers/main_content/01_abi_basics.c +++ b/src/training_answers/main_content/01_abi_basics.c @@ -34,4 +34,9 @@ int weird_func(int a) { void call_weird_func(int a, int *b) { *b = weird_func(a); } + +void call_weird_func_2(int a, int *b) { + a = a + 2; + *b = weird_func(a); +} #pragma pop