diff --git a/code/duffle/gen/duffle.macs.h b/code/duffle/gen/duffle.macs.h index 8d24ce4..b6a1716 100644 --- a/code/duffle/gen/duffle.macs.h +++ b/code/duffle/gen/duffle.macs.h @@ -2,7 +2,7 @@ #pragma once #endif // Auto-generated by tape_atom_annotation_pass.lua — DO NOT EDIT -// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h +// Source: C:/projects/Pikuma/ps1/code/duffle/lottes_tape.h // Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*) // + auto word-counts (so tape_atom.metadata.h stays manual-only // for encoding macros). @@ -85,7 +85,7 @@ WORD_COUNT(mac_pack_color_word, 3) #define mac_format_f3_color(r, g, b) \ mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) -WORD_COUNT(mac_format_f3_color, 1) +WORD_COUNT(mac_format_f3_color, 3) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3. * PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). */ @@ -100,7 +100,7 @@ WORD_COUNT(mac_gte_store_f3_post_rtpt, 3) , mac_pack_color_word(O_(Poly_G4,c1), 0, r1,g1,b1) \ , mac_pack_color_word(O_(Poly_G4,c2), 0, r2,g2,b2) \ , mac_pack_color_word(O_(Poly_G4,c3), 0, r3,g3,b3) -WORD_COUNT(mac_format_g4_color, 4) +WORD_COUNT(mac_format_g4_color, 12) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the * G4 triangle portion to p0/p1/p2. diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index ce0616e..e8aaa1e 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -13,7 +13,9 @@ typedef U4 const MipsCode; typedef Slice_(MipsCode); typedef Slice_MipsCode MipsAtom; + #define MipsAtom_(sym) MipsCode tmpl(code,sym) [] align_(4) = + // Bare form: file-scope declaration with hardcoded body. // Used for components with no args (e.g., ac_load_tri_indices) or // identifier-args (hardcoded register names). @@ -21,6 +23,7 @@ typedef Slice_MipsCode MipsAtom; // expands to: // MipsCode ac_X[] align_(4) = { body }; #define MipsAtomComp_(sym) MipsCode sym [] align_(4) = + // Function form: function-body block that returns a MipsAtom slice. // Used for components with value-args (e.g., ac_format_f3_color). // FI_ MipsAtom ac_X(args) MipsAtomComp_Proc_(ac_X, { body }) diff --git a/code/gte_hello/gen/gte_hello.offsets.h b/code/gte_hello/gen/gte_hello.offsets.h index 7415184..cba333b 100644 --- a/code/gte_hello/gen/gte_hello.offsets.h +++ b/code/gte_hello/gen/gte_hello.offsets.h @@ -1,13 +1,13 @@ // Auto-generated by tape_atom_offset_gen.meta.lua — DO NOT EDIT -// Source: C:\projects\Pikuma\ps1\code\gte_hello\hello_gte_tape.c +// Source: C:/projects/Pikuma/ps1/code/gte_hello/hello_gte_tape.c #pragma once #pragma region hello_gte_tape -// --- atom: cube_g4_face (42 words) --- +// --- atom: cube_g4_face (50 words) --- -#define _atom_offset_cull_cube_g4_face_exit 26 +#define _atom_offset_cull_cube_g4_face_exit 34 #define _atom_offset_bounds_chk_cube_g4_face_exit 2 enum { diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 5799c10..ac91c7d 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -336,9 +336,9 @@ function M.split_top_level_commas(body) local token_start = 1 while i <= #body do local c = body:byte(i) - if c == 40 then local _, a = M.read_parens(body, i); i = a -- '(' - elseif c == 123 then local _, a = M.read_braces(body, i); i = a -- '{' - elseif c == 91 then local _, a = M.read_brackets(body, i); i = a -- '[' + if c == 40 then local _, a = M.read_parens(body, i); i = a + elseif c == 123 then local _, a = M.read_braces(body, i); i = a + elseif c == 91 then local _, a = M.read_brackets(body, i); i = a elseif c == 44 then -- ',' tokens[#tokens + 1] = body:sub(token_start, i - 1) i = i + 1 @@ -347,7 +347,8 @@ function M.split_top_level_commas(body) local nx = M.skip_str_or_cmt(body, i) -- Don't advance token_start on skip: a trailing `/* ... */` after -- a macro call is part of the current entry's chunk, not the - -- start of the next one. + -- start of the next one. This keeps comments merged into the + -- next entry's chunk (so they don't inflate the word count). if nx > i then i = nx else i = i + 1 end end end diff --git a/scripts/tape_atom.offset_gen.meta.lua b/scripts/tape_atom.offset_gen.meta.lua index eb18aa9..4d458a1 100644 --- a/scripts/tape_atom.offset_gen.meta.lua +++ b/scripts/tape_atom.offset_gen.meta.lua @@ -208,6 +208,7 @@ local function scan_atom_body(body, word_counts) local pos = 0 local labels = {} local branches = {} + local _dbg_count = 0 for _, tok in ipairs(split_top_level_commas(body)) do local k = 1 local tlen = #tok @@ -426,33 +427,60 @@ end -- Main -- ============================================================ +-- Scan a directory recursively for files matching a pattern. +-- No regex (per the no_regex constraint) — uses plain byte matching. +local function scan_dir(dir, suffix) + local results = {} + local p = io.popen('dir /b /s "' .. dir .. '\\' .. suffix .. '" 2>nul') + if not p then return results end + for raw_line in p:lines() do + -- dir /b outputs paths with backslashes; normalize to forward. + local path = raw_line:gsub("\\", "/") + results[#results + 1] = path + end + p:close() + return results +end + local function main(args) if #args < 2 then print("Usage: luajit gen_atom_offsets.lua [source2 ...]") os.exit(1) end - local word_counts = load_word_counts(args[1]) - for i = 2, #args do - local source_path = args[i] - -- Also load the auto-generated .macs.h for this source's - -- directory (if it exists). The metaprogram emits - -- WORD_COUNT(mac_X, N) entries there for the MipsAtomComp_ - -- declarations in the source. Merging ensures the offset - -- computation has the right word counts (no "unknown macro" - -- warnings for the auto-generated components). - local source_dir = dirname(source_path) - -- dirname() keeps the trailing separator; basename_no_ext() - -- on a path with a trailing separator returns "". Strip it. - if #source_dir > 0 and (source_dir:sub(-1) == "/" or source_dir:sub(-1) == "\\") then - source_dir = source_dir:sub(1, -2) - end - local macs_path = source_dir .. "/gen/" .. basename_no_ext(source_dir) .. ".macs.h" - local ok, mc = pcall(load_word_counts, macs_path) - if ok then + -- Find the project root (parent of the metadata file's dir). + local meta_dir = dirname(args[1]) + if #meta_dir > 0 and (meta_dir:sub(-1) == "/" or meta_dir:sub(-1) == "\\") then + meta_dir = meta_dir:sub(1, -2) + end + local project_root = dirname(meta_dir) + -- Scan the project for all *.macs.h files and load their WORD_COUNT + -- entries as the source of truth. The metaprogram's recursive + -- counting (which accounts for macro-in-macro expansion) gives + -- the correct values; the metadata file's manually-maintained + -- entries are NOT loaded here (they may be wrong for recursive cases). + local word_counts = {} + local macs_files = scan_dir(project_root, "*.macs.h") + for _, macs_path in ipairs(macs_files) do + -- Convert forward slashes to backslashes for Windows file open. + local win_path = macs_path:gsub("/", "\\") + local ok, mc = pcall(load_word_counts, win_path) + if ok and type(mc) == "table" then for name, count in pairs(mc) do word_counts[name] = count end end + end + -- Also load the metadata's encoding-macro entries (which are NOT + -- in the macs.h, since the macs.h is component-only). These are + -- the regular 1-word-per-macro entries like load_word, add_ui, etc. + local meta_counts = load_word_counts(args[1]) + for name, count in pairs(meta_counts) do + if not word_counts[name] then + word_counts[name] = count + end + end + for i = 2, #args do + local source_path = args[i] process_source(source_path, word_counts) end end diff --git a/scripts/tape_atom_annotation_pass.lua b/scripts/tape_atom_annotation_pass.lua index f411b52..b63e913 100644 --- a/scripts/tape_atom_annotation_pass.lua +++ b/scripts/tape_atom_annotation_pass.lua @@ -892,6 +892,62 @@ local function convert_line_comments_to_block(s) return result end +-- Compute the word count of a component body, accounting for +-- macro expansion. Each comma-separated entry in the body is a +-- "slot" that contributes its own word count. For most entries +-- (regular MIPS instructions) the count is 1. For `mac_Y(...)` +-- calls, the count is the word count of mac_Y (recursive lookup). +-- +-- The component list passed in is the full set of components in the +-- source, so we can resolve any `mac_X` call to its definition. +local function compute_component_word_count(c, components) + -- Build a lookup table: mac_X_name -> component + local comp_by_name = {} + for _, cc in ipairs(components) do + comp_by_name[cc.name] = cc + end + -- Cache to avoid infinite recursion (if components cycle). + local cache = {} + local function rec(name) + if cache[name] then return cache[name] end + cache[name] = 0 -- mark as in-progress + local cc = comp_by_name[name] + local n + if cc then + local body_tokens = {} + for _, t in ipairs(duffle.split_top_level_commas(cc.body)) do + local trimmed = duffle.trim(t) + if trimmed ~= "" then body_tokens[#body_tokens + 1] = trimmed end + end + n = 0 + for _, t in ipairs(body_tokens) do + -- Read the first identifier from the token. + local ident, after = duffle.read_ident(t, 1) + -- Components are stored without the `mac_` prefix + -- (e.g. "format_f3_color"). The token has `mac_format_f3_color(...)`, + -- so strip the `mac_` prefix to look up the component. + local comp_name = ident + if comp_name and comp_name:sub(1, 4) == "mac_" then + comp_name = comp_name:sub(5) + end + if comp_name and comp_by_name[comp_name] then + -- It's a `mac_X(...)` call. Recurse. + n = n + rec(comp_name) + else + -- Regular instruction (or some other token): 1 word. + n = n + 1 + end + end + else + -- Not a known component: assume 1 word (regular instruction). + n = 1 + end + cache[name] = n + return n + end + return rec(c.name) +end + local function emit_component_macros_h(source_path, components) if #components == 0 then return end @@ -956,19 +1012,36 @@ local function emit_component_macros_h(source_path, components) sig = "..." end + -- Compute the word count of the component body, accounting for + -- macro expansion. Each comma-separated entry in the body is one + -- "instruction slot", but a `mac_Y(...)` call expands to Y's + -- word count. The offset_gen and the metadata's WORD_COUNT table + -- both use this resolved count. + -- + -- We compute it once per component (not per token) and emit + -- the value in the WORD_COUNT entry. The lookup table `counts_by_name` + -- is built from the components list (which find_component_atoms + -- already populated) and falls back to 1 for non-mac tokens. + local counts_by_name = {} + for _, cc in ipairs(components) do + local cc_count = compute_component_word_count(cc, components) + counts_by_name["mac_" .. cc.name] = cc_count + end + local n = compute_component_word_count(c, components) + if n > 0 then -- Convert `//` line comments to `/* */` block comments in each -- token. C macros use `\` line-continuations; if a `//` comment -- appears before a `\`, the rest of the line (including the -- continuation) is consumed by the `//`, breaking the macro. -- Converting to `/* */` preserves the macro structure. - for j = 1, n do + for j = 1, #tokens do tokens[j] = convert_line_comments_to_block(tokens[j]) end -- Emit the mac_() macro lines[#lines + 1] = "#define mac_" .. c.name .. "(" .. sig .. ") \\" lines[#lines + 1] = "\t" .. tokens[1] .. " \\" - for j = 2, n do + for j = 2, #tokens do lines[#lines + 1] = ",\t" .. tokens[j] .. " \\" end -- Strip the trailing line-continuation on the last body line.