Last adjustments for metaprogram, hitting a intermission checkpoint to do docs, writeups, and other research distillations.

This commit is contained in:
ed
2026-08-21 14:02:41 -04:00
parent b61610d819
commit a37ffe6f58
6 changed files with 192 additions and 11 deletions
+1 -1
View File
@@ -248,11 +248,11 @@ FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ u4_(mem.ptr), mem.len
FI_ void tb_emit(TapeBuilder* tb, MipsAtom* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; } FI_ void tb_emit(TapeBuilder* tb, MipsAtom* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; }
FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; } FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; }
#define tb_emit_(atom) tb_emit(& tb, atom) #define tb_emit_(atom) tb_emit(& tb, atom)
#define tb_data_(field, data) tb_data(& tb, u4_(data))
FI_ void tb_bind(TapeBuilder* tb, Slice data) { mem_copy(tb->ptr + tb->used * S_(MipsCode), u4_(data.ptr), data.len); tb->used += data.len / S_(MipsCode); } FI_ void tb_bind(TapeBuilder* tb, Slice data) { mem_copy(tb->ptr + tb->used * S_(MipsCode), u4_(data.ptr), data.len); tb->used += data.len / S_(MipsCode); }
#define tb_bind_(tb,type,...) tb_bind(tb, (Slice){ (B1*)(& (type){__VA_ARGS__}), S_(type) }); static_assert(S_(type) % S_(MipsCode) == 0) #define tb_bind_(tb,type,...) tb_bind(tb, (Slice){ (B1*)(& (type){__VA_ARGS__}), S_(type) }); static_assert(S_(type) % S_(MipsCode) == 0)
// NOTE(Ed): Wip still ideating convention. Possibly will never use a composite.
#define tb_emit_wbind_(tb,atom,...) tb_emit(tb,atom); tb_bind_(tb,tmpl(Binds,atom),__VA_ARGS__) #define tb_emit_wbind_(tb,atom,...) tb_emit(tb,atom); tb_bind_(tb,tmpl(Binds,atom),__VA_ARGS__)
#define tb_emit_wbind2_(tb,atom,type,...) tb_emit(tb,atom); tb_bind_(tb,type,__VA_ARGS__) #define tb_emit_wbind2_(tb,atom,type,...) tb_emit(tb,atom); tb_bind_(tb,type,__VA_ARGS__)
+5 -5
View File
@@ -247,15 +247,15 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
tb.used = 0; tb_scope_run(& tb) { tb.used = 0; tb_scope_run(& tb) {
// Grab latest state from bios. // Grab latest state from bios.
tb_emit_(pad_bios_snapshot); tb_emit_(pad_bios_snapshot);
tb_data_(raw, & smem.pad_raw[0]); tb_data(& tb, u4_(& smem.pad_raw[0]));
tb_data_(state, & smem.pad[0]); tb_data(& tb, u4_(& smem.pad[0]));
// tb_emit_(pad_bios_snapshot); // tb_emit_(pad_bios_snapshot);
// tb_data_(raw, & smem.pad_raw[1]); // tb_data_(raw, & smem.pad_raw[1]);
// tb_data_(state, & smem.pad[1]); // tb_data_(state, & smem.pad[1]);
tb_emit_(pad_input_cam); tb_emit_(pad_input_cam);
tb_data_(state, & smem.pad[0]); tb_data(& tb, u4_(& smem.pad[0]));
tb_data_(cam, & smem.cam); tb_data(& tb, u4_(& smem.cam));
// tb_emit_(pad_input_cube_rotation); // tb_emit_(pad_input_cube_rotation);
// tb_data_(state, & smem.pad[0]); // tb_data_(state, & smem.pad[0]);
@@ -288,7 +288,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
if (use_c11_path == false) if (use_c11_path == false)
{ {
tb.used = 0; tb_scope_run(& tb) { tb.used = 0; tb_scope_run(& tb) {
resolve_look_at(& tb, & smem.cam.look_at, & smem.cam.pos, & smem.cube.pos, & v3s4(0, -fp_one, 0)); camera_look_at(& tb, & smem.cam, & smem.cube.pos, & v3s4(0, -fp_one, 0));
} }
} }
+16
View File
@@ -573,6 +573,21 @@ local function render_section_atom_bundles(add, view)
end end
end end
--- @param add fun(s: string): nil
--- @param view ModuleView
--- @return nil
local function render_section_tape_emits(add, view)
local wrote = false ---@type boolean
for _, src in ipairs(view.sources or {}) do ---@type integer, SourceFile
for _, emit in ipairs((src.scan and src.scan.tape_emits) or {}) do ---@type integer, TapeEmit
wrote = true
add(string.format("- `%s` `%s`", emit.name or "?", emit.binds or ""))
end
end
if not wrote then add("_(none)_"); add(""); return end
add("")
end
--- @param add fun(s: string): nil --- @param add fun(s: string): nil
--- @param view ModuleView --- @param view ModuleView
--- @return nil --- @return nil
@@ -925,6 +940,7 @@ local SECTION_RENDERERS = { ---@type SectionRenderer[]
{ header = "## Component annotations", render = render_section_component_annotations }, { header = "## Component annotations", render = render_section_component_annotations },
{ header = "## Binds_* structs", render = render_section_binds }, { header = "## Binds_* structs", render = render_section_binds },
{ header = "## Atom bundles", render = render_section_atom_bundles }, { header = "## Atom bundles", render = render_section_atom_bundles },
{ header = "## Tape emits", render = render_section_tape_emits },
{ header = "## Phases / views / ctx", render = render_section_phases }, { header = "## Phases / views / ctx", render = render_section_phases },
{ header = "## Register aliases", render = render_section_aliases }, { header = "## Register aliases", render = render_section_aliases },
{ header = "## Auto-reg", render = render_section_autoreg }, { header = "## Auto-reg", render = render_section_autoreg },
+104 -1
View File
@@ -189,6 +189,14 @@ local parse_enum_int_literal ---@type fun(text: string, start: integer): (intege
--- @class TapeChain --- @class TapeChain
--- @field [integer] string -- ordered atom names in one tb_emit chain --- @field [integer] string -- ordered atom names in one tb_emit chain
--- @class TapeEmit
--- @field name string
--- @field binds string|nil
--- @field line integer
--- @field path string
--- @field slot string|nil
--- @field data_words integer|nil
--- @class RegUseView --- @class RegUseView
--- @field names string[] --- @field names string[]
--- @field lanes boolean --- @field lanes boolean
@@ -215,6 +223,7 @@ local parse_enum_int_literal ---@type fun(text: string, start: integer): (intege
--- @field reg_use_schemas table<string, RegUseSchema> --- @field reg_use_schemas table<string, RegUseSchema>
--- @field reg_use_errors RegUseError[] --- @field reg_use_errors RegUseError[]
--- @field tape_chains TapeChain[] --- @field tape_chains TapeChain[]
--- @field tape_emits TapeEmit[]
--- @field atom_bundles table<string, AtomBundle> --- @field atom_bundles table<string, AtomBundle>
--- @field _source_file string|nil --- @field _source_file string|nil
--- @field _code_macros table<string, integer>|nil -- bag --- @field _code_macros table<string, integer>|nil -- bag
@@ -2900,6 +2909,23 @@ local function parse_addrs_assign(source, pos, ident_end, line_of, out)
return rhs return rhs
end end
--- @param out SourceScan
--- @param name string
--- @param last string
--- @param line integer
--- @return nil
local function push_tape_emit(out, name, last, line)
out.tape_emits = out.tape_emits or {}
out.tape_emits[#out.tape_emits + 1] = { ---@type TapeEmit
name = name,
binds = nil,
line = line,
path = out._source_file or "",
slot = last:find("->", 1, true) and name or nil,
data_words = 0,
}
end
--- @param source string --- @param source string
--- @param pos integer --- @param pos integer
--- @param ident_end integer --- @param ident_end integer
@@ -2910,10 +2936,12 @@ local function parse_tb_emit_(source, pos, ident_end, line_of, out)
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
if source:sub(after, after) ~= "(" then return ident_end end if source:sub(after, after) ~= "(" then return ident_end end
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
local name = duffle.trim(inner or ""):match("^([%w_]+)") ---@type string local last = duffle.trim(inner or "") ---@type string
local name = last:match("^([%w_]+)") ---@type string
if name then if name then
out._chain = out._chain or {} out._chain = out._chain or {}
out._chain[#out._chain + 1] = name out._chain[#out._chain + 1] = name
push_tape_emit(out, name, last, line_of(pos))
end end
return after_p or (after + 1) return after_p or (after + 1)
end end
@@ -2938,6 +2966,49 @@ local function parse_tb_emit(source, pos, ident_end, line_of, out)
if name then if name then
out._chain = out._chain or {} out._chain = out._chain or {}
out._chain[#out._chain + 1] = name out._chain[#out._chain + 1] = name
push_tape_emit(out, name, last, line_of(pos))
end
return after_p or (after + 1)
end
--- @param source string
--- @param pos integer
--- @param ident_end integer
--- @param line_of fun(pos: integer): integer
--- @param out SourceScan
--- @return integer
local function parse_tb_bind_(source, pos, ident_end, line_of, out)
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
if source:sub(after, after) ~= "(" then return ident_end end
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
local args = duffle.split_top_level_commas(inner or "") ---@type string[]
local typ = duffle.trim(args[2] or "") ---@type string
if typ ~= "" then
local emits = out.tape_emits or {} ---@type TapeEmit[]
for i = #emits, 1, -1 do ---@type integer
if emits[i].binds == nil then
emits[i].binds = typ
break
end
end
end
return after_p or (after + 1)
end
--- @param source string
--- @param pos integer
--- @param ident_end integer
--- @param line_of fun(pos: integer): integer
--- @param out SourceScan
--- @return integer
local function parse_tb_data(source, pos, ident_end, line_of, out)
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
if source:sub(after, after) ~= "(" then return ident_end end
local _, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
local emits = out.tape_emits or {} ---@type TapeEmit[]
local last = emits[#emits] ---@type TapeEmit|nil
if last then
last.data_words = (last.data_words or 0) + 1
end end
return after_p or (after + 1) return after_p or (after + 1)
end end
@@ -2945,6 +3016,8 @@ end
local C_STMT_PARSERS = { ---@type table<string, fun(source: string, pos: integer, ident_end: integer, line_of: fun(pos: integer): integer, out: SourceScan): integer> local C_STMT_PARSERS = { ---@type table<string, fun(source: string, pos: integer, ident_end: integer, line_of: fun(pos: integer): integer, out: SourceScan): integer>
tb_emit_ = parse_tb_emit_, tb_emit_ = parse_tb_emit_,
tb_emit = parse_tb_emit, tb_emit = parse_tb_emit,
tb_bind_ = parse_tb_bind_,
tb_data = parse_tb_data,
addrs = parse_addrs_assign, addrs = parse_addrs_assign,
} }
@@ -2998,6 +3071,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
raw_atoms = {}, raw_atoms = {},
binds = {}, binds = {},
atom_bundles = {}, atom_bundles = {},
tape_emits = {},
atom_infos = {}, atom_infos = {},
component_atom_infos = {}, component_atom_infos = {},
macros = {}, macros = {},
@@ -3313,6 +3387,7 @@ local function merge_corpus_registries(corpus)
corpus.reg_use_errors = corpus.reg_use_errors or {} corpus.reg_use_errors = corpus.reg_use_errors or {}
corpus.tape_chains = corpus.tape_chains or {} corpus.tape_chains = corpus.tape_chains or {}
corpus.atom_bundles = corpus.atom_bundles or {} corpus.atom_bundles = corpus.atom_bundles or {}
corpus.tape_emits = corpus.tape_emits or {}
-- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge). -- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge).
-- This is safe because M.run is the only writer to these tables within a single orchestrator invocation. -- This is safe because M.run is the only writer to these tables within a single orchestrator invocation.
@@ -3331,6 +3406,7 @@ local function merge_corpus_registries(corpus)
"reg_use_errors", "reg_use_errors",
"tape_chains", "tape_chains",
"atom_bundles", "atom_bundles",
"tape_emits",
}) do }) do
corpus[key] = {} corpus[key] = {}
end end
@@ -3438,6 +3514,9 @@ local function merge_corpus_registries(corpus)
for _, chain in ipairs(scan.tape_chains or {}) do ---@type integer, TapeChain for _, chain in ipairs(scan.tape_chains or {}) do ---@type integer, TapeChain
corpus.tape_chains[#corpus.tape_chains + 1] = chain corpus.tape_chains[#corpus.tape_chains + 1] = chain
end end
for _, emit in ipairs(scan.tape_emits or {}) do ---@type integer, TapeEmit
corpus.tape_emits[#corpus.tape_emits + 1] = emit
end
-- atom_bundles: keyed by typedef name. First-wins per name (like components). -- atom_bundles: keyed by typedef name. First-wins per name (like components).
for name, bundle in pairs(scan.atom_bundles or {}) do ---@type string, AtomBundle for name, bundle in pairs(scan.atom_bundles or {}) do ---@type string, AtomBundle
@@ -3462,6 +3541,30 @@ local function merge_corpus_registries(corpus)
bundle.entries = entries bundle.entries = entries
end end
end end
-- Resolve tb_emit names: atom first, else unique catalog slot → entries[slot].
-- Ambiguous slot or missing entry: leave the raw ident (no A/B later).
for _, emit in ipairs(corpus.tape_emits) do ---@type integer, TapeEmit
if corpus.atoms_by_name[emit.name] == nil then
local slot = emit.slot or emit.name ---@type string
local hit = nil ---@type AtomBundle|nil
local n = 0 ---@type integer
for _, bundle in pairs(corpus.atom_bundles) do ---@type string, AtomBundle
for _, s in ipairs(bundle.slots or {}) do ---@type integer, string
if s == slot then
n = n + 1
hit = bundle
break
end
end
end
local ident = hit and hit.entries and hit.entries[slot] ---@type string|nil
if n == 1 and ident then
emit.name = ident
emit.slot = slot
end
end
end
end end
local SCHEMA_BODY_ERROR = { ---@type table<string, boolean> -- bag: reguse error kind -> true local SCHEMA_BODY_ERROR = { ---@type table<string, boolean> -- bag: reguse error kind -> true
+62
View File
@@ -4016,6 +4016,66 @@ end
-- Each check is one table row and one `check_*` function. -- Each check is one table row and one `check_*` function.
-- This is the plex pattern: the iteration is in ONE place (validate), the variation is in DATA (this table). -- This is the plex pattern: the iteration is in ONE place (validate), the variation is in DATA (this table).
--- @param src SourceFile
--- @param pipe_ctx PassScratch
--- @param findings Finding[]
--- @return nil
local function check_tb_bind_type_match(src, pipe_ctx, findings)
local atoms_by_name = pipe_ctx.atoms_by_name or {} ---@type table<string, AtomEntry>
local info_by = {} ---@type table<string, AtomInfoEntry>
for _, info in ipairs(pipe_ctx.atom_infos_all or {}) do ---@type integer, AtomInfoEntry
info_by[info.atom_name] = info
end
for name, info in pairs(pipe_ctx.info_by_atom or {}) do ---@type string, AtomInfoEntry
info_by[name] = info
end
for _, emit in ipairs((src.scan and src.scan.tape_emits) or {}) do ---@type integer, TapeEmit
if atoms_by_name[emit.name] then
local atom_binds = info_by[emit.name] and info_by[emit.name].binds ---@type string|nil
if emit.binds and atom_binds and emit.binds ~= atom_binds then
findings[#findings + 1] = {
atom = emit.name,
line = emit.line or 0,
check = "tb_bind_type_match",
kind = "error",
msg = string.format("tb_emit '%s' binds %s but atom_bind is %s"
, emit.name, emit.binds, atom_binds),
}
end
end
end
end
--- @param src SourceFile
--- @param pipe_ctx PassScratch
--- @param findings Finding[]
--- @return nil
local function check_tb_bind_required(src, pipe_ctx, findings)
local atoms_by_name = pipe_ctx.atoms_by_name or {} ---@type table<string, AtomEntry>
local info_by = {} ---@type table<string, AtomInfoEntry>
for _, info in ipairs(pipe_ctx.atom_infos_all or {}) do ---@type integer, AtomInfoEntry
info_by[info.atom_name] = info
end
for name, info in pairs(pipe_ctx.info_by_atom or {}) do ---@type string, AtomInfoEntry
info_by[name] = info
end
for _, emit in ipairs((src.scan and src.scan.tape_emits) or {}) do ---@type integer, TapeEmit
if atoms_by_name[emit.name] then
local atom_binds = info_by[emit.name] and info_by[emit.name].binds ---@type string|nil
if atom_binds and emit.binds == nil and (emit.data_words or 0) == 0 then
findings[#findings + 1] = {
atom = emit.name,
line = emit.line or 0,
check = "tb_bind_required",
kind = "error",
msg = string.format("tb_emit '%s' has atom_bind(%s) but no tb_bind_ or tb_data"
, emit.name, atom_binds),
}
end
end
end
end
local CHECK_RULES = { ---@type CheckRule[] local CHECK_RULES = { ---@type CheckRule[]
{ name = "transfer_hazards", per_atom = check_transfer_hazards }, { name = "transfer_hazards", per_atom = check_transfer_hazards },
{ name = "gte_input_latch", per_atom = check_gte_input_latch }, { name = "gte_input_latch", per_atom = check_gte_input_latch },
@@ -4040,6 +4100,8 @@ local CHECK_RULES = { ---@type CheckRule[]
{ name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref }, { name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref },
{ name = "component_self_consistency", per_source = check_component_self_consistency }, { name = "component_self_consistency", per_source = check_component_self_consistency },
{ name = "atom_calls_inferred_traffic", per_atom = check_atom_calls_inferred_traffic }, { name = "atom_calls_inferred_traffic", per_atom = check_atom_calls_inferred_traffic },
{ name = "tb_bind_type_match", per_source = check_tb_bind_type_match },
{ name = "tb_bind_required", per_source = check_tb_bind_required },
} }
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
+4 -4
View File
@@ -78,6 +78,7 @@ local PASS_FLAG_DISPATCH_KEY = "__pass__" ---@type string
--- @field word_counts WordCounts --- @field word_counts WordCounts
--- @field components table<string, Component> --- @field components table<string, Component>
--- @field atom_bundles table<string, AtomBundle>|nil --- @field atom_bundles table<string, AtomBundle>|nil
--- @field tape_emits TapeEmit[]|nil
--- @field collisions CorpusCollision[] --- @field collisions CorpusCollision[]
--- @field resolver SourceResolver --- @field resolver SourceResolver
--- @field component_atom_infos AtomInfoEntry[]|nil --- @field component_atom_infos AtomInfoEntry[]|nil
@@ -374,8 +375,7 @@ COMMON_FLAGS:
--help Show this help and exit --help Show this help and exit
EXIT CODES: EXIT CODES:
0 All requested passes succeeded 0 Ran. Findings print on stderr and in the report; they do not fail the process.
1 Validation errors found
2 Metaprogram internal error 2 Metaprogram internal error
EXAMPLES: EXAMPLES:
@@ -667,6 +667,7 @@ local function build_ctx(args)
word_counts = {}, word_counts = {},
components = {}, components = {},
atom_bundles = {}, atom_bundles = {},
tape_emits = {},
collisions = {}, collisions = {},
resolver = resolution.resolver, resolver = resolution.resolver,
} }
@@ -818,8 +819,7 @@ local function main(argv)
local requested = args.requested_set ---@type string[] local requested = args.requested_set ---@type string[]
local closed = topo_sort(PASSES, requested) ---@type string[] local closed = topo_sort(PASSES, requested) ---@type string[]
local had_errors = dispatch_passes(ctx, closed) ---@type boolean dispatch_passes(ctx, closed)
if had_errors then os.exit(EXIT_VALIDATION_ERRORS) end
end) end)
if not ok then if not ok then