From 6eee0249ff004d59f50d2242fc7b11096cf7d3f8 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 7 Jul 2026 01:22:09 -0400 Subject: [PATCH] learning... --- code/duffle/atom_dsl.h | 3 +- code/duffle/lottes_tape.h | 18 ---- code/gte_hello/hello_gte.c | 6 +- code/gte_hello/hello_gte_tape.c | 26 ++++- scripts/tape_atom_annotation_pass.lua | 148 +++++++++++++++++--------- 5 files changed, 127 insertions(+), 74 deletions(-) diff --git a/code/duffle/atom_dsl.h b/code/duffle/atom_dsl.h index 4b06954..282ad0e 100644 --- a/code/duffle/atom_dsl.h +++ b/code/duffle/atom_dsl.h @@ -206,7 +206,8 @@ * writes at this granularity. * * ============================================================================*/ -#define tape_regs(...) (__VA_ARGS__) +#define atom_reads(...) (__VA_ARGS__) +#define atom_writes(...) (__VA_ARGS__) /* ============================================================================ * ATOM ANNOTATION MACROS diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index d9e805e..cb3701c 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -189,22 +189,6 @@ internal MipsAtom_(mips_flush_icache) { , mac_yield() }; -typedef Struct_(Binds_SyncPrimCursor) { - U4 PrimtiveArena_Used; - U4 PrimtiveBase; -}; -internal MipsAtom_(sync_prim_cursor) { - /* Pop the C-struct address and base address from the tape */ - // Note(Ed): Argument shuffle.... - load_word(R_AT, R_TapePtr, O_(Binds_SyncPrimCursor,PrimtiveArena_Used)), - load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimCursor,PrimtiveBase)), - add_ui_1( R_TapePtr, S_(Binds_SyncPrimCursor)), - /* Calculate byte offset and store directly back to RAM */ - sub_u(R_T0, R_PrimCursor, R_T0), // R_T0 = PrimitiveArea_Used(R_AT) - R_PrimCur - store_word(R_T0, R_AT, 0), // PrimitiveBase(R_AT)[0] = R_T0 - mac_yield() -}; - internal MipsAtom_(set_gte_world) { /* Pop matrix address from tape into R_T3 ($11) */ load_word(R_T3, R_TapePtr, 0), @@ -221,7 +205,6 @@ internal MipsAtom_(set_gte_world) { mac_yield() }; - // TODO(Ed): I'm not sure yet if the bindings are redundant with the floortri atom yet. typedef Struct_(Binds_CubeTri) { @@ -278,7 +261,6 @@ internal MipsAtom_(rbind_cube_tri) { * Inner branch (OTZ bounds): branch_equal(R_AT, R_0, 13) * → Skip 13 instructions from BD slot, land at add_ui(R_FaceCur,...) * ============================================================================ */ - atom_resource(cube_tri, "model_ship_cube") atom_region (cube_tri, REGION_PRIM_ARENA) atom_group (cube_tri, GROUP_RENDER_PRIMS) atom_cadence (cube_tri, CADENCE_FRAME) diff --git a/code/gte_hello/hello_gte.c b/code/gte_hello/hello_gte.c index b652945..7213022 100644 --- a/code/gte_hello/hello_gte.c +++ b/code/gte_hello/hello_gte.c @@ -259,7 +259,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf) tb_emit(& tb, code_cube_tri); } - tb_emit(& tb, code_sync_prim_cursor); + tb_emit(& tb, code_sync_primitive_arena); tb_data(& tb, u4_(& pa->used)); tb_data(& tb, prim_base); } @@ -351,8 +351,8 @@ void update(PrimitiveArena* pa, U4* ordering_buf) for (U4 i = 0; i < Floor_num_faces; i++) { tb_emit(& tb, code_floor_tri); } - - tb_emit(& tb, code_sync_prim_cursor); + // After code_floor_tri iterations complete, the primitive arena's used counter is wrong. + tb_emit(& tb, code_sync_primitive_arena); tb_data(& tb, u4_(& pa->used)); tb_data(& tb, prim_base); } diff --git a/code/gte_hello/hello_gte_tape.c b/code/gte_hello/hello_gte_tape.c index adf67f7..d2229b6 100644 --- a/code/gte_hello/hello_gte_tape.c +++ b/code/gte_hello/hello_gte_tape.c @@ -27,8 +27,8 @@ atom_group( floor_tri, GROUP_RENDER_FLOOR) atom_cadence(floor_tri, CADENCE_FRAME) atom_annot( floor_tri, phase_work, - tape_regs(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase), - tape_regs(R_PrimCursor, R_FaceCursor)) + atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase), + atom_writes(R_PrimCursor, R_FaceCursor)) internal MipsAtom_(floor_tri) { // T0-T2 allocated @@ -69,11 +69,29 @@ MipsAtom_(floor_tri) { /* 8. Insert into Ordering Table Linked List */ mac_insert_ot_tag(R_T1, 0x0400), - add_ui(R_PrimCursor, R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */ + add_ui_1(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */ + // Note(Ed): No bounds checking, should be checked before atom runs. /* 9. Advance Input Cursor & Yield (Both branch targets land here) */ atom_label(floor_tri_exit) - add_ui(R_FaceCursor, R_FaceCursor, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */ + add_ui_1(R_FaceCursor, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */ + mac_yield() +}; + +typedef Struct_(Binds_SyncPrimitiveArena) { U4 PrimtiveArena_Used; U4 PrimtiveBase; }; + atom_region( sync_primitive_arena, REGION_PRIM_ARENA) + atom_group( sync_primitive_arena, GROUP_RENDER_FLOOR) + atom_cadence(sync_primitive_arena, CADENCE_FRAME) + atom_annot( sync_primitive_arena, phase_work, + atom_reads( R_TapePtr, R_PrimCursor), + atom_writes(R_TapePtr)) +internal MipsAtom_(sync_primitive_arena) { + load_word(R_AT, R_TapePtr, O_(Binds_SyncPrimitiveArena,PrimtiveArena_Used)), + load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimitiveArena,PrimtiveBase)), + add_ui_1( R_TapePtr, S_(Binds_SyncPrimitiveArena)), + /* Calculate byte offset and store directly back to RAM */ + sub_u( R_T0, R_PrimCursor, R_T0), // R_T0 = R_PrimCursor - prim-base + store_word(R_T0, R_AT, 0), // R_AT[0] = R_T0 mac_yield() }; diff --git a/scripts/tape_atom_annotation_pass.lua b/scripts/tape_atom_annotation_pass.lua index fff8774..bde994d 100644 --- a/scripts/tape_atom_annotation_pass.lua +++ b/scripts/tape_atom_annotation_pass.lua @@ -265,7 +265,14 @@ local MACRO_EXPANSION = { ["CADENCE_ONDEMAND"] = "ondemand", } -local function valid_phase(p) return KNOWN_PHASES[p] end +-- Phases + regions + cadences are defined further below (after TAPE_ATOM_MACROS). +-- We expose valid_phase via upvalue once KNOWN_PHASES is defined, but for now +-- it's a closure-resolved at call time. + +-- Table refs initialised later; valid_phase is hooked up below KNOWN_PHASES. +local KNOWN_PHASES, KNOWN_REGIONS, KNOWN_CADENCES -- forward declarations + +local function valid_phase(p) return KNOWN_PHASES and KNOWN_PHASES[p] or false end local function is_wave_context_reg(name) return WAVE_CONTEXT_REGS[name] ~= nil end -- ============================================================ @@ -315,9 +322,10 @@ end -- Extract identifier args from a parenthesized group. Returns a list -- of {kind, value} pairs where kind is one of: --- "ident" -- a bare identifier (e.g. TAPE_PHASE_WORK) --- "regs" -- a TAPE_REGS(...) call whose args are extracted as a list --- "other" -- something we can't classify (preserved as text) +-- "ident" -- a bare identifier (e.g. phase_work) +-- "atom_reads" -- an atom_reads(...) call: value is the register list +-- "atom_writes"-- an atom_writes(...) call: value is the register list +-- "other" -- something we can't classify (preserved as text) local function parse_atom_annot_args(inner) -- Split at top-level commas, respecting nested parens. local args = {} @@ -325,20 +333,37 @@ local function parse_atom_annot_args(inner) for _, tok in ipairs(tokens) do local s = trim(tok) if s ~= "" then - -- TAPE_REGS(...) → extract inner identifiers - if s:sub(1, 10) == "TAPE_REGS(" and s:sub(-1) == ")" then - local regs_inner = s:sub(11, -2) + -- Detect register-list calls: atom_reads(...) / atom_writes(...) / tape_regs(...) + local regs_kind = nil + local regs_inner = nil + if s:sub(-1) == ")" then + if s:sub(1, 11) == "atom_reads(" then + regs_kind = "atom_reads" + regs_inner = s:sub(12, -2) + elseif s:sub(1, 12) == "atom_writes(" then + regs_kind = "atom_writes" + regs_inner = s:sub(13, -2) + end + end + + if regs_kind then local regs = {} for r in regs_inner:gmatch("[^,]+") do local trimmed = trim(r) if trimmed ~= "" then table.insert(regs, trimmed) end end - table.insert(args, {kind = "regs", value = regs}) + -- Resolve any phase_* / R_* alias macros + for i, r in ipairs(regs) do + if MACRO_EXPANSION[r] then regs[i] = MACRO_EXPANSION[r] end + end + table.insert(args, {kind = regs_kind, value = regs}) else - -- Bare identifier (e.g. TAPE_PHASE_WORK) + -- Bare identifier (e.g. phase_work) local id, _ = read_ident(s, 1) if id and trim(s) == id then - table.insert(args, {kind = "ident", value = id}) + local v = id + if MACRO_EXPANSION[v] then v = MACRO_EXPANSION[v] end + table.insert(args, {kind = "ident", value = v}) else table.insert(args, {kind = "other", value = s}) end @@ -358,20 +383,20 @@ local TAPE_ATOM_MACROS = { } -- Phase token names (must match macros in tape_atom_dsl.h) -local KNOWN_PHASES = { +KNOWN_PHASES = { ["init"] = true, ["bind"] = true, ["setup"] = true, ["work"] = true, ["commit"] = true, ["terminate"] = true, } -- Region token names (must match REGION_* macros in tape_atom_dsl.h) -local KNOWN_REGIONS = { +KNOWN_REGIONS = { ["prim_arena"] = true, ["face_arena"] = true, ["vertex_arena"] = true, ["ot_arena"] = true, ["heap_3d_models"] = true, ["cdrom_stream"] = true, ["vram_heap"] = true, } -- Cadence token names (must match CADENCE_* macros in tape_atom_dsl.h) -local KNOWN_CADENCES = { +KNOWN_CADENCES = { ["frame"] = true, ["once"] = true, ["ondemand"] = true, } @@ -403,60 +428,81 @@ local function find_atom_annotations(source) if #args < 1 then table.insert(annots, { - line = line_of(source, i), + line = line_of(source, i), macro = ident, kind = macro_def.kind, - error = "missing atom name (first arg)" + error = "missing atom name (first arg)", }) else local name = args[1].value - -- For TAPE_ATOM_BIND: arg layout is (name, Binds_Struct, writes) - -- For others: (name, phase, reads, writes) - -- INIT / TERMINATE: (name) + -- atom_bind: (name, Binds_Struct, writes) + -- atom_annot: (name, phase, reads, writes) + -- atom_setup / atom_commit: (name, reads) + -- atom_init / atom_terminate: (name) local entry = { - line = line_of(source, i), - macro = ident, - name = name, - kind = macro_def.kind, - binds = nil, - phase = nil, - reads = {}, - writes = {}, + line = line_of(source, i), + macro = ident, + name = name, + kind = macro_def.kind, + binds = nil, + phase = nil, + reads = {}, + writes = {}, + errors = {}, } + + -- Is this arg a register-list call (any of the recognized forms)? + local function is_regs(a) + return a and (a.kind == "atom_reads" or a.kind == "atom_writes" or a.kind == "regs") + end + if macro_def.binds then - -- (name, Binds_Struct, writes) + -- atom_bind(name, Binds_Struct, writes) if #args >= 2 and args[2].kind == "ident" then entry.binds = args[2].value end - if #args >= 3 and args[3].kind == "regs" then + if #args >= 3 and is_regs(args[3]) then + -- A bind writes the wave-context, so atom_writes(...) is the + -- canonical form, but legacy regs(...) is also accepted. entry.writes = args[3].value end - elseif ident == "TAPE_ATOM_INIT" or ident == "TAPE_ATOM_TERMINATE" then - -- (name) — no phase, no reads/writes to extract - elseif ident == "TAPE_ATOM_SETUP" then - -- (name, reads) - if #args >= 2 and args[2].kind == "regs" then + elseif ident == "atom_init" or ident == "atom_terminate" then + -- (name) only, no reads/writes to extract + elseif ident == "atom_setup" then + -- atom_setup(name, reads) + if #args >= 2 and is_regs(args[2]) then entry.reads = args[2].value end - elseif ident == "TAPE_ATOM_COMMIT" then - -- (name, reads) - if #args >= 2 and args[2].kind == "regs" then + elseif ident == "atom_commit" then + -- atom_commit(name, reads) + if #args >= 2 and is_regs(args[2]) then entry.reads = args[2].value end - elseif ident == "TAPE_ATOM_ANNOT" then - -- (name, phase, reads, writes) + elseif ident == "atom_annot" then + -- atom_annot(name, phase, reads, writes) if #args >= 2 and args[2].kind == "ident" then - -- Expand TAPE_PHASE_* macro references - local phase_id = args[2].value - entry.phase = MACRO_EXPANSION[phase_id] or phase_id + entry.phase = MACRO_EXPANSION[args[2].value] or args[2].value end - if #args >= 3 and args[3].kind == "regs" then + -- reads slot: atom_reads(...) is the canonical form; + -- atom_writes(...) in the reads slot is a likely bug. + if #args >= 3 and is_regs(args[3]) then + if args[3].kind == "atom_writes" then + table.insert(entry.errors, + "reads slot has atom_writes — swap order?") + end entry.reads = args[3].value end - if #args >= 4 and args[4].kind == "regs" then + -- writes slot: atom_writes(...) is canonical; + -- atom_reads(...) here is a likely bug. + if #args >= 4 and is_regs(args[4]) then + if args[4].kind == "atom_reads" then + table.insert(entry.errors, + "writes slot has atom_reads — swap order?") + end entry.writes = args[4].value end end + table.insert(annots, entry) end i = after_paren @@ -865,11 +911,11 @@ end local function validate(source_path, word_counts) local source = read_file(source_path) - local annots = find_atom_annotations(source) - local macros = find_macro_word_annotations(source) - local pragmas = find_atom_pragmas(source) - local binds = find_binds_structs(source) - local atoms = find_atom_names(source) + local annots = find_atom_annotations(source) + local macros = find_macro_word_annotations(source) + local pragmas = find_atom_pragmas(source) + local binds = find_binds_structs(source) + local atoms = find_atom_names(source) -- Index for O(1) lookup local atom_index = {} @@ -892,6 +938,12 @@ local function validate(source_path, word_counts) msg = string.format("annotation for '%s' has no matching MipsAtom_(%s) { ... }", a.name, a.name) }) end + -- Per-entry parser errors (e.g. reads/writes slot mix-ups) + if a.errors then + for _, msg in ipairs(a.errors) do + table.insert(errors, {line = a.line, msg = string.format("'%s': %s", a.name, msg)}) + end + end end -- 2. Every atom must have exactly one annotation (no orphans, no duplicates).