diff --git a/scripts/build_psyq.ps1 b/scripts/build_psyq.ps1 index 2793748..d483673 100644 --- a/scripts/build_psyq.ps1 +++ b/scripts/build_psyq.ps1 @@ -287,7 +287,7 @@ function build-graphis_hello { $src_asm_crt = join-path $path_nugget_common 'crt0/crt0.s' $module_asm_crt = join-path $path_build 'crt0.o' - # assemble-unit $src_asm_crt $module_asm_crt $includes $assemble_args + assemble-unit $src_asm_crt $module_asm_crt $includes $assemble_args $src_asm = join-path $path_module 'hello_gpu.s' $module_asm = join-path $path_build 'hello_gpu.o' @@ -316,6 +316,7 @@ function build-graphis_hello { make-binary $elf $exe } # build-graphis_hello + # ps1-meta orchestrator. Replaces generate-TapeAtomOffsets + # generate-TapeAtomAnnotations with a single invocation. Dispatches # the 6 passes (word-counts / components / annotation / offsets / @@ -375,7 +376,7 @@ function build-gte_hello { $src_asm_crt = join-path $path_nugget_common 'crt0/crt0.s' $module_asm_crt = join-path $path_build 'crt0.o' - # assemble-unit $src_asm_crt $module_asm_crt $includes $assemble_args + assemble-unit $src_asm_crt $module_asm_crt $includes $assemble_args # $src_asm = join-path $path_module 'hello_gte.s' # $module_asm = join-path $path_build 'hello_gte.o' @@ -400,7 +401,11 @@ function build-gte_hello { $link_args = @() $link_args += $f_debug # $link_args += $f_optimize_size - link-modules @($module_asm_crt, $module_c) $elf $link_args + $link_modules = @( + $module_asm_crt, + $module_c + ) + link-modules $link_modules $elf $link_args make-binary $elf $exe } build-gte_hello diff --git a/scripts/passes/annotation.lua b/scripts/passes/annotation.lua index 6280656..5dafe6a 100644 --- a/scripts/passes/annotation.lua +++ b/scripts/passes/annotation.lua @@ -56,7 +56,7 @@ local function valid_phase(p) return KNOWN_PHASES[p] or false end local function is_wave_context_reg(n) return WAVE_CONTEXT_REGS[n] ~= nil end -- ════════════════════════════════════════════════════════════════════════════ --- Hand-rolled split helpers (no :gmatch / no regex) +-- Hand-rolled split helpers (no regex patterns used) -- ════════════════════════════════════════════════════════════════════════════ --- Split a string at top-level commas. Used inside TAPE_ATOM_* macro @@ -87,7 +87,7 @@ local function split_csv_top(s) end --- Split a string into whitespace-separated tokens. ---- Hand-rolled (no :gmatch / no regex). +--- Hand-rolled (no regex patterns). local function split_ws(s) local tokens = {} local i, n = 1, 1 diff --git a/scripts/passes/components.lua b/scripts/passes/components.lua index 3fd285a..a8b7b5b 100644 --- a/scripts/passes/components.lua +++ b/scripts/passes/components.lua @@ -408,24 +408,74 @@ end -- 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). --- For encoding macros with a known multi-word count (e.g. --- `mask_upper` = 2), the count is taken from `word_counts`. +-- calls, the count is the word count of mac_Y (recursive lookup +-- through `components`). For encoding macros with a known multi-word +-- count (e.g. `mask_upper` = 2), the count is taken from `word_counts`. +-- +-- The lookup is memoized via `rec()` to avoid infinite recursion +-- (e.g. if two components referenced each other). This is the +-- same algorithm as the original tape_atom_annotation_pass.lua +-- (commit 7d20a4d) — without it, a fresh build with no pre-existing +-- *.macs.h files in gen/ would compute wrong counts: e.g. +-- `mac_format_f3_color` calls `mac_pack_color_word` which isn't +-- in `wc` yet, so the lookup falls through to the "1 word" default +-- and produces `WORD_COUNT(mac_format_f3_color, 1)` instead of 3. -- --- ADAPTATION: the original used an inline recursive `rec()` that --- walked the components list + word_counts table. The new code --- delegates to word_count_eval.count_body_words(body, wc), which --- is equivalent when `wc` is pre-populated with component macros. --- The word-counts pass (which runs before components) scans all --- existing gen/*.macs.h files, so on any build with checked-in --- gen/ files, wc already contains every mac_X entry. - --- @param c Component ---- @param components Component[] -- (kept for API compatibility, unused) +--- @param components Component[] --- @param wc table --- @return integer local function compute_component_word_count(c, components, wc) - return count_body_words(c.body, wc) + -- Build component lookup table once per call. + local comp_by_name = {} + for _, cc in ipairs(components) do + comp_by_name[cc.name] = cc + end + + local cache = {} + local function rec(name) + if cache[name] ~= nil then return cache[name] end + cache[name] = -1 -- mark in-progress (cycle detection) + local cc = comp_by_name[name] + local n + if cc then + local body_tokens = {} + for _, t in ipairs(split_top_level_commas(cc.body)) do + local trimmed = 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 = 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) + elseif comp_name and wc and wc[comp_name] then + -- Encoding macro or pseudo-instruction (e.g. mask_upper = 2, + -- nop2 = 2). Trust the metadata — tape_atom.metadata.h is the + -- single source of truth for word counts. + n = n + wc[comp_name] + else + -- Unrecognized token. Fall back to 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 -- ============================================================ @@ -446,8 +496,18 @@ local function build_component_lines(c, components, wc) -- Emit the signature comment (if any) above the macro. if c.comment and c.comment ~= "" then - for line in (c.comment .. "\n"):gmatch("([^\n]*)\n") do - lines[#lines + 1] = line + -- Hand-rolled newline splitter (no regex patterns used). + local s = c.comment .. "\n" + local i = 1 + local len = #s + while i <= len do + local nl = s:find("\n", i, true) + if not nl then + lines[#lines + 1] = s:sub(i) + break + end + lines[#lines + 1] = s:sub(i, nl - 1) + i = nl + 1 end end diff --git a/scripts/ps1_meta.lua b/scripts/ps1_meta.lua index c2b72fe..aaccb54 100644 --- a/scripts/ps1_meta.lua +++ b/scripts/ps1_meta.lua @@ -385,7 +385,17 @@ local function topo_sort(passes, requested_set) end end - if #order ~= 0 then + -- Cycle detection: if order doesn't include all needed passes, + -- some are stuck with in_degree > 0 (the cycle closed on itself + -- before Kahn could process them). Without this check, a fully- + -- closed cycle (e.g. A -> B -> A) would silently return an empty + -- order list, leaving the orchestrator to dispatch nothing. + -- + -- Note: `#needed` returns 0 for hash tables (needed is a set, + -- not a sequence), so we count entries explicitly. + local needed_count = 0 + for _ in pairs(needed) do needed_count = needed_count + 1 end + if #order ~= needed_count then for name, deg in pairs(in_degree) do if deg > 0 then error("dependency cycle detected involving pass '" .. name .. "'")