finally starting to approach decent performance.

This commit is contained in:
ed
2026-07-11 17:30:16 -04:00
parent e662d175ab
commit d9406553b3
5 changed files with 61 additions and 145 deletions
+9 -6
View File
@@ -350,6 +350,8 @@ end
-- Project pre-scanned MipsAtomComp_ / MipsAtomComp_Proc_ entries into Component shape.
-- Does per-source backward lookups for args (preceding function decl) and comment (preceding comment block).
-- Carries `body_tokens` forward from scan-source so word_count_rec reads from the precomputed table
-- instead of calling duffle.tokenize_body again.
-- @param source string -- the full source text (needed for backward lookups)
-- @param scan table -- SourceScan from duffle.scan_source
-- @return Component[]
@@ -360,11 +362,12 @@ local function project_components(source, scan)
local args = find_function_args_for(source, a.raw_name, a.ident_pos)
local comment = preceding_comment_block(source, a.ident_pos)
out[#out + 1] = {
line = a.line,
name = a.name,
body = a.body,
args = args,
comment = comment,
line = a.line,
name = a.name,
body = a.body,
body_tokens = a.body_tokens,
args = args,
comment = comment,
}
end
end
@@ -445,7 +448,7 @@ local function word_count_rec(name, comp_by_name, wc, cache)
local n
if cc then
n = 0
local tokens = cc.body_tokens or duffle.tokenize_body(cc.body)
local tokens = cc.body_tokens
for _, t in ipairs(tokens) do
local trimmed = t.tok
if trimmed ~= "" then
+13 -46
View File
@@ -164,44 +164,6 @@ local function scan_for_atom_markers(token, at_pos, labels, branches)
end
end
--- Find the end position (just past the closing ')') of the first atom_label/atom_offset call in `tok`. Returns 0 if no such call.
--- @param tok string
--- @return integer -- 0 if no marker call found; otherwise end-1 (just past ')')
local function find_marker_call_end(tok)
local pos = 1
local tok_len = #tok
while pos <= tok_len do
pos = duffle.skip_ws_and_cmt(tok, pos)
if pos > tok_len then break end
local ch = tok:sub(pos, pos)
if duffle.is_space(ch) then
pos = pos + 1
elseif ch == "/" then
-- comment — skip past it (delegated to duffle.skip_str_or_cmt)
local nx = duffle.skip_str_or_cmt(tok, pos)
pos = (nx > pos) and nx or (pos + 1)
else
local ident, after_ident = duffle.read_ident(tok, pos)
-- scan: <ident>
if ident == LABEL_MARKER or ident == OFFSET_MARKER then
-- scan: atom_label(<name>) OR atom_offset(<tag>, <target>)
local open_paren = duffle.skip_ws_and_cmt(tok, after_ident)
if tok:sub(open_paren, open_paren) == "(" then
local _, end_paren = duffle.read_parens(tok, open_paren)
return end_paren - 1
end
return 0
end
pos = after_ident or (pos + 1)
end
end
return 0
end
-- ════════════════════════════════════════════════════════════════════════════
-- Per-atom body scan (for atom_label / atom_offset markers)
-- ════════════════════════════════════════════════════════════════════════════
-- (internal) Count words emitted by the rest of `tok` after a marker call
-- (the marker call itself emits 0 words, but the source pattern may bundle the marker with the next instruction on the same line,
-- separated by no top-level comma).
@@ -210,9 +172,12 @@ end
-- @param word_counts table
-- @return integer
local function count_marker_rest(tok, word_counts)
local marker_end = find_marker_call_end(tok)
if marker_end <= 0 or marker_end >= #tok then return 0 end
local rest = duffle.trim(tok:sub(marker_end + 1))
-- duffle.find_marker_call_end returns the position PAST the closing `)` of the marker call
-- (or nil if `tok` isn't a marker call). Canonical impl in duffle.lua is faster than the
-- file-local copy that used to live here (byte-indexed, no `tok:sub` per char).
local marker_end = duffle.find_marker_call_end(tok)
if not marker_end or marker_end >= #tok then return 0 end
local rest = duffle.trim(tok:sub(marker_end))
if rest == "" then return 0 end
return count_token_words(rest, word_counts)
end
@@ -348,17 +313,19 @@ end
local M = {}
-- Project the pre-scanned SourceScan entries into the {name, body} shape this pass needs.
-- Project the pre-scanned SourceScan entries into the {name, body, body_tokens} shape this pass needs.
-- MipsAtom_ entries have kind="atom"; MipsCode code_<name> entries have kind="raw_atom".
-- `body_tokens` is set by scan-source on every `scan.atoms[i]` / `scan.raw_atoms[i]`; we carry it forward
-- so `scan_atom_body` reads from the precomputed table directly (no per-atom tokenize_body fallback).
-- @param scan table -- SourceScan from duffle.scan_source
-- @return table[] -- list of {name=, body=}
-- @return table[] -- list of {name=, body=, body_tokens=}
local function project_atoms(scan)
local out = {}
for _, a in ipairs(scan.atoms) do
out[#out + 1] = { name = a.raw_name, body = a.body }
out[#out + 1] = { name = a.raw_name, body = a.body, body_tokens = a.body_tokens }
end
for _, a in ipairs(scan.raw_atoms) do
out[#out + 1] = { name = a.name, body = a.body }
out[#out + 1] = { name = a.name, body = a.body, body_tokens = a.body_tokens }
end
return out
end
@@ -374,7 +341,7 @@ local function process_source(ctx, src)
local atoms_data = {}
for _, atom in ipairs(atoms) do
local labels, branches, total = scan_atom_body(atom.body_tokens or duffle.tokenize_body(atom.body), ctx.shared.word_counts)
local labels, branches, total = scan_atom_body(atom.body_tokens, ctx.shared.word_counts)
atoms_data[#atoms_data + 1] = {
name = atom.name,
total_words = total,
+1 -1
View File
@@ -784,7 +784,7 @@ local function validate(ctx, src)
local findings = {}
for _, a in ipairs(atoms) do
a.paths = a.paths or {}
a.paths.tokens = duffle.tokenize_body(a.body)
a.paths.tokens = a.body_tokens
a.paths.line_in_body = duffle.build_body_line_index(a.body)
a.paths.tok_class = classify_tokens(a.paths.tokens)
-83
View File
@@ -4,7 +4,6 @@
--- 1. **Public utilities** (used by `passes/components.lua`, `passes/offsets.lua`, `passes/annotation.lua`):
--- - `M.count_token_words(token, wc)` — words emitted by one token
--- - `M.scan_dir(dir, suffix)` — glob walk for *.macs.h
--- - `M.count_body_words(body, wc)` — words emitted by an atom body
--- 2. **Pass entry** `M.run(ctx)` — loads metadata.h + *.macs.h into `ctx.shared.word_counts` for downstream passes.
--- 3. **Internal helpers** for the body scanner.
---
@@ -178,88 +177,6 @@ end
--- Invalidate the scan cache (call after creating new .macs.h files in the same Lua process — usually not needed).
function M._invalidate_scan_cache() package.loaded[SCAN_CACHE_KEY] = nil end
-- ┌────────────────────────────────────────────────────────────────────┐
-- │ Shared utility: count_body_words │
-- └────────────────────────────────────────────────────────────────────┘
--- Count words emitted by an entire atom body (a brace-delimited block).
--- Splits by top-level commas; for each token, delegates to count_token_words.
--- Handles `atom_label(name)` / `atom_offset(tag, name)` markers
--- (record at current pos, do NOT advance pos; if the marker call bundles an instruction after it, count that instruction too).
---
--- @param body string -- brace-delimited atom body (without braces)
--- @param wc WordCounts -- the shared word-count table
--- @return integer -- total words
function M.count_body_words(body, wc)
local total = 0
for _, tok in ipairs(duffle.split_top_level_commas(body)) do
local pos = 1
local tok_len = #tok
while pos <= tok_len and duffle.is_space(tok:sub(pos, pos)) do
pos = pos + 1
end
local leading_ident = duffle.read_ident(tok, pos)
local is_marker = leading_ident == "atom_label" or leading_ident == "atom_offset"
if is_marker then
-- Marker call: record at current pos, do NOT advance pos.
-- But the source pattern may bundle the marker with the next instruction on a new line (no top-level comma between them).
-- In that case, the rest of `tok` after the marker call is a real instruction that must still be counted.
local marker_end = M.find_marker_call_end(tok)
if marker_end > 0 and marker_end < #tok then
local rest = duffle.trim(tok:sub(marker_end + 1))
if rest ~= "" then
total = total + M.count_token_words(rest, wc)
end
end
else
total = total + M.count_token_words(tok, wc)
end
end
return total
end
--- Find the end position (just past the closing ')') of the first atom_label/atom_offset call in `tok`. Returns 0 if no such call.
--- Internal helper for count_body_words.
---
--- @param tok string
--- @return integer -- 0 if no marker call found
function M.find_marker_call_end(tok)
local pos = 1
local tok_len = #tok
while pos <= tok_len do
pos = duffle.skip_ws_and_cmt(tok, pos)
if pos > tok_len then break end
local ch = tok:sub(pos, pos)
if duffle.is_space(ch) then
pos = pos + 1
elseif ch == "/" then
-- comment — skip past it (delegated to duffle.skip_str_or_cmt)
local nx = duffle.skip_str_or_cmt(tok, pos)
pos = (nx > pos) and nx or (pos + 1)
else
local ident, after_ident = duffle.read_ident(tok, pos)
local marker_end = find_marker_end(tok, ident, after_ident)
if marker_end > 0 then return marker_end end
pos = after_ident or (pos + 1)
end
end
return 0
end
-- (internal) If `ident` is `atom_label`/`atom_offset` followed by `(...)`, return the position just past the closing ')'.
-- Otherwise 0.
-- @param tok string
-- @param ident string|nil
-- @param after_ident integer
-- @return integer
local function find_marker_end(tok, ident, after_ident)
if ident ~= "atom_label" and ident ~= "atom_offset" then return 0 end
local open_paren = duffle.skip_ws_and_cmt(tok, after_ident)
if tok:sub(open_paren, open_paren) ~= "(" then return 0 end
local _, end_paren = duffle.read_parens(tok, open_paren)
return end_paren - 1
end
-- ┌────────────────────────────────────────────────────────────────────┐
-- │ Pass entry: M.run(ctx) — "word-counts" pass │
-- └────────────────────────────────────────────────────────────────────┘