mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
Improving program model, (better structs, better path awareness ties to those structs).
This commit is contained in:
@@ -21,16 +21,8 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
-- Type declarations
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- SourceFile, PassCtx, PassResult, PassShared, Corpus: see ps1_meta.lua
|
||||
-- SourceScan, AtomEntry, BindsEntry, RegTypeDefault, AtomViewEntry: see scan_source.lua
|
||||
|
||||
--- @class AtomAnnotation
|
||||
--- @field atom_name string -- Atom name (scan.atom_infos row)
|
||||
--- @field info_line integer -- Source line of the atom_info call
|
||||
--- @field binds string|nil -- Binds_X name if any
|
||||
--- @field reads string[] -- R_* names (read targets)
|
||||
--- @field writes string[] -- R_* names (write targets)
|
||||
--- @field errors string[]|nil -- Parse-time errors from scan_source (atom_info body malformed)
|
||||
-- SourceFile, PassCtx, PassResult, PassShared, Corpus, Finding: see ps1_meta.lua
|
||||
-- SourceScan, AtomEntry, AtomInfoEntry, BindsEntry, RegTypeDefault, AtomViewEntry: see scan_source.lua
|
||||
|
||||
--- @class RegTypeOccurrence
|
||||
--- @field reg string
|
||||
@@ -38,44 +30,30 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
--- @field source_line integer
|
||||
|
||||
--- @class Findings
|
||||
--- @field errors PassFinding[]
|
||||
--- @field warnings PassFinding[]
|
||||
--- @field info PassFinding[]
|
||||
--- @field errors Finding[]
|
||||
--- @field warnings Finding[]
|
||||
--- @field info Finding[]
|
||||
|
||||
--- @class PipeCtx
|
||||
--- @field atom_index table<string, AtomEntry> -- raw_name or name -> scan.atoms row (kind atom/atom_proc)
|
||||
--- @field binds_index table<string, BindsEntry>
|
||||
--- @field annot_counts table<string, integer> -- bag: atom name -> annotation count
|
||||
--- @field types table<string, RegTypeDefault>
|
||||
--- @field atom_views table<string, AtomViewEntry>
|
||||
--- @field seen_defaults table<string, integer> -- bag: register ident -> occurrence count
|
||||
--- @field seen_field table<string, integer> -- bag: leftover field-count slot
|
||||
--- @field _scan SourceScan
|
||||
--- @field word_counts WordCounts|nil
|
||||
--- @field register_alias_registry table<string, AliasEntry>|nil
|
||||
--- @field type_name_registry table<string, TypeNameEntry>|nil
|
||||
--- @field type_occurrences RegTypeOccurrence[]|nil
|
||||
--- @field atom_infos_list AtomInfoEntry[]|nil
|
||||
--- @field binds_list BindsEntry[]|nil
|
||||
-- PassScratch: see ps1_meta.lua
|
||||
|
||||
--- @class AnnotatedResult
|
||||
--- @field atoms AtomEntry[]
|
||||
--- @field annots AtomAnnotation[]
|
||||
--- @field annots AtomInfoEntry[]
|
||||
--- @field macros MacroEntry[]
|
||||
--- @field binds BindsEntry[]
|
||||
--- @field errors PassFinding[]
|
||||
--- @field warnings PassFinding[]
|
||||
--- @field info PassFinding[]
|
||||
--- @field errors Finding[]
|
||||
--- @field warnings Finding[]
|
||||
--- @field info Finding[]
|
||||
--- @field source string|nil
|
||||
|
||||
--- @class CheckRule
|
||||
--- @field per_annot (fun(item: AtomAnnotation, pipe_ctx: PipeCtx, findings: Findings): nil)|nil
|
||||
--- @field per_annot (fun(item: AtomInfoEntry, pipe_ctx: PassScratch, findings: Findings): nil)|nil
|
||||
|
||||
--- @class SourceScan
|
||||
--- @field type_occurrences RegTypeOccurrence[]|nil
|
||||
|
||||
--- @class AnnotationPass
|
||||
--- @field validate fun(ctx: PassCtx, src: SourceFile, corpus_pipe_ctx: PipeCtx|nil): AnnotatedResult
|
||||
--- @field validate fun(ctx: PassCtx, src: SourceFile, corpus_pipe_ctx: PassScratch|nil): AnnotatedResult
|
||||
--- @field run fun(ctx: PassCtx): PassResult
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -85,8 +63,8 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
--- `macro_word_drift` writes errors[] for missing or mismatched metadata and info[] for a match.
|
||||
|
||||
--- Check: Every annotated atom must have a matching MipsAtom_(name) declaration.
|
||||
--- @param info AtomAnnotation
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param info AtomInfoEntry
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_atom_decl_exists(info, pipe_ctx, findings)
|
||||
@@ -100,8 +78,8 @@ end
|
||||
|
||||
--- Check: Every atom may have AT MOST ONE annotation.
|
||||
--- Post-loop: Needs full-corpus `annot_counts` from pipe_ctx.
|
||||
--- @param _item AtomAnnotation|nil
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param _item AtomInfoEntry|nil
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_unique_annotation(_item, pipe_ctx, findings)
|
||||
@@ -117,8 +95,8 @@ end
|
||||
|
||||
--- Check: BIND atoms must reference a real Binds_* struct.
|
||||
--- I keep this as a warning so the annotation pass can report the common test-fixture case; `check_abi_handoff` in static analysis supplies the build-stopping error.
|
||||
--- @param info AtomAnnotation
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param info AtomInfoEntry
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_binds_struct_exists(info, pipe_ctx, findings)
|
||||
@@ -135,7 +113,7 @@ end
|
||||
--- Check: TAPE_WORDS(mac_X, N) ↔ WORD_COUNT(mac_X, N) drift.
|
||||
--- Three outcomes: missing (error), mismatch (error), match (info).
|
||||
--- @param m MacroEntry
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_macro_word_drift(m, pipe_ctx, findings)
|
||||
@@ -164,7 +142,7 @@ end
|
||||
--- Check: atom_dbg_reg_default(R_X, <type>) targets an alias in `pipe_ctx.register_alias_registry` and a type in `pipe_ctx.type_name_registry`.
|
||||
--- Pointer depth remains bounded to 0 or 1, and duplicate defaults remain errors.
|
||||
--- @param _src SourceFile -- unused (kept for the per_source shape)
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_semantic_reg_defaults(_src, pipe_ctx, findings)
|
||||
@@ -215,7 +193,7 @@ end
|
||||
--- Check: atom_reg_types(R_X, <type>) entries target an alias in `pipe_ctx.register_alias_registry` and a type in `pipe_ctx.type_name_registry`.
|
||||
--- A bare `atom_reg` marker opts the `R_<n>` alias into GPR identity; references to R_T0..R_T3 require the same explicit marker.
|
||||
--- @param _src SourceFile
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_atom_reg_types(_src, pipe_ctx, findings)
|
||||
@@ -247,7 +225,7 @@ end
|
||||
|
||||
--- Check: atom_view(Binds_X) entries reference a Binds_* struct with at least one field.
|
||||
--- @param _src SourceFile
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_atom_view_layout(_src, pipe_ctx, findings)
|
||||
@@ -277,7 +255,7 @@ end
|
||||
|
||||
--- Check: Binds_* structs require unique field names because atom_view uses those names for typed-field lookup in gdb.
|
||||
--- @param _src SourceFile
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_binds_no_duplicate_fields(_src, pipe_ctx, findings)
|
||||
@@ -310,7 +288,7 @@ end
|
||||
--- 6. unsupported target_kind -> marker precedes an unrelated declaration
|
||||
--- Valid markers stamp `debug_skip` on whole-atom, bare-component, and proc-component declaration records in scan_source.lua.
|
||||
--- @param marker DebugSkipMarker
|
||||
--- @param _pipe_ctx PipeCtx -- Unused; kept for consistency with per_annot
|
||||
--- @param _pipe_ctx PassScratch -- Unused; kept for consistency with per_annot
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_skip_marker(marker, _pipe_ctx, findings)
|
||||
@@ -368,7 +346,7 @@ end
|
||||
--- Warn when a source references an unregistered alias.
|
||||
--- When a source uses an unregistered R_X, this check emits one pass-level info entry for that source and directs C-ABI register names to explicit alias registration.
|
||||
--- @param _src SourceFile
|
||||
--- @param pipe_ctx PipeCtx
|
||||
--- @param pipe_ctx PassScratch
|
||||
--- @param findings Findings
|
||||
--- @return nil
|
||||
local function check_wave_context_migration(_src, pipe_ctx, findings)
|
||||
@@ -425,9 +403,9 @@ local CHECK_RULES = { ---@type CheckRule[]
|
||||
--- Builds one pass-wide pipe_ctx from the merged `corpus.*` registries and source-ordered `corpus.atom_infos`; per-source declarations and bodies remain in `src.scan`.
|
||||
--- The module ownership contract above requires callers to construct `ctx.shared.corpus` through `build_ctx`; the error message below enforces that gate.
|
||||
--- @param ctx PassCtx
|
||||
--- @return PipeCtx
|
||||
--- @return PassScratch
|
||||
local function build_corpus_pipe_ctx(ctx)
|
||||
local view = duffle.corpus_view(ctx) ---@type PipeCtx
|
||||
local view = duffle.corpus_view(ctx) ---@type PassScratch
|
||||
local annot_counts = {} ---@type table<string, integer> -- bag: atom name -> annotation count
|
||||
for _, info in ipairs(view.atom_infos) do ---@type integer, AtomInfoEntry
|
||||
if info and info.atom_name then
|
||||
@@ -443,7 +421,7 @@ end
|
||||
--- Validate one source against its pre-scanned SourceScan payload + the corpus-wide pipe_ctx.
|
||||
--- @param ctx PassCtx
|
||||
--- @param src SourceFile
|
||||
--- @param corpus_pipe_ctx PipeCtx|nil -- Built once per pass from corpus registries; nil builds the same projection here.
|
||||
--- @param corpus_pipe_ctx PassScratch|nil -- Built once per pass from corpus registries; nil builds the same projection here.
|
||||
--- @return AnnotatedResult
|
||||
local function validate(ctx, src, corpus_pipe_ctx)
|
||||
corpus_pipe_ctx = corpus_pipe_ctx or build_corpus_pipe_ctx(ctx)
|
||||
@@ -453,7 +431,7 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
||||
local seen_defaults = {}; for reg, _ in pairs (scan.types or {}) do seen_defaults[reg] = (seen_defaults[reg] or 0) + 1 end ---@type table<string, integer> -- bag: register ident -> occurrence count
|
||||
local atom_infos_list = {}; for _, ai in ipairs(scan.atom_infos or {}) do atom_infos_list[#atom_infos_list + 1] = ai end ---@type AtomInfoEntry[]
|
||||
|
||||
local pipe_ctx = { ---@type PipeCtx
|
||||
local pipe_ctx = { ---@type PassScratch
|
||||
atom_index = {},
|
||||
binds_index = {},
|
||||
annot_counts = corpus_pipe_ctx.annot_counts,
|
||||
@@ -548,12 +526,12 @@ M.validate = validate
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type PassOutputEntry[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
-- Build the shared pipe_ctx once for this run; every validate() call sees the same cross-source registries.
|
||||
-- The corpus owns the canonical cross-source registries; per-source scans retain body / declaration ownership.
|
||||
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx) ---@type PipeCtx
|
||||
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx) ---@type PassScratch
|
||||
local corpus = ctx.shared.corpus ---@type Corpus
|
||||
|
||||
-- Group `corpus.sources_by_dir` by module, validate every source in each bucket, and emit one errors.h per directory.
|
||||
@@ -562,17 +540,17 @@ function M.run(ctx)
|
||||
for dir, dir_sources in pairs(by_dir) do ---@type string, SourceFile[]
|
||||
local dir_basename = dir:match("([^/\\]+)$") or dir ---@type string
|
||||
local dir_atoms = 0 ---@type integer
|
||||
local dir_errors = {} ---@type PassFinding[]
|
||||
local dir_warnings = {} ---@type PassFinding[]
|
||||
local dir_errors = {} ---@type Finding[]
|
||||
local dir_warnings = {} ---@type Finding[]
|
||||
for _, src in ipairs(dir_sources) do ---@type integer, SourceFile
|
||||
local result = validate(ctx, src, corpus_pipe_ctx) ---@type AnnotatedResult
|
||||
result.source = src.path -- tag for downstream rendering
|
||||
dir_atoms = dir_atoms + #result.atoms
|
||||
for _, e in ipairs(result.errors) do ---@type integer, PassFinding
|
||||
for _, e in ipairs(result.errors) do ---@type integer, Finding
|
||||
dir_errors[#dir_errors + 1] = { line = e.line, msg = e.msg, source = src.path }
|
||||
errors [#errors + 1] = { line = e.line, msg = e.msg }
|
||||
end
|
||||
for _, w in ipairs(result.warnings) do ---@type integer, PassFinding
|
||||
for _, w in ipairs(result.warnings) do ---@type integer, Finding
|
||||
dir_warnings[#dir_warnings + 1] = { line = w.line, msg = w.msg }
|
||||
warnings [#warnings + 1] = { line = w.line, msg = w.msg }
|
||||
end
|
||||
|
||||
@@ -595,8 +595,8 @@ end
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type PassOutputEntry[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
if type(corpus) ~= "table" or type(corpus.source_order) ~= "table" then
|
||||
|
||||
+15
-17
@@ -28,8 +28,8 @@
|
||||
|
||||
--- @class AutoRegResult
|
||||
--- @field outputs AutoRegOutput[]
|
||||
--- @field errors PassFinding[]
|
||||
--- @field warnings PassFinding[]
|
||||
--- @field errors Finding[]
|
||||
--- @field warnings Finding[]
|
||||
|
||||
--- @class AutoRegPass
|
||||
--- @field run fun(ctx: PassCtx): AutoRegResult
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||
local isa = require("duffle_isa") ---@type DuffleIsa
|
||||
|
||||
--- ════════════════════════════════════════════════════════════════════════════
|
||||
--- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY
|
||||
@@ -54,15 +55,12 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
--- R_K0/K1 (codes 26-27) — Kernel / interrupt handler reserves. Never touched by user code.
|
||||
--- R_GP/SP/FP/RA (codes 28-31) — R_SP/R_FP/R_RA are tape-runtime carriers between tape_enter and tape_exit; R_GP stays the host global pointer.
|
||||
---
|
||||
local POOL = { ---@type GprIdent[]
|
||||
"R_V0", "R_V1",
|
||||
"R_T0", "R_T1", "R_T2", "R_T3",
|
||||
"R_T4", "R_T5", "R_T6", "R_T7",
|
||||
"R_A0", "R_A1", "R_A2", "R_A3",
|
||||
"R_S0", "R_S1", "R_S2", "R_S3",
|
||||
"R_S4", "R_S5", "R_S6", "R_S7",
|
||||
"R_T8", "R_T9",
|
||||
}
|
||||
local POOL = {} ---@type GprIdent[]
|
||||
for _, row in ipairs(isa.GPR_ROLE) do ---@type integer, GprRole
|
||||
if row.pool then
|
||||
POOL[#POOL + 1] = row.name
|
||||
end
|
||||
end
|
||||
|
||||
-- Map from integer MIPS GPR code (the `code` field on AliasEntry) to the physical GPR ident in POOL.
|
||||
-- The standard MIPS O32 ABI register numbering matches mips.h's R_*_Code #defines (mips.h).
|
||||
@@ -94,7 +92,7 @@ end
|
||||
--- @param phase_label string
|
||||
--- @param decls table<string, string> -- bag: auto-reg symbol -> decl payload
|
||||
--- @return GprAllocMap
|
||||
--- @return PassFinding[]
|
||||
--- @return Finding[]
|
||||
local function allocate_phase(phase_label, decls)
|
||||
-- Deep-copy POOL into a fresh sequence table. The original `table.unpack and table.unpack(POOL) or { unpack(POOL) }`
|
||||
-- idiom wraps the unpacked values in a single inner table under LuaJIT 5.1 (`table.unpack` is nil; the `or` returns one value),
|
||||
@@ -102,7 +100,7 @@ local function allocate_phase(phase_label, decls)
|
||||
local pool = {} ---@type GprIdent[]
|
||||
for i = 1, #POOL do pool[i] = POOL[i] end ---@type integer
|
||||
local result = {} ---@type GprAllocMap
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
for _, sym in ipairs(stable_sort_keys(decls)) do ---@type integer, string
|
||||
local next_gpr = table.remove(pool, 1) ---@type GprIdent|nil
|
||||
if not next_gpr then
|
||||
@@ -226,8 +224,8 @@ local M = {} ---@type AutoRegPass
|
||||
--- @return AutoRegResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type AutoRegOutput[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
if type(corpus) ~= "table" then
|
||||
@@ -244,12 +242,12 @@ function M.run(ctx)
|
||||
-- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations).
|
||||
local phase_allocations = {} ---@type table<string, GprAllocMap> -- bag: phase_label -> alloc map
|
||||
for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do ---@type string, table<string, string>
|
||||
local mapping, errs = allocate_phase(phase_label, decls) ---@type GprAllocMap, PassFinding[]
|
||||
local mapping, errs = allocate_phase(phase_label, decls) ---@type GprAllocMap, Finding[]
|
||||
for sym, gpr in pairs(mapping) do ---@type string, GprIdent
|
||||
phase_allocations[phase_label] = phase_allocations[phase_label] or {}
|
||||
phase_allocations[phase_label][sym] = gpr
|
||||
end
|
||||
for _, e in ipairs(errs) do ---@type integer, PassFinding
|
||||
for _, e in ipairs(errs) do ---@type integer, Finding
|
||||
errors[#errors + 1] = e
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--- passes/components.lua — Component-macro header generator.
|
||||
---
|
||||
--- Ownership: `corpus.word_counts`, `corpus.components`, and `corpus.component_body_index`.
|
||||
--- Ownership: `corpus.word_counts` and `corpus.components`.
|
||||
--- Scanner owns `declaration_comment` and `debug_skip` on each declaration record; this pass projects both forward.
|
||||
---
|
||||
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations (kind="comp_bare" / "comp_proc"),
|
||||
@@ -56,20 +56,24 @@ local MACS_FILENAME = "macs.h" ---@type string
|
||||
-- SourceScan, AtomEntry, CorpusCollision, CollisionSite: see scan_source.lua
|
||||
-- BodyToken: see emission_model.lua
|
||||
-- WordCounts: see word_count_eval.lua
|
||||
-- ComponentBodyEntry: see duffle_emit.lua
|
||||
-- InstructionRow, GteCommandRow: see duffle_isa.lua
|
||||
|
||||
--- @class Component
|
||||
--- @field name string -- Atom name (without `ac_` prefix)
|
||||
--- @field body string -- Brace-delimited body (without the braces)
|
||||
--- @field body_off integer|nil -- Byte offset of body[1] in source
|
||||
--- @field body_tokens BodyToken[]|nil
|
||||
--- @field args string|nil -- Function-args string (function form only)
|
||||
--- @field arg_names string[]|nil -- Formal names with leading `ab` dropped
|
||||
--- @field line integer -- Source line of the declaration
|
||||
--- @field comment string|nil -- Scanner-owned `declaration_comment`; the components pass reads it from the scanner record
|
||||
--- @field kind string -- "comp_bare" | "comp_proc" (atom_proc is NOT a component — see `project_components`)
|
||||
--- @field debug_skip boolean -- Mirror of `a.debug_skip` (scanner-owned); true iff a bare `atom_dbg_skip` marker immediately preceded the declaration
|
||||
--- @field name string -- Atom name (without `ac_` prefix)
|
||||
--- @field body string -- Brace-delimited body (without the braces)
|
||||
--- @field body_off integer|nil -- Byte offset of body[1] in source
|
||||
--- @field body_tokens BodyToken[]|nil
|
||||
--- @field args string|nil -- Function-args string (function form only)
|
||||
--- @field arg_names string[]|nil -- Formal names with leading `ab` dropped
|
||||
--- @field line integer -- Source line of the declaration
|
||||
--- @field comment string|nil -- Scanner-owned `declaration_comment`; the components pass reads it from the scanner record
|
||||
--- @field kind string -- "comp_bare" | "comp_proc" (atom_proc is NOT a component — see `project_components`)
|
||||
--- @field debug_skip boolean -- Mirror of `a.debug_skip` (scanner-owned); true iff a bare `atom_dbg_skip` marker immediately preceded the declaration
|
||||
--- @field path string|nil -- Slash-normalized source path (collision sites)
|
||||
--- @field source string|nil -- Absolute source path (emit)
|
||||
--- @field line_of (fun(pos: integer): integer)|nil
|
||||
--- @field cycle_cost integer|nil -- From metadata[c.name]; nil when the body was not costed
|
||||
--- @field gp0_contrib integer|nil -- From metadata[c.name]; nil when the body was not costed
|
||||
|
||||
--- @class ComponentMeta
|
||||
--- @field cycle_cost integer
|
||||
@@ -800,16 +804,7 @@ local function update_canonical_word_counts(corpus, components, counts)
|
||||
end
|
||||
end
|
||||
|
||||
--- @class ComponentDef
|
||||
--- @field name string -- Bare name (without ac_/mac_ prefix)
|
||||
--- @field line integer -- Definition source line (line of `MipsAtomComp_(ac_X)` / `MipsAtomComp_Proc_(ac_X, ...)`)
|
||||
--- @field path string -- Absolute source path of the definition
|
||||
--- @field kind string -- "comp_bare" | "comp_proc" (atom_proc is NOT a component)
|
||||
--- @field debug_skip boolean -- Mirror of the scanner-owned `a.debug_skip`; consumers read this directly
|
||||
--- @field cycle_cost integer|nil -- From metadata[c.name]; nil when the body was not costed
|
||||
--- @field gp0_contrib integer|nil -- From metadata[c.name]; nil when the body was not costed
|
||||
|
||||
--- (internal) Populate `corpus.components` with this source's components-by-name map.
|
||||
--- (internal) Populate `corpus.components` with this source's one component row per bare name.
|
||||
--- First declaration wins; later declarations of the same bare name are dropped and recorded as a collision via `corpus.collisions` (kind = "component").
|
||||
--- The pass does NOT write to `ctx.shared.components`.
|
||||
--- No parallel skip map is built here; consumers that need the per-component skip state read `corpus.components[name].debug_skip` directly.
|
||||
@@ -818,28 +813,29 @@ end
|
||||
--- @param src SourceFile
|
||||
--- @param components Component[]
|
||||
--- @param metadata ComponentMetaMap
|
||||
--- @param scan SourceScan
|
||||
--- @return nil
|
||||
local function update_canonical_components(corpus, src, components, metadata)
|
||||
local rel_path = src.path:gsub("\\", "/") ---@type string
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
local function update_canonical_components(corpus, src, components, metadata, scan)
|
||||
local rel_path = src.path:gsub("\\", "/") ---@type string
|
||||
local line_of = scan and scan.line_of ---@type (fun(pos: integer): integer)|nil
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
-- Keyed by bare name (e.g. `yield`, `load_tri_indices`).
|
||||
-- The atoms_source_map pass looks up components by bare name from the corpus;
|
||||
-- `mac_` prefix lives at the call-site identifier and is stripped before lookup.
|
||||
local m = metadata and metadata[c.name] or nil ---@type ComponentMeta|nil
|
||||
if corpus.components[c.name] == nil then
|
||||
corpus.components[c.name] = {
|
||||
name = c.name,
|
||||
line = c.line,
|
||||
path = rel_path,
|
||||
kind = c.kind or "comp_bare",
|
||||
debug_skip = c.debug_skip == true,
|
||||
cycle_cost = m and m.cycle_cost or nil,
|
||||
gp0_contrib = m and m.gp0_contrib or nil,
|
||||
}
|
||||
c.path = rel_path
|
||||
c.source = src.path
|
||||
c.line_of = line_of
|
||||
c.kind = c.kind or "comp_bare"
|
||||
c.debug_skip = c.debug_skip == true
|
||||
c.cycle_cost = m and m.cycle_cost or nil
|
||||
c.gp0_contrib = m and m.gp0_contrib or nil
|
||||
corpus.components[c.name] = c
|
||||
else
|
||||
-- A second declaration of the same bare name: record a typed collision so static-analysis + the report can surface it.
|
||||
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
||||
local existing = corpus.components[c.name] ---@type ComponentDef
|
||||
local existing = corpus.components[c.name] ---@type Component
|
||||
if existing.path ~= rel_path or existing.line ~= c.line then
|
||||
local kind = c.kind or "comp_bare" ---@type string
|
||||
local first_kind = existing.kind or "comp_bare" ---@type string
|
||||
@@ -856,37 +852,12 @@ local function update_canonical_components(corpus, src, components, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Populate `corpus.component_body_index` with this source's body index entries.
|
||||
--- First declaration wins; later declarations are dropped (no separate collision record: the components collision is already surfaced by `update_canonical_components`).
|
||||
--- The pass writes to `corpus.component_body_index` only (the corpus owns this projection).
|
||||
--- @param corpus Corpus
|
||||
--- @param src SourceFile
|
||||
--- @param components Component[]
|
||||
--- @param scan SourceScan
|
||||
--- @return nil
|
||||
local function update_canonical_component_body_index(corpus, src, components, scan)
|
||||
local line_of = scan and scan.line_of ---@type (fun(pos: integer): integer)|nil
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
if corpus.component_body_index[c.name] == nil then
|
||||
corpus.component_body_index[c.name] = {
|
||||
body_tokens = c.body_tokens,
|
||||
body_off = c.body_off,
|
||||
line_of = line_of,
|
||||
source = src.path,
|
||||
declaration = c.line,
|
||||
kind = c.kind,
|
||||
arg_names = c.arg_names,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param ctx PassCtx
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type MacsOutput[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
-- Corpus ownership gate.
|
||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
@@ -904,8 +875,7 @@ function M.run(ctx)
|
||||
|
||||
-- Projection ownership:
|
||||
-- * `corpus.word_counts["mac_"..name]` — current component count
|
||||
-- * `corpus.components[name]` — bare-name component definition
|
||||
-- * `corpus.component_body_index[name]` — body / line_of / source index
|
||||
-- * `corpus.components[name]` — one row: body, line_of, source, cost
|
||||
-- The pass writes to the corpus only; consumers read from the corpus directly.
|
||||
|
||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/macs.h`.
|
||||
@@ -937,8 +907,7 @@ function M.run(ctx)
|
||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||
local per_source = project_components(src.text, src.scan) or {} ---@type Component[]
|
||||
if #per_source > 0 then
|
||||
update_canonical_components(corpus, src, per_source, metadata_per_source[src])
|
||||
update_canonical_component_body_index(corpus, src, per_source, src.scan)
|
||||
update_canonical_components(corpus, src, per_source, metadata_per_source[src], src.scan)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -450,7 +450,7 @@ local DEFAULT_BASENAME = "hello_gte" ---@type string
|
||||
|
||||
--- DieSchema / FORM_WRITERS / atom-table records live on this file.
|
||||
--- SourceFile, Path, AtomName: see duffle.lua.
|
||||
--- Corpus, PassCtx, PassResult, PassFlags, PassShared, PassFinding: see ps1_meta.lua.
|
||||
--- Corpus, PassCtx, PassResult, PassFlags, PassShared, Finding: see ps1_meta.lua.
|
||||
--- AtomEntry, AliasEntry, TypeNameEntry, TypeField, AtomInfoEntry, BindsEntry,
|
||||
--- AtomViewEntry, AtomCtxEntry, AtomPhaseGroup, SourceScan, RegTypeOverride: see scan_source.lua.
|
||||
--- AtomPaths, WordEvent, BodyToken: see emission_model.lua.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--- passes/emission_model.lua: Per-atom emission projection.
|
||||
---
|
||||
--- The `emission-model` pass owns `atom.paths`, the canonical per-atom mutable surface for atoms and raw atoms with bodies in `ctx.shared.corpus.source_order`.
|
||||
--- For each atom, the pass invokes `duffle.project_emission(body_text, component_index, word_counts, components)`.
|
||||
--- For each atom, the pass invokes `duffle.project_emission(body_text, components, word_counts, components)`.
|
||||
--- It stores the ordered `items` stream plus the dense `word_events` / `markers` / `invocations` views on `atom.paths`.
|
||||
---
|
||||
--- Public boundary:
|
||||
@@ -92,17 +92,7 @@
|
||||
--- @field consuming_encoder string|nil
|
||||
--- @field consuming_arg_pos integer|nil
|
||||
|
||||
--- @class EmitError
|
||||
--- @field kind string
|
||||
--- @field line integer|nil
|
||||
--- @field msg string
|
||||
--- @field source string|nil
|
||||
--- @field schema_name string|nil
|
||||
|
||||
--- @class EmitWarning
|
||||
--- @field kind string
|
||||
--- @field line integer|nil
|
||||
--- @field msg string
|
||||
-- Finding: see ps1_meta.lua
|
||||
|
||||
--- @class AtomPaths
|
||||
--- @field tokens BodyToken[]
|
||||
@@ -111,8 +101,8 @@
|
||||
--- @field word_events WordEvent[]
|
||||
--- @field markers EmissionMarker[]
|
||||
--- @field invocations InvocationRecord[]
|
||||
--- @field errors EmitError[]
|
||||
--- @field warnings EmitWarning[]
|
||||
--- @field errors Finding[]
|
||||
--- @field warnings Finding[]
|
||||
|
||||
--- @class EmissionModelPass
|
||||
--- @field run fun(ctx: PassCtx): PassResult
|
||||
@@ -136,7 +126,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
-- * ROOT invocations (`inv.parent_id == 0`) receive body-relative `call_line` values directly from `M.LineIndex(body_text)` in the walker.
|
||||
-- The source `line_of` closure supplies physical lines at the close site, so this function converts each root value exactly once.
|
||||
-- * INNER invocations (`inv.parent_id ~= 0`) receive physical `call_line` values directly from the COMPONENT's `line_of` in the walker.
|
||||
-- Recursive descent forwards that closure through `corpus.component_body_index[name].line_of`; those values arrive physical and remain unchanged.
|
||||
-- Recursive descent forwards that closure through `corpus.components[name].line_of`; those values arrive physical and remain unchanged.
|
||||
--
|
||||
-- After this function, every `inv.call_line` is physical. DWARF and provenance output read it directly.
|
||||
-- The word-event loop forwards the already-physical `outer_inv.call_line` into `we.call_line` for words inside an invocation.
|
||||
@@ -155,7 +145,7 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
||||
-- The walker assigns line 2 to the body's first content line because line 1 is the trailing `\n` after `{`. Body-text line k therefore maps to `root_body_line + (k - 1)`.
|
||||
-- `body_off - 1` points at the opening `{`, whose line index identifies the header line. `body_off` points after `{` and would shift every word row forward by one line.
|
||||
local root_body_line = root_line_of(atom_record.body_off - 1) or atom_record.line or 0 ---@type integer
|
||||
local component_index = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
||||
local components = corpus.components or {} ---@type table<string, Component>
|
||||
local word_items = {} ---@type EmissionItem[]
|
||||
|
||||
for _, item in ipairs(projection.items) do ---@type integer, EmissionItem
|
||||
@@ -176,7 +166,7 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
||||
local inner_id = ids[#ids] ---@type integer
|
||||
local inner_inv = inner_id and projection.invocations[inner_id] ---@type InvocationRecord|nil
|
||||
if inner_inv then
|
||||
local component = component_index[inner_inv.component_name] ---@type ComponentBodyEntry|nil
|
||||
local component = components[inner_inv.component_name] ---@type Component|nil
|
||||
if component and component.line_of then
|
||||
-- Walker used `comp.line_of`, which is the source's physical LineIndex. item.line is already physical.
|
||||
return item.line or 0
|
||||
@@ -257,13 +247,13 @@ end
|
||||
local function project_atom(atom_record, src, corpus)
|
||||
local body = atom_record.body or "" ---@type string
|
||||
local wc = corpus.word_counts or {} ---@type WordCounts
|
||||
local cbi = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
||||
local comps = corpus.components or {} ---@type table<string, Component>
|
||||
local schema = nil ---@type RegUseSchema|nil
|
||||
if atom_record.reg_use_schema_name then
|
||||
schema = corpus.reg_use_schemas and corpus.reg_use_schemas[atom_record.reg_use_schema_name]
|
||||
end
|
||||
-- That construction site stamps `invocation.debug_skip` while appending each record to `proj.invocations`.
|
||||
local proj = duffle.project_emission(body, cbi, wc, corpus.components, { ---@type EmissionProjection
|
||||
local proj = duffle.project_emission(body, comps, wc, comps, { ---@type EmissionProjection
|
||||
reg_use_schema = schema,
|
||||
reg_use_param = atom_record.reg_use_param_name,
|
||||
atom_name = atom_record.name,
|
||||
@@ -271,13 +261,22 @@ local function project_atom(atom_record, src, corpus)
|
||||
})
|
||||
if atom_record.reg_use_schema_name and not schema then
|
||||
proj.errors[#proj.errors + 1] = {
|
||||
kind = "reguse_missing_schema",
|
||||
msg = string.format("RegUse schema %q is missing", atom_record.reg_use_schema_name),
|
||||
kind = "error",
|
||||
check = "reguse_missing_schema",
|
||||
msg = string.format("RegUse schema %q is missing", atom_record.reg_use_schema_name),
|
||||
schema_name = atom_record.reg_use_schema_name,
|
||||
}
|
||||
end
|
||||
for _, err in ipairs(corpus.reg_use_errors or {}) do ---@type integer, EmitError
|
||||
for _, err in ipairs(corpus.reg_use_errors or {}) do ---@type integer, RegUseError
|
||||
if err.schema_name == atom_record.reg_use_schema_name then
|
||||
proj.errors[#proj.errors + 1] = err
|
||||
proj.errors[#proj.errors + 1] = {
|
||||
kind = "error",
|
||||
check = err.kind,
|
||||
line = err.line or err.source_line or 0,
|
||||
msg = err.msg or "",
|
||||
source = err.source or err.source_file,
|
||||
schema_name = err.schema_name,
|
||||
}
|
||||
end
|
||||
end
|
||||
local paths = { ---@type AtomPaths
|
||||
@@ -303,8 +302,8 @@ end
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type PassOutputEntry[]
|
||||
local errors = {} ---@type EmitError[]
|
||||
local warnings = {} ---@type EmitWarning[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
local corpus = ctx and ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
if type(corpus) ~= "table" then error("emission_model: ctx.shared.corpus is required (canonical projection)", 0) end
|
||||
@@ -322,20 +321,24 @@ function M.run(ctx)
|
||||
return
|
||||
end
|
||||
local proj = project_atom(atom, src, corpus) ---@type EmissionProjection
|
||||
for _, e in ipairs(proj.errors) do ---@type integer, EmitError
|
||||
-- Preserve `kind` (cycle / count_mismatch / unbalanced) so readers dispatch on the diagnostic class and leave the message string as display text.
|
||||
for _, e in ipairs(proj.errors) do ---@type integer, Finding
|
||||
-- Finding.kind is severity. Finding.check holds the diagnostic code
|
||||
-- (cycle / count_mismatch / unbalanced / reguse_*).
|
||||
errors[#errors + 1] = {
|
||||
kind = e.kind,
|
||||
line = e.line,
|
||||
msg = e.msg,
|
||||
source = e.source or src.path,
|
||||
kind = "error",
|
||||
check = e.check,
|
||||
line = e.line,
|
||||
msg = e.msg,
|
||||
source = e.source or src.path,
|
||||
schema_name = e.schema_name,
|
||||
}
|
||||
end
|
||||
for _, w in ipairs(proj.warnings) do ---@type integer, EmitWarning
|
||||
for _, w in ipairs(proj.warnings) do ---@type integer, Finding
|
||||
warnings[#warnings + 1] = {
|
||||
kind = w.kind,
|
||||
line = w.line,
|
||||
msg = w.msg,
|
||||
kind = "warning",
|
||||
check = w.check,
|
||||
line = w.line,
|
||||
msg = w.msg,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -137,7 +137,7 @@ end
|
||||
--- missing `consuming_encoder` -> ERROR. A lone top-level `atom_offset` is not a branch.
|
||||
--- @param labels table<string, integer>
|
||||
--- @param branches OffsetBranch[]
|
||||
--- @param errors PassFinding[]
|
||||
--- @param errors Finding[]
|
||||
--- @return BranchOffset[]
|
||||
local function compute_offsets(labels, branches, errors)
|
||||
local results = {} ---@type BranchOffset[]
|
||||
@@ -259,7 +259,7 @@ local M = {} ---@type OffsetsPass
|
||||
--- @param ctx PassCtx
|
||||
--- @param dir string
|
||||
--- @param sources SourceFile[]
|
||||
--- @param errors PassFinding[]
|
||||
--- @param errors Finding[]
|
||||
--- @return string|nil
|
||||
local function process_directory(ctx, dir, sources, errors)
|
||||
local atoms_data = {} ---@type AtomData[]
|
||||
@@ -297,8 +297,8 @@ end
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {} ---@type OffsetOutput[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
local errors = {} ---@type Finding[]
|
||||
local warnings = {} ---@type Finding[]
|
||||
|
||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
if type(corpus) ~= "table" then
|
||||
|
||||
+26
-44
@@ -54,36 +54,17 @@ local PASS_NAME = "report" ---@type string
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- SourceFile: see duffle.lua
|
||||
-- PassCtx, PassResult, PassFinding, PassOutputEntry: see ps1_meta.lua
|
||||
-- PassCtx, PassResult, Finding, PassOutputEntry: see ps1_meta.lua
|
||||
-- AtomEntry, SourceScan, BindsEntry, AtomInfoEntry, AliasEntry,
|
||||
-- AtomPhaseGroup, AtomViewEntry, AtomCtxEntry, CorpusCollision: see scan_source.lua
|
||||
-- CheckFinding, AtomAnalysis: see static_analysis.lua
|
||||
-- AtomAnalysis: see static_analysis.lua
|
||||
-- WordCounts: see word_count_eval.lua
|
||||
-- ComponentBodyEntry: see duffle_emit.lua
|
||||
-- Component: see components.lua
|
||||
-- AtomPaths, WordEvent: see emission_model.lua
|
||||
-- GprAllocMap: see auto_reg.lua
|
||||
|
||||
-- Shapes produced by `passes/annotation.lua`'s `M.validate()`.
|
||||
|
||||
--- @class AnnotEntry
|
||||
--- @field line integer -- Source line
|
||||
--- @field macro string -- Macro name (e.g. "atom_reads")
|
||||
--- @field name string -- Atom name (if a `name(...)` was given)
|
||||
--- @field kind string -- "atom_info" | "atom_bind" | ...
|
||||
--- @field binds string|nil -- Binds_X name if any
|
||||
--- @field reads string[] -- R_* names (read targets)
|
||||
--- @field writes string[] -- R_* names (write targets)
|
||||
--- @field error string|nil -- Error message if annotation was malformed
|
||||
|
||||
--- @class BindsField
|
||||
--- @field name string -- Field name
|
||||
--- @field offset integer -- Byte offset within the Binds_X struct
|
||||
|
||||
--- @class BindsStruct
|
||||
--- @field name string -- Struct name (e.g. "Binds_Floor")
|
||||
--- @field line integer -- Source line of the typedef
|
||||
--- @field bytes integer -- Total byte size
|
||||
--- @field fields BindsField[] -- The field list
|
||||
-- Binds_* rows are BindsEntry (scan_source.lua). Report reads src.scan.binds.
|
||||
|
||||
--- @class MacroEntry
|
||||
--- @field name string -- Macro name (e.g. "WORD_COUNT(my_macro, 4)")
|
||||
@@ -93,12 +74,12 @@ local PASS_NAME = "report" ---@type string
|
||||
--- @class AnnotationResult
|
||||
--- @field source string -- Set by this pass; original source path
|
||||
--- @field atoms AtomEntry[] -- Atom declarations in this source
|
||||
--- @field annots AnnotEntry[] -- Annotation entries
|
||||
--- @field annots AtomInfoEntry[] -- Annotation entries
|
||||
--- @field macros MacroEntry[] -- Macro word-count declarations
|
||||
--- @field binds BindsStruct[] -- Binds_* struct declarations
|
||||
--- @field errors PassFinding[] -- Errors from validation
|
||||
--- @field warnings PassFinding[] -- Warnings from validation
|
||||
--- @field info PassFinding[] -- Info summary (not rendered here)
|
||||
--- @field binds BindsEntry[] -- Binds_* struct declarations
|
||||
--- @field errors Finding[] -- Errors from validation
|
||||
--- @field warnings Finding[] -- Warnings from validation
|
||||
--- @field info Finding[] -- Info summary (not rendered here)
|
||||
|
||||
--- @class ModuleEntry
|
||||
--- @field dir string -- Absolute directory path
|
||||
@@ -196,7 +177,7 @@ local PASS_NAME = "report" ---@type string
|
||||
--- @field sources SourceFile[]
|
||||
--- @field decls AtomEntry[]
|
||||
--- @field schemas RegUseSchema[]
|
||||
--- @field findings CheckFinding[]
|
||||
--- @field findings Finding[]
|
||||
--- @field corpus Corpus
|
||||
|
||||
--- @class SectionRenderer
|
||||
@@ -424,11 +405,11 @@ end
|
||||
--- @param view ModuleView
|
||||
--- @return nil
|
||||
local function render_section_components(add, view)
|
||||
local rows = {} ---@type ComponentReportRow[]
|
||||
local index = (view.corpus and view.corpus.component_body_index) or {} ---@type table<string, ComponentBodyEntry>
|
||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||
local rows = {} ---@type ComponentReportRow[]
|
||||
local index = (view.corpus and view.corpus.components) or {} ---@type table<string, Component>
|
||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||
if a.kind == "comp_bare" or a.kind == "comp_proc" then
|
||||
local idx = index[a.name] or {} ---@type ComponentBodyEntry
|
||||
local idx = index[a.name] or {} ---@type Component
|
||||
local args = idx.arg_names or {} ---@type string[]
|
||||
rows[#rows + 1] = {
|
||||
name = a.name,
|
||||
@@ -698,8 +679,8 @@ end
|
||||
--- @param view ModuleView
|
||||
--- @return nil
|
||||
local function render_section_findings(add, view)
|
||||
local by_atom = {} ---@type table<string, CheckFinding[]>
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||
local by_atom = {} ---@type table<string, Finding[]>
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, Finding
|
||||
local key = f.atom or "?" ---@type string
|
||||
by_atom[key] = by_atom[key] or {}
|
||||
by_atom[key][#by_atom[key] + 1] = f
|
||||
@@ -707,11 +688,11 @@ local function render_section_findings(add, view)
|
||||
if next(by_atom) == nil then add("_(none)_"); add(""); return end
|
||||
local seen = {} ---@type table<string, boolean> -- bag: atom name already emitted
|
||||
--- @param name string
|
||||
--- @param fs CheckFinding[]
|
||||
--- @param fs Finding[]
|
||||
--- @return nil
|
||||
local function emit(name, fs)
|
||||
add("### " .. name)
|
||||
for _, f in ipairs(fs) do ---@type integer, CheckFinding
|
||||
for _, f in ipairs(fs) do ---@type integer, Finding
|
||||
local msg = f.msg or "" ---@type string
|
||||
local slot = slot_suffix(f.gpr_key or f.producer_destination) ---@type string|nil
|
||||
if slot and not msg:find("(slot ", 1, true) then
|
||||
@@ -766,11 +747,12 @@ local HIDDEN_UNLESS_WRITTEN = { ---@type table<string, boolean> -- bag: GPR key
|
||||
R_AT = true, R_TapePtr = true, R_AtomJmp = true,
|
||||
}
|
||||
|
||||
local PHYSICAL_GPR = { ---@type table<string, boolean> -- bag: physical GPR alias -> true
|
||||
R_T0 = true, R_T1 = true, R_T2 = true, R_T3 = true,
|
||||
R_T4 = true, R_T5 = true, R_T6 = true, R_T7 = true,
|
||||
R_V0 = true, R_V1 = true,
|
||||
}
|
||||
local PHYSICAL_GPR = {} ---@type table<string, boolean> -- bag: physical GPR alias -> true
|
||||
for _, row in ipairs(duffle.GPR_ROLE) do ---@type integer, GprRole
|
||||
if row.pool then
|
||||
PHYSICAL_GPR[row.name] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- @param atom AtomEntry
|
||||
--- @param key string
|
||||
@@ -945,7 +927,7 @@ local function render_module_meta_report(view)
|
||||
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
||||
end
|
||||
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, Finding
|
||||
if f.kind == "error" then n_err = n_err + 1
|
||||
elseif f.kind == "warning" then n_warn = n_warn + 1
|
||||
else n_info = n_info + 1
|
||||
@@ -1087,7 +1069,7 @@ function M.run(ctx)
|
||||
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
||||
end
|
||||
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||
for _, f in ipairs(view.findings or {}) do ---@type integer, Finding
|
||||
if f.kind == "error" then n_err = n_err + 1
|
||||
elseif f.kind == "warning" then n_warn = n_warn + 1
|
||||
else n_info = n_info + 1
|
||||
|
||||
@@ -118,6 +118,7 @@ local parse_enum_int_literal ---@type fun(text: string, start: integer): (intege
|
||||
--- @field ctx_atom string|nil
|
||||
--- @field phase string|nil
|
||||
--- @field info_line integer
|
||||
--- @field errors string[]|nil -- parse-time atom_info errors
|
||||
|
||||
--- @class BindsEntry
|
||||
--- @field line integer
|
||||
|
||||
+633
-593
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@
|
||||
--- 1. **Public utility** `M.count_token_words(token, wc)`: Used by `passes/offsets.lua`, `passes/annotation.lua`, and other passes.
|
||||
--- 2. **Pass entry** `M.run(ctx)`: Loads the authored `word_count.metadata.h` into `ctx.shared.corpus.word_counts` for downstream passes.
|
||||
--- The generated `.macs.h` files are OUTPUT artifacts and are NOT inputs to this pass;
|
||||
--- Current component counts are owned by `passes/components.lua` (which populates `corpus.word_counts` and `corpus.component_body_index`
|
||||
--- Current component counts are owned by `passes/components.lua` (which populates `corpus.word_counts` and `corpus.components`
|
||||
--- AFTER computing each current count from the just-built body + `corpus.word_counts`).
|
||||
---
|
||||
--- **Canonical contract**:
|
||||
--- * `ctx.shared.corpus.word_counts` is the count table.
|
||||
--- * `corpus.word_counts` is the sole count table. Consumers read `corpus.word_counts` directly.
|
||||
--- * `ctx.shared.components` and `ctx.shared.component_body_index` are NOT created by this pass (projections only).
|
||||
--- * `ctx.shared.components` is NOT created by this pass (projections only).
|
||||
--- * No `.macs.h` recursive discovery (no `scan_dir`, no scan cache, no `_invalidate_scan_cache`).
|
||||
---
|
||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
||||
|
||||
Reference in New Issue
Block a user