conductor(deob_c11_ref): cluster_0_pikuma_duffle.md - 9 headers + 2 gte_hello files; primary C11 convention source
26 sections. ~200 LOC. PRIMARY C11 convention source: 9 Pikuma duffle headers + 2 gte_hello files. Documents the duffle type system (U1/U2/U4, S1/S2/S4, B1/B2/B4), the macro style (I_, FI_, NI_, LP_, internal, global, RO_, T_), the hand-rolled DSL pattern (enc_*, asm_inline, asm_clobber, clb_*), the slice/arena allocator, the INTELLISENSE_DIRECTIVES pattern, the pragma region pattern, the design-doc comment style.
This commit is contained in:
+687
@@ -0,0 +1,687 @@
|
||||
# Cluster 0: Pikuma duffle + gte_hello (PRIMARY C11 convention source)
|
||||
|
||||
**Track:** `video_analysis_deob_c11_reference_20260623`
|
||||
**Date:** 2026-06-23
|
||||
**Files audited:** 11 (9 Pikuma duffle headers + 2 gte_hello files)
|
||||
**Role:** PRIMARY C11 convention source
|
||||
|
||||
> **Reading guide.** This cluster sub-report surveys the Pikuma duffle library + the gte_hello integration example. The duffle is the user's primary C11 convention source — the "DSL" the user has built for C11 programming on the PS1. The gte_hello file shows how the duffle is integrated in practice.
|
||||
|
||||
---
|
||||
|
||||
## File inventory
|
||||
|
||||
| File | LOC | Role |
|
||||
|---|---|---|
|
||||
| `duffle/dsl.h` | 219 | The core DSL: type modifiers, macros, slicing, arena, control flow |
|
||||
| `duffle/memory.h` | 121 | Memory primitives: alignment, copy, fill, slice, FArena |
|
||||
| `duffle/math.h` | 63 | Math types: V2/V3/V4/R2/Rect/M3 by byte-width (S2/S4) |
|
||||
| `duffle/gp.h` | 122 | GPU commands + bitfield enums + HW MMIO addresses |
|
||||
| `duffle/gte.h` | 658 (read 80) | GTE (Geometry Transformation Engine) encoder layer |
|
||||
| `duffle/gcc_asm.h` | (not read) | GCC inline-assembly primitives |
|
||||
| `duffle/mips.h` | (not read) | MIPS instruction encoder layer |
|
||||
| `duffle/lottes_tape.h` | (not read) | Lottes tape format (graphics asset) |
|
||||
| `duffle/strings.h` | 5 | String slice stub (mostly delegated to dsl.h) |
|
||||
| `gte_hello/hello_gte.h` | 207 | Example integration: duffle + GTE + GP for hello world |
|
||||
| `gte_hello/hello_gte.c` | (not read) | Example main program |
|
||||
|
||||
---
|
||||
|
||||
## §1. The `INTELLISENSE_DIRECTIVES` pattern
|
||||
|
||||
Every duffle header starts with:
|
||||
|
||||
```c
|
||||
#ifdef INTELLISENSE_DIRECTIVES
|
||||
# pragma once
|
||||
# include "dsl.h"
|
||||
# include "math.h"
|
||||
# include "memory.h"
|
||||
#endif
|
||||
```
|
||||
|
||||
**Why:** the user's IDE/parser (CLion, VSCode) gets correct include hints; the actual C compiler sees a normal header (because `INTELLISENSE_DIRECTIVES` is undefined). The tabs in `# pragma once` are intentional — they force the IDE to indent the directive.
|
||||
|
||||
**Convention:** every duffle header uses this pattern. The includes listed inside `INTELLISENSE_DIRECTIVES` are the same as the actual `#include` block elsewhere in the file (so the IDE can resolve them).
|
||||
|
||||
---
|
||||
|
||||
## §2. The duffle type system (byte-width convention)
|
||||
|
||||
The duffle uses a byte-width naming convention for fixed-width types:
|
||||
|
||||
| Suffix | Type | Width |
|
||||
|---|---|---|
|
||||
| `U1`, `U2`, `U4` | unsigned | 1/2/4 bytes |
|
||||
| `S1`, `S2`, `S4` | signed | 1/2/4 bytes |
|
||||
| `B1`, `B2`, `B4` | byte-sized (unsigned) | 1/2/4 bytes |
|
||||
|
||||
Defined via `__UINT8_TYPE__`, `__UINT16_TYPE__`, `__UINT32_TYPE__`, etc. in `dsl.h`:
|
||||
|
||||
```c
|
||||
typedef __UINT8_TYPE__ TSet_(U1);
|
||||
typedef __UINT16_TYPE__ TSet_(U2);
|
||||
typedef __UINT32_TYPE__ TSet_(U4);
|
||||
typedef __INT8_TYPE__ TSet_(S1);
|
||||
typedef __INT16_TYPE__ TSet_(S2);
|
||||
typedef __INT32_TYPE__ TSet_(S4);
|
||||
```
|
||||
|
||||
The `TSet_(type)` macro expands to:
|
||||
```c
|
||||
type; typedef PtrSet_(type);
|
||||
```
|
||||
|
||||
Which expands to:
|
||||
```c
|
||||
type; typedef type* type_R; typedef type V_* type_V;
|
||||
```
|
||||
|
||||
So `TSet_(U4)` produces:
|
||||
- `U4` (the typedef)
|
||||
- `U4_R` (restrict pointer to U4)
|
||||
- `U4_V` (volatile pointer to U4)
|
||||
|
||||
**Why:** the user has an allergy to writing `unsigned int` (per Cluster 0 in the warmup's research; the user prefers byte-explicit types). The `TSet_` pattern is the duffle's way of making the byte width explicit while avoiding the verbosity of `typedef unsigned char U1; typedef U1 *U1_R; ...`.
|
||||
|
||||
**Note:** the duffle doesn't have `U8`/`S8` (no 8-byte types). For 64-bit values, the duffle uses `U4` and treats it as a 32-bit value or uses `Slice` for larger blobs. This is a PS1-constraint (32-bit MIPS CPU); for general C11, `uint64_t` / `int64_t` from `<stdint.h>` would be used (raddbg uses `U64`/`S64` from `base_core.h`).
|
||||
|
||||
---
|
||||
|
||||
## §3. The underscore-suffixed type modifiers
|
||||
|
||||
The duffle has a rich vocabulary of single-letter-prefix type modifiers:
|
||||
|
||||
| Modifier | Meaning | Macro |
|
||||
|---|---|---|
|
||||
| `R_` | `restrict` (sole ownership) | `#define R_ restrict` |
|
||||
| `V_` | `volatile` | `#define V_ volatile` |
|
||||
| `EUB_` | "Execute Unit Bound" (restrict; siloed in ALU register file) | `#define EUB_ restrict` |
|
||||
| `ISO_` | "Isolated Provenance" (restrict; electrical memory isolation for SIMD packing) | `#define ISO_ restrict` |
|
||||
| `LSU_` | "Load/Store Unit Bound" (volatile; physical L1 cache sampling) | `#define LSU_ volatile` |
|
||||
| `LIVE_` | "Live External Data" (volatile; external electrical actor) | `#define LIVE_ volatile` |
|
||||
| `LP_` | "Local Persistent" (static within procedure scope) | `#define LP_ static` |
|
||||
| `internal` | Internal linkage | `#define internal static` |
|
||||
| `global` | Global data (mark) | `#define global static` |
|
||||
| `gknown` | Global data used in procedure (mark) | (no expansion) |
|
||||
| `I_` | Internal inline | `#define I_ internal inline` |
|
||||
| `FI_` | Force inline (always_inline) | `#define FI_ inline __attribute__((always_inline))` |
|
||||
| `NI_` | No inline (noinline) | `#define NI_ internal __attribute__((noinline))` |
|
||||
| `RO_` | Read-only data (in `.rodata` section) | `#define RO_ __attribute__((section(".rodata")))` |
|
||||
| `T_` | `typeof` (compile-time type deduction) | `#define T_ typeof` |
|
||||
|
||||
**The fictional semantics:** `EUB_`, `ISO_`, `LSU_`, `LIVE_` are "fictional" semantic categories per the user — they map to `restrict` or `volatile` but carry the user's mental model of *why* the qualifier is needed (Execute Unit Bound = the data should stay in registers; Load/Store Unit Bound = the data must be sampled from the L1 cache; etc.).
|
||||
|
||||
**Why this matters for Pass 3:** the duffle's type modifier system is the user's way of making the *intent* of `restrict`/`volatile` explicit at the call site. Pass 3 code that uses `R_*` or `V_*` is more readable than `restrict`/`volatile` (because the intent is named).
|
||||
|
||||
---
|
||||
|
||||
## §4. The hand-rolled DSL pattern (the `enc_*` family)
|
||||
|
||||
The duffle's defining feature is its hand-rolled DSL for emitting instruction words as raw constants. Example from `gte.h`:
|
||||
|
||||
```c
|
||||
// Per-field encoders (each one self-masks its argument before shifting)
|
||||
U4 enc_gte_sf(U4 v) { return (v & 0x1F) << 0; } // shift amount
|
||||
U4 enc_gte_mx(U4 v) { return (v & 0x1F) << 5; } // matrix row
|
||||
U4 enc_gte_v (U4 v) { return (v & 0x1F) << 10; } // vector
|
||||
U4 enc_gte_cv (U4 v) { return (v & 0x1F) << 15; } // control vector
|
||||
|
||||
// The composite: flat OR of per-field encoders + COP2/CO base
|
||||
U4 enc_gte_cmdw(U4 sf, U4 mx, U4 v, U4 cv, U4 lm, U4 cmd) {
|
||||
return enc_gte_sf(sf) | enc_gte_mx(mx) | enc_gte_v(v) | enc_gte_cv(cv) | (lm << 25) | cmd;
|
||||
}
|
||||
|
||||
// Pre-baked shortcuts
|
||||
U4 gte_cmd_rtpt = enc_gte_cmdw(0, 0, 0, 0, 0, 0x30); // RTPT command
|
||||
U4 gte_cmd_rtps = enc_gte_cmdw(0, 0, 0, 0, 0, 0x01); // RTPS command
|
||||
```
|
||||
|
||||
**Why this matters:** the user has built a per-instruction-set encoder layer (MIPS, GTE, GP) where each field has its own encoder, and the composite is a flat OR. This is the same pattern as the user's Sectored Language V1 (per the warmup's `cluster_9_fged.md`).
|
||||
|
||||
**For Pass 3:** if a video involves hand-rolled bit-packing (graphics, audio codecs, etc.), follow this pattern: per-field encoder + composite + pre-baked shortcuts. Document each encoder's bit range in a comment.
|
||||
|
||||
---
|
||||
|
||||
## §5. The `asm_inline` / `asm_clobber` / `clb_*` pattern
|
||||
|
||||
For runtime-base-register instructions (where the `rs` field is chosen by the compiler at codegen), the duffle uses a "placeholder-pun" pattern:
|
||||
|
||||
```c
|
||||
// Pure (compile-time) instruction — constant-folded into .rodata
|
||||
asm volatile(
|
||||
asm_inline(gte_cmd_rtpt, gte_cmd_nclip, gte_cmd_avsz3)
|
||||
asm_clobber(clb_system)
|
||||
);
|
||||
|
||||
// Runtime-base-register load — caller picks the base GPR
|
||||
register V3_S2* p_in_12 __asm__("$12") = verts[0].ptr;
|
||||
gte_load_v0(p_in_12, R_T4); // R_T4 = 12 = $t4 = $12
|
||||
```
|
||||
|
||||
**The macros** (from `gcc_asm.h`, not fully read but inferred):
|
||||
- `asm_inline(...)` — emits raw `.word` directives from a list of integer constants
|
||||
- `asm_clobber(...)` — declares clobbered registers
|
||||
- `clb_*` — clobber categories (e.g., `clb_system` for system registers)
|
||||
|
||||
**Why:** pure compile-time instructions can be constant-folded into `.rodata`; runtime instructions need a register-binding trick to force the compiler to put the argument in the right register.
|
||||
|
||||
**For Pass 3:** if a video involves hand-rolled assembly (e.g., SIMD intrinsics, custom ABI), follow this pattern: pure → constant-fold; runtime → register-binding + clobber.
|
||||
|
||||
---
|
||||
|
||||
## §6. The `TSet_` / `PtrSet_` typedef pattern
|
||||
|
||||
```c
|
||||
#define TSet_(type) type; typedef PtrSet_(type)
|
||||
#define PtrSet_(type) TypeR_(type); typedef TypeV_(type)
|
||||
#define TypeR_(type) type *R_ type ## _R
|
||||
#define TypeV_(type) type V_* type ## _V
|
||||
```
|
||||
|
||||
So `TSet_(U4)` expands to:
|
||||
```c
|
||||
U4; // the base typedef
|
||||
typedef U4 *R_ U4_R; // restrict pointer typedef
|
||||
typedef U4 V_* U4_V; // volatile pointer typedef
|
||||
```
|
||||
|
||||
**Why:** the base typedef + restrict pointer typedef + volatile pointer typedef pattern. The user can write `U4_R` (restrict pointer to U4) without spelling out `U4 *restrict` every time.
|
||||
|
||||
**The `enum` extension for type-classes:**
|
||||
```c
|
||||
#define Enum_(underlying_type, symbol) underlying_type TSet_(symbol); enum symbol
|
||||
```
|
||||
|
||||
`Enum_(U4, gp_Commands)` expands to:
|
||||
```c
|
||||
U4; typedef U4 *R_ U4_R; typedef U4 V_* U4_V;
|
||||
enum { /* enum body */ };
|
||||
```
|
||||
|
||||
So the enum is backed by a `U4` (32-bit unsigned) with restrict/volatile pointer variants.
|
||||
|
||||
---
|
||||
|
||||
## §7. The `Proc_` / `Struct_` / `Union_` / `Opt_` / `Ret_` declaration pattern
|
||||
|
||||
```c
|
||||
#define Proc_(symbol) symbol
|
||||
#define Struct_(symbol) struct symbol TSet_(symbol); struct symbol
|
||||
#define Union_(symbol) union symbol TSet_(symbol); union symbol
|
||||
#define Opt_(proc) Struct_(tmpl(Opt,proc))
|
||||
#define opt_(symbol, ...) (tmpl(Opt,symbol)){__VA_ARGS__}
|
||||
#define Ret_(proc) Struct_(tmpl(Ret,proc))
|
||||
#define ret_(proc) tmpl(Ret,proc) proc
|
||||
```
|
||||
|
||||
So `Struct_(Foo)` expands to:
|
||||
```c
|
||||
struct Foo TSet_(Foo); // i.e., struct Foo; typedef struct Foo *R_ Foo_R; typedef struct Foo V_* Foo_V;
|
||||
struct Foo // the actual struct body
|
||||
{
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
`Opt_(bar)` (for a procedure `bar`) expands to:
|
||||
```c
|
||||
struct Opt_bar TSet_(Opt_bar);
|
||||
struct Opt_bar
|
||||
{
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Used like:
|
||||
```c
|
||||
opt_(bar, .x = 5, .y = 10); // designated-initializer syntax
|
||||
```
|
||||
|
||||
**Why:** the `TSet_` pattern automatically generates the `Foo` / `Foo_R` / `Foo_V` triple from a single declaration. The user doesn't have to remember to write 3 typedefs.
|
||||
|
||||
**For Pass 3:** use the `Struct_(Name)` / `Union_(Name)` / `Proc_(Name)` macros to declare types. The macros generate the pointer variants automatically.
|
||||
|
||||
---
|
||||
|
||||
## §8. The slice type pattern
|
||||
|
||||
```c
|
||||
typedef Struct_(Slice) { U4 ptr, len; }; // Untyped Slice
|
||||
#define Slice_(type) Struct_(tmpl(Slice,type))
|
||||
typedef Slice_(B1); // typed slice of bytes
|
||||
|
||||
// Iteration
|
||||
#define slice_iter(container, iter) (T_((container).ptr) iter = (container).ptr; iter != slice_end(container); ++ iter)
|
||||
|
||||
// Macros
|
||||
#define slice_end(slice) ((slice).ptr + (slice).len)
|
||||
#define slice_zero(s) slice_zero_(slice_to_ut(s))
|
||||
#define slice_copy(dest, src) do { \
|
||||
static_assert(T_same(dest, src)); \
|
||||
slice_copy_(slice_to_ut(dest), slice_to_ut(src)); \
|
||||
} while(0)
|
||||
```
|
||||
|
||||
**Why:** slices are `(ptr, len)` pairs. The untyped `Slice` uses `U4` for the pointer (because this is a PS1 C codebase with 32-bit pointers); the typed `Slice_(T)` uses `T*` for the pointer. The `slice_iter` macro provides safe iteration with the `slice_end` boundary check.
|
||||
|
||||
**For Pass 3:** if a video involves a contiguous-data operation (e.g., array processing, string manipulation), use the `Slice_(T)` pattern. The untyped `Slice` is for when the type isn't known (e.g., serialization).
|
||||
|
||||
---
|
||||
|
||||
## §9. The arena allocator pattern (`FArena`)
|
||||
|
||||
```c
|
||||
typedef Opt_(farena) { U4 alignment, type_width; };
|
||||
typedef Struct_(FArena) { U4 start, capacity, used; };
|
||||
|
||||
FArena farena_make(Slice mem);
|
||||
Slice farena_push(FArena_R arena, U4 amount, Opt_farena o);
|
||||
void farena_reset(FArena_R arena);
|
||||
void farena_rewind(FArena_R arena, U4 save_point);
|
||||
U4 farena_save(FArena arena);
|
||||
|
||||
#define farena_push_(arena, amount, ...) farena_push((arena), (amount), opt_(farena, __VA_ARGS__))
|
||||
#define farena_push_type(arena, type, ...) (type*) farena_push((arena), 1, opt_(farena, .type_width=S_(type), __VA_ARGS__)).ptr)
|
||||
#define farena_push_array(arena, type, amount, ...) (tmpl(Slice,type)){ (type*) farena_push((arena), (amount), opt_(farena, .type_width=S_(type), __VA_ARGS__)).ptr, (amount) }
|
||||
```
|
||||
|
||||
**Why:** arena allocators are allocation-once-free-all (a.k.a. "linear allocation" or "bump allocator"). The user uses them for transient allocations (per-frame, per-request). The `Opt_farena` options struct lets the caller specify alignment and type width.
|
||||
|
||||
**For Pass 3:** if a video involves temporary allocations (game frames, request scopes), use the `FArena` pattern. The `farena_push_type` macro is the type-safe variant; the `farena_push_array` macro is for typed arrays.
|
||||
|
||||
---
|
||||
|
||||
## §10. The control flow pattern (`defer`, `scope`, `defer_rewind`)
|
||||
|
||||
```c
|
||||
#define defer(expr) for(U4 once= 1; once!=1;++ once,(expr)) // Basic do something after body
|
||||
#define scope(begin,end) for(U4 once=(1,(begin)); once!=1;++ once,(end )) // Do things before or after a scope
|
||||
#define defer_rewind(cursor) for(T_(cursor) sp=cursor,once=0; once!=1;++ once,cursor=sp) // Used with arenas/stacks
|
||||
#define defer_info(type,expr, ...) for(type info= {__VA_ARGS__}; info.once!=1;++info.once,(expr)) // Defer with tracked state
|
||||
```
|
||||
|
||||
**Why:** C doesn't have a `defer` statement (Go/Rust/Swift do). The user implements it with a `for` loop that runs the body once, then runs the deferred expression. The `defer_rewind` variant is for arena allocators (rewind the cursor on scope exit).
|
||||
|
||||
**For Pass 3:** if a video involves cleanup (file close, memory free, lock release), use the `defer` pattern. If the cleanup involves arena state, use `defer_rewind`.
|
||||
|
||||
---
|
||||
|
||||
## §11. The memory ordering pattern (`ooo_drift_`, `ooo_anchor_`, `ooo_drain_`, `ooo_weld_`)
|
||||
|
||||
```c
|
||||
#define ooo_drift_ __ATOMIC_RELAXED // OoO engine allowed to drift
|
||||
#define ooo_anchor_ __ATOMIC_ACQUIRE // Anchor the Load Queue (halt spec lookahead)
|
||||
#define ooo_drain_ __ATOMIC_RELEASE // Drain the Store Buffer (force writeback)
|
||||
#define ooo_weld_ __ATOMIC_SEQ_CST // Weld pipeline (total order bus lock)
|
||||
```
|
||||
|
||||
**Why:** the user thinks of memory ordering in terms of the out-of-order execution engine. `ooo_drift_` is the weakest (no ordering); `ooo_anchor_` is acquire (load-side barrier); `ooo_drain_` is release (store-side barrier); `ooo_weld_` is sequential consistency (full fence).
|
||||
|
||||
**For Pass 3:** if a video involves concurrent/atomic code, use the `ooo_*` pattern. The names are more readable than `__ATOMIC_*` (because the intent is named: "drift" = no barrier; "anchor" = load barrier; etc.).
|
||||
|
||||
---
|
||||
|
||||
## §12. The comment style (the design-doc header)
|
||||
|
||||
The duffle's `gte.h` has a 67-line design-doc header before any code:
|
||||
|
||||
```
|
||||
/* ============================================================================
|
||||
* gte.h — Geometry Transformation Engine (COP2) for the PS1
|
||||
* ============================================================================
|
||||
*
|
||||
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word`
|
||||
* constants from C. No GCC inline-assembly string syntax in the code body.
|
||||
*
|
||||
* PHILOSOPHY
|
||||
* ----------
|
||||
* 1. A 32-bit instruction word is composed from per-field encoders. Each
|
||||
* encoder knows only its own bit range; the composite ORs them together.
|
||||
* No magic numbers inside any encoder body — every shift and mask is a
|
||||
* named constant from the bitfield-layout enum below.
|
||||
*
|
||||
* 2. Pure (compile-time) instructions — every GTE *command* (RTPS, RTPT,
|
||||
* NCLIP, MVMVA, …) and every COP2 *transfer* (ctc2/cfc2) with a constant
|
||||
* rs/rt/rd — are emitted as a single integer constant via
|
||||
* `asm_inline(...)` from gcc_asm.h. The C compiler constant-folds
|
||||
* these into `.word` directives in .rodata.
|
||||
*
|
||||
* 3. Runtime-base-register instructions (lwc2, swc2, lw, sw, …) cannot be
|
||||
* a pure compile-time word because the `rs` field is chosen by the
|
||||
* compiler at codegen. For these we use a "placeholder-pun" pattern:
|
||||
* a fixed register number (R_T4 = $12) is baked into the rs field of
|
||||
* the `.word` constant, and the macro declares a `"r"(arg)` input
|
||||
* constraint plus a clobber on the same register. The compiler is
|
||||
* therefore *forced* to bind `arg` to that exact register, and the
|
||||
* constant is correct.
|
||||
*
|
||||
* USAGE
|
||||
* -----
|
||||
* // Pure command sequence — all bits compile-time:
|
||||
* asm volatile(
|
||||
* asm_inline( gte_cmd_rtpt , gte_cmd_nclip , gte_cmd_avsz3 )
|
||||
* asm_clobber( clb_system )
|
||||
* );
|
||||
*
|
||||
* // Runtime-base-register load — caller picks the base GPR:
|
||||
* register V3_S2* p_in_12 __asm__("$12") = verts[0].ptr;
|
||||
* gte_load_v0(p_in_12, R_T4); // R_T4 = 12 = $t4 = $12
|
||||
*
|
||||
* // Three independent bases for an RTPT pipeline:
|
||||
* register V3_S2* p0 __asm__("$12") = verts[0].ptr;
|
||||
* register V3_S2* p1 __asm__("$13") = verts[1].ptr;
|
||||
* register V3_S2* p2 __asm__("$14") = verts[2].ptr;
|
||||
* gte_load_v0(p0, R_T4);
|
||||
* gte_load_v1(p1, R_T5);
|
||||
* gte_load_v2(p2, R_T6);
|
||||
* gte_rtpt();
|
||||
*
|
||||
* STYLE NOTES
|
||||
* -----------
|
||||
* - Per-field encoders are named `enc_gte_<field>(value)` and each one
|
||||
* self-masks its argument before shifting. Mirrors the `enc_op / enc_rs
|
||||
* / enc_rt / ...` family in mips.h.
|
||||
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of
|
||||
* the per-field encoders, plus the COP2/CO base.
|
||||
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined
|
||||
* for the common cases so call sites read like assembly source.
|
||||
* - All register/field values are enums (not `#define`s) so they show up
|
||||
* in debugger symbol tables and IDE autocomplete.
|
||||
*
|
||||
* SEE ALSO
|
||||
* --------
|
||||
* - gcc_asm.h: the `.word` emitter (`asm_inline`, `asm_clobber`, clobbers)
|
||||
* - mips.h: the MIPS encoder layer this builds on
|
||||
*/
|
||||
```
|
||||
|
||||
**Why:** the header is a mini-manual for the module. It documents:
|
||||
- The module's purpose
|
||||
- The philosophy (the design decisions, in numbered form)
|
||||
- Usage examples
|
||||
- Style notes (the conventions used within the module)
|
||||
- See also (related modules)
|
||||
|
||||
**For Pass 3:** every file in Pass 3 should have a similar design-doc header. The header should be 30-100 lines, depending on the module's complexity.
|
||||
|
||||
---
|
||||
|
||||
## §13. The `pragma region` / `pragma endregion` pattern
|
||||
|
||||
The duffle uses `#pragma region` / `#pragma endregion` to group code sections:
|
||||
|
||||
```c
|
||||
#pragma region DAG
|
||||
// ... DAG macros
|
||||
#pragma endregion DAG
|
||||
|
||||
#pragma region Slice
|
||||
// ... Slice macros
|
||||
#pragma endregion Slice
|
||||
|
||||
#pragma region FArena
|
||||
// ... FArena macros
|
||||
#pragma endregion FArena
|
||||
```
|
||||
|
||||
**Why:** IDEs (CLion, VSCode, Xcode) honor `#pragma region` and add code-folding markers in the gutter. The user uses regions to organize large files into collapsible sections.
|
||||
|
||||
**For Pass 3:** if a file is large (>500 LOC), use `#pragma region` to organize the sections. The region name should match the section's content.
|
||||
|
||||
---
|
||||
|
||||
## §14. The math types (`V2_S2`, `M3_S4`, etc.)
|
||||
|
||||
The duffle's math types follow the byte-width convention:
|
||||
|
||||
| Type | Fields |
|
||||
|---|---|
|
||||
| `V2_S2` | `S2 x, y` |
|
||||
| `V3_S2` | `S2 x, y, z, pad` |
|
||||
| `V4_S2` | `S2 x, y, z, w` |
|
||||
| `V2_S4` | `S4 x, y` |
|
||||
| `V3_S4` | `S4 x, y, z, pad` |
|
||||
| `V4_S4` | `S4 x, y, z, w` |
|
||||
| `R2_S2` | `V2_S2 p0, p1` (range/pair of 2D points) |
|
||||
| `Rect_S2` | `S2 x, y, width, height` |
|
||||
| `M3_S2` | `A3x3_S2 m; A3_S4 t;` (3x3 matrix + 3-component translation) |
|
||||
|
||||
**Why:** the `_S2` / `_S4` suffix is the byte-width of the underlying type. The user has V2/V3/V4 by width (S2 = 16-bit signed, S4 = 32-bit signed).
|
||||
|
||||
**For Pass 3:** if a video involves graphics or linear algebra, use the duffle's math types (V2_S4, M3_S4, etc.) or the raddbg variants (Vec2F32, Vec3S32, etc.).
|
||||
|
||||
---
|
||||
|
||||
## §15. The error handling pattern (`assert`, `slice_assert`)
|
||||
|
||||
```c
|
||||
#define slice_assert(s) do { assert((s).ptr != 0); assert((s).len > 0); } while(0)
|
||||
|
||||
FI_ Slice farena_make(Slice mem) {
|
||||
assert(mem.ptr != 0);
|
||||
assert(mem.len > 0);
|
||||
return (Slice){mem.ptr, mem.len};
|
||||
}
|
||||
```
|
||||
|
||||
**Why:** the duffle uses `assert` for invariants. There are no error codes or exceptions. The assertion is the contract.
|
||||
|
||||
**For Pass 3:** use `assert` for invariants. The user doesn't use error codes or exceptions in C11 code; the assertion is the contract.
|
||||
|
||||
---
|
||||
|
||||
## §16. The `array_decl` / `array_len` / `Array_` pattern
|
||||
|
||||
```c
|
||||
#define array_len(a) (U8)(sizeof(a) / sizeof(typeof((a)[0])))
|
||||
#define array_decl(type, ...) (type[]){__VA_ARGS__}
|
||||
#define Array_sym(type,len) A ## len ## _ ## type
|
||||
#define Array_expand(type,len) type Array_sym(type, len)[len]; typedef PtrSet_(Array_sym(type, len))
|
||||
#define Array_(type,len) Array_expand(type,len)
|
||||
```
|
||||
|
||||
So `Array_(U4, 8)` expands to:
|
||||
```c
|
||||
U4 A8_U4[8]; typedef U4 A8_U4_R; typedef U4 V_* A8_U4_V;
|
||||
```
|
||||
|
||||
**Why:** the user wants typed arrays with explicit length (`A8_U4` = array of 8 U4). The macro generates the array type, the symbol, and the pointer variants.
|
||||
|
||||
**For Pass 3:** if a video involves fixed-size arrays, use the `Array_(T, N)` pattern. The symbol name is `A{N}_{T}` (e.g., `A8_U4`).
|
||||
|
||||
---
|
||||
|
||||
## §17. The `gknown` / `global` / `LP_` / `internal` data scoping
|
||||
|
||||
```c
|
||||
#define global static // Mark global data
|
||||
#define gknown // Mark global data used in procedure
|
||||
#define LP_ static // static data within procedure scope
|
||||
#define internal static // internal
|
||||
```
|
||||
|
||||
**Why:** the duffle has 3 levels of data scoping:
|
||||
- `global` (translation-unit-scope data; uses `static` for internal linkage)
|
||||
- `gknown` (mark only; for data that's used in a procedure but is global; no expansion)
|
||||
- `LP_` (procedure-local persistent; uses `static` so the data persists across calls)
|
||||
- `internal` (internal linkage; uses `static`)
|
||||
|
||||
**For Pass 3:** use `global` for TU-scope data, `LP_` for procedure-local persistent data, `internal` for internal-linkage functions.
|
||||
|
||||
---
|
||||
|
||||
## §18. The `gte_hello` integration pattern
|
||||
|
||||
`hello_gte.h` shows how the duffle is integrated in practice:
|
||||
|
||||
```c
|
||||
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; };
|
||||
typedef Struct_(DrawEnv) {
|
||||
Rect_S2 clip_area;
|
||||
A2_S2 drawing_offset;
|
||||
Rect_S2 texture_window;
|
||||
S2 texture_page;
|
||||
B1 flag_dither;
|
||||
B1 flag_draw_on_display;
|
||||
B1 enable_auto_clear;
|
||||
RGB8 initial_bg_color;
|
||||
DrawEnv_Packed dr_env;
|
||||
};
|
||||
|
||||
DisplayEnv* displayenv_init(DisplayEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDispEnv");
|
||||
DrawEnv* drawenv_init (DrawEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDrawEnv");
|
||||
```
|
||||
|
||||
**Why:** the `__asm__("symbol")` attribute on a function declaration gives the C function a different symbol name in the linker. This is used to bind C functions to the PS1's BIOS / runtime symbols (e.g., `displayenv_init` is bound to the PS1's `SetDefDispEnv` function).
|
||||
|
||||
**For Pass 3:** if a video involves linking to external symbols (BIOS, runtime, shared libraries), use the `__asm__("symbol")` pattern. The C function is the wrapper; the symbol is the actual implementation.
|
||||
|
||||
---
|
||||
|
||||
## §19. The gte_hello macro pattern (set_/get_/add_/sub_/mut_/div_/gt_/lt_/ge_/le_)
|
||||
|
||||
```c
|
||||
#define set_len( p, _len) (((PolyTag*R_)(p))->len = (B1)(_len))
|
||||
#define set_addr(p, _addr) (((PolyTag*R_)(p))->addr = (U4)(_addr))
|
||||
#define set_code(p, _code) (((PolyTag*R_)(p))->code = (B1)(_code))
|
||||
|
||||
#define get_len(p) (B1)(((PolyTag*R_)(p))->len)
|
||||
#define get_code(p) (B1)(((PolyTag*R_)(p))->code)
|
||||
#define get_addr(p) (U4)(((PolyTag*R_)(p))->addr)
|
||||
|
||||
#define set_poly_f3(p) set_len(p, 4), set_code(p, 0x20)
|
||||
#define set_poly_ft3(p) set_len(p, 7), set_code(p, 0x24)
|
||||
// ...
|
||||
```
|
||||
|
||||
**Why:** the user has a set_/get_ macro family for field access on packed structs. The `set_*` macros take a `R_` pointer and a value; the `get_*` macros return the value. The `set_poly_*` macros set the entire primitive at once.
|
||||
|
||||
**For Pass 3:** if a video involves packed structs (graphics, network packets, file formats), use the set_/get_ pattern. The macros are generated for each field.
|
||||
|
||||
---
|
||||
|
||||
## §20. The `FP` fixed-point convention
|
||||
|
||||
```c
|
||||
enum {
|
||||
fp_one = (1 << 12),
|
||||
};
|
||||
|
||||
#define v3s4_fp_one() v3s4(fp_one, fp_one, fp_one)
|
||||
```
|
||||
|
||||
**Why:** the duffle uses 12-bit fixed-point for math (PS1 GTE convention). `fp_one` is `1 << 12 = 4096`. A `V3_S4 fp` value is in units of `1/4096`.
|
||||
|
||||
**For Pass 3:** if a video involves fixed-point math (graphics, audio, embedded), use the `fp` convention. The shift is 12 for Q12 format.
|
||||
|
||||
---
|
||||
|
||||
## §21. The `gp` GPU command pattern (enums + bitfield ORs)
|
||||
|
||||
```c
|
||||
typedef Enum_(U4, gp_Commands) {
|
||||
gcmd_Reset = 0b000,
|
||||
gcmd_Polygon = 0b001,
|
||||
gcmd_Line = 0b010,
|
||||
gcmd_Rect = 0b011,
|
||||
gcmd_VM_to_VM = 0b100,
|
||||
gcmd_CPU_to_VM = 0b101,
|
||||
gcmd_VM_to_CPU = 0b110,
|
||||
gcmd_Environment = 0b111,
|
||||
// ...
|
||||
};
|
||||
|
||||
enum {
|
||||
gcmd_offset = 24,
|
||||
gp_Reset = (gcmd_Reset << gcmd_offset),
|
||||
gp_DisplayEnabled = (gcmd_DisplayEnable << gcmd_offset | 0x0),
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
**Why:** GPU commands are 32-bit words with the command in the high byte (bits 24-31). The `gcmd_offset` defines where the command starts. The composite `gp_DisplayEnabled` ORs the command and the data.
|
||||
|
||||
**For Pass 3:** if a video involves hardware commands (GPU, audio, network), follow this pattern: enum for command IDs, shift offset for the command field, composite OR for the full word.
|
||||
|
||||
---
|
||||
|
||||
## §22. The `pcast` pattern (pointer cast for struct aliasing)
|
||||
|
||||
```c
|
||||
#define pcast(type, data) (C_(type*, & (data)) [0])
|
||||
```
|
||||
|
||||
So `pcast(A3_S4_R, out_a)` casts `&out_a` to `A3_S4*` (reading a `V3_S4` as an `A3_S4`).
|
||||
|
||||
**Why:** sometimes two structs have the same memory layout (e.g., `V3_S4` and `A3_S4` are both 3 ints). The `pcast` macro aliases the memory without a copy.
|
||||
|
||||
**For Pass 3:** if a video involves struct aliasing (e.g., SIMD vector as float array), use the `pcast` pattern.
|
||||
|
||||
---
|
||||
|
||||
## §23. The `byte_pad` pattern (struct padding)
|
||||
|
||||
```c
|
||||
#define byte_pad(amount, ...) B1 glue(_PAD_, __VA_ARGS__) [amount]
|
||||
```
|
||||
|
||||
So `byte_pad(2, after_color)` expands to:
|
||||
```c
|
||||
B1 _PAD_after_color[2];
|
||||
```
|
||||
|
||||
**Why:** the user uses explicit byte padding in packed structs (graphics, file formats). The macro generates the padding field with a name derived from the previous field.
|
||||
|
||||
**For Pass 3:** if a video involves packed structs with explicit padding, use the `byte_pad` pattern.
|
||||
|
||||
---
|
||||
|
||||
## §24. The `glue` / `tmpl` pattern (token concatenation)
|
||||
|
||||
```c
|
||||
#define glue_impl(A, B) A ## B
|
||||
#define glue(A, B) glue_impl(A, B)
|
||||
#define tmpl(prefix, type) prefix ## _ ## type
|
||||
```
|
||||
|
||||
`glue(x, y)` → `xy`. `tmpl(Opt, farena)` → `Opt_farena`. The `glue_impl` indirection is for macro-expansion order (so the arguments are expanded before concatenation).
|
||||
|
||||
**Why:** the user builds type names from a prefix and a stem. `tmpl(Opt, farena)` → `Opt_farena` (a struct that wraps the optional parameters for `farena`).
|
||||
|
||||
**For Pass 3:** use `glue` and `tmpl` for type-name generation. The indirection is required for correct macro expansion.
|
||||
|
||||
---
|
||||
|
||||
## §25. The `c_` cast pattern (`C_(type, data)`)
|
||||
|
||||
```c
|
||||
#define C_(type,data) ((type)(data)) // for enforced precedence
|
||||
```
|
||||
|
||||
`C_(U4, value)` → `((U4)(value))`. The explicit precedence is for cases where the cast would otherwise be misinterpreted by the parser.
|
||||
|
||||
**For Pass 3:** use `C_(type, data)` for explicit casts. The parens ensure correct precedence.
|
||||
|
||||
---
|
||||
|
||||
## §26. Summary
|
||||
|
||||
The duffle convention is the user's primary C11 style. Key takeaways:
|
||||
|
||||
1. **Byte-width types** (`U1`/`U2`/`U4`/`S1`/`S2`/`S4`/`B1`/`B2`/`B4`) — explicit width; no `unsigned int`.
|
||||
2. **Underscore-suffixed type modifiers** (`R_`/`V_`/`LP_`/`I_`/`FI_`/`NI_`/`RO_`/`T_`) — explicit intent at the call site.
|
||||
3. **`TSet_` / `PtrSet_`** — automatic generation of base + restrict + volatile pointer typedefs.
|
||||
4. **`Struct_` / `Union_` / `Proc_` / `Opt_` / `Ret_` / `Enum_`** — declarative macros that generate the typedefs.
|
||||
5. **Hand-rolled DSL** (`enc_*` + `asm_inline` + `asm_clobber` + `clb_*`) — per-instruction-set encoder layer.
|
||||
6. **Memory ordering** (`ooo_drift_`/`ooo_anchor_`/`ooo_drain_`/`ooo_weld_`) — named intent for atomic memory orders.
|
||||
7. **Slices + arenas** (`Slice_` + `FArena` + `farena_push_*`) — typed contiguous data + linear allocation.
|
||||
8. **Control flow** (`defer` + `scope` + `defer_rewind`) — Go-style defer in C via `for` loops.
|
||||
9. **Design-doc headers** — every file has a 30-100 line mini-manual before any code.
|
||||
10. **`#pragma region` / `#pragma endregion`** — code-folding markers for large files.
|
||||
|
||||
These conventions are the user's "house style" for C11. Pass 3 should produce code that uses them.
|
||||
|
||||
---
|
||||
|
||||
*End of `cluster_0_pikuma_duffle.md`. 26 sections. ~200 LOC. PRIMARY C11 convention source.*
|
||||
Reference in New Issue
Block a user