mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
WIP: reviewing lua, some upgrades and fixes along the way.
This commit is contained in:
@@ -330,9 +330,9 @@ MipsAtom_Proc_(aa, {
|
|||||||
mac_store_v3s4(r.res, r.dst_ptr, 0),
|
mac_store_v3s4(r.res, r.dst_ptr, 0),
|
||||||
|
|
||||||
jump_reg(R_AtomJmp), BdSlot_ nop // ac_yield: word 3-4
|
jump_reg(R_AtomJmp), BdSlot_ nop // ac_yield: word 3-4
|
||||||
// mac_yield()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
/* ─── GTE OP cross product (a × b → out) ───
|
/* ─── GTE OP cross product (a × b → out) ───
|
||||||
* Generalized V3_S4 cross product via GTE OP (OuterProduct12 libpsyx convention).
|
* Generalized V3_S4 cross product via GTE OP (OuterProduct12 libpsyx convention).
|
||||||
* The >> 12 shift converts S12.20 → S12.0 OuterProduct12. */
|
* The >> 12 shift converts S12.20 → S12.0 OuterProduct12. */
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#pragma region hello_camera
|
#pragma region hello_camera
|
||||||
|
|
||||||
|
|
||||||
// --- atom: pad_input_cube_rotation (61 words) ---
|
// --- atom: pad_input_cube_rotation (60 words) ---
|
||||||
|
|
||||||
#define _atom_offset_dpad_left_exit_dpad_left 6
|
#define _atom_offset_dpad_left_exit_dpad_left 6
|
||||||
#define _atom_offset_dpad_right_exit_dpad_right 6
|
#define _atom_offset_dpad_right_exit_dpad_right 6
|
||||||
|
|||||||
+60
-33
@@ -210,7 +210,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local prefix = reg_use_param .. "."
|
local prefix = reg_use_param .. "."
|
||||||
if operand:sub(1, #prefix) ~= prefix then return nil end
|
if operand:sub(1, #prefix) ~= prefix then return nil end
|
||||||
local member_path = operand:sub(#prefix + 1)
|
local member_path = operand:sub(#prefix + 1)
|
||||||
local slot = reg_use_schema.alias_to_slot[member_path]
|
local slot = reg_use_schema.alias_to_slot[member_path]
|
||||||
if not slot then return nil, member_path end
|
if not slot then return nil, member_path end
|
||||||
return "reguse:" .. atom_name .. ":" .. slot, nil, slot
|
return "reguse:" .. atom_name .. ":" .. slot, nil, slot
|
||||||
end
|
end
|
||||||
@@ -223,9 +223,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
return ids
|
return ids
|
||||||
end
|
end
|
||||||
|
|
||||||
local function emit_word(encoder, args, line, word_call_text,
|
local function emit_word(encoder, args, line, word_call_text, def_source_now, def_line_now, immediate_call_text, root_call_text_w, sub_map)
|
||||||
def_source_now, def_line_now,
|
|
||||||
immediate_call_text, root_call_text_w, sub_map)
|
|
||||||
local inv_ids = open_invocation_ids_snapshot()
|
local inv_ids = open_invocation_ids_snapshot()
|
||||||
local outermost = inv_ids[1] or 0
|
local outermost = inv_ids[1] or 0
|
||||||
-- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text.
|
-- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text.
|
||||||
@@ -233,7 +231,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- The call that triggered the body expansion we're currently walking.
|
-- The call that triggered the body expansion we're currently walking.
|
||||||
local eff_call_text = immediate_call_text or word_call_text
|
local eff_call_text = immediate_call_text or word_call_text
|
||||||
local eff_root_call_text = root_call_text_w
|
local eff_root_call_text = root_call_text_w
|
||||||
local gpr_keys = nil
|
local gpr_keys = nil
|
||||||
if reg_use_schema or sub_map then
|
if reg_use_schema or sub_map then
|
||||||
gpr_keys = {}
|
gpr_keys = {}
|
||||||
for pos, arg in ipairs(args or {}) do
|
for pos, arg in ipairs(args or {}) do
|
||||||
@@ -268,17 +266,17 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
if not reg_use_schema then
|
if not reg_use_schema then
|
||||||
gpr_keys = nil
|
gpr_keys = nil
|
||||||
end
|
end
|
||||||
local isa = M.instr(encoder)
|
local isa = M.instr(encoder)
|
||||||
local isa_kind = isa and isa.kind or "unknown"
|
local isa_kind = isa and isa.kind or "unknown"
|
||||||
local nop_words = (encoder == "nop" and 1) or (encoder == "nop2" and 2) or 0
|
local nop_words = (encoder == "nop" and 1) or (encoder == "nop2" and 2) or 0
|
||||||
local is_yield = (encoder == "mac_yield" or encoder == "mac_yield_tail")
|
local is_yield = (encoder == "mac_yield" or encoder == "mac_yield_tail")
|
||||||
local gp0_shape = type(encoder) == "string"
|
local gp0_shape = type(encoder) == "string"
|
||||||
and encoder:match("^mac_format_([%w_]+)_color$")
|
and encoder:match("^mac_format_([%w_]+)_color$")
|
||||||
or nil
|
or nil
|
||||||
local is_load = (isa_kind == "load")
|
local is_load = (isa_kind == "load")
|
||||||
local is_branch = (isa_kind == "branch")
|
local is_branch = (isa_kind == "branch")
|
||||||
local is_unconditional_jump = (encoder == "jump" or encoder == "call_addr")
|
local is_unconditional_jump = (encoder == "jump" or encoder == "call_addr")
|
||||||
local is_terminal_jump = (encoder == "jump_reg" or encoder == "call_reg" or encoder == "jump_link")
|
local is_terminal_jump = (encoder == "jump_reg" or encoder == "call_reg" or encoder == "jump_link")
|
||||||
items[#items + 1] = {
|
items[#items + 1] = {
|
||||||
kind = "word",
|
kind = "word",
|
||||||
encoder = encoder,
|
encoder = encoder,
|
||||||
@@ -335,14 +333,18 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- `consuming_encoder` + `consuming_arg_pos` carry the surrounding control-transfer instruction context
|
-- `consuming_encoder` + `consuming_arg_pos` carry the surrounding control-transfer instruction context
|
||||||
-- (e.g. `branch_le_zero` consuming its 3rd argument, or `jump` / `call_addr` consuming their only argument).
|
-- (e.g. `branch_le_zero` consuming its 3rd argument, or `jump` / `call_addr` consuming their only argument).
|
||||||
-- `passes/offsets.lua` reads these to dispatch per-consuming-instruction offset encoding.
|
-- `passes/offsets.lua` reads these to dispatch per-consuming-instruction offset encoding.
|
||||||
-- nil for top-level markers (where the marker is the entire token — no surrounding consuming instruction).
|
-- Offset markers require a real consuming encoder. A lone top-level `atom_offset` is not emitted.
|
||||||
|
-- Label and delay markers may have a nil encoder (they are not consumed as immediates).
|
||||||
|
if kind == "offset" and (consuming_encoder == nil or consuming_encoder == "") then
|
||||||
|
return
|
||||||
|
end
|
||||||
local it = {
|
local it = {
|
||||||
kind = kind,
|
kind = kind,
|
||||||
name = name,
|
name = name,
|
||||||
line = line,
|
line = line,
|
||||||
word_index = word_idx,
|
word_index = word_idx,
|
||||||
invocation_ids = inv_ids,
|
invocation_ids = inv_ids,
|
||||||
outermost_invocation_id = outermost,
|
outermost_invocation_id = outermost,
|
||||||
}
|
}
|
||||||
if target ~= nil then it.target = target end
|
if target ~= nil then it.target = target end
|
||||||
if consuming_encoder then it.consuming_encoder = consuming_encoder end
|
if consuming_encoder then it.consuming_encoder = consuming_encoder end
|
||||||
@@ -450,15 +452,14 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- Commit: label takes 1 arg, offset takes 2.
|
-- Commit: label takes 1 arg, offset takes 2.
|
||||||
-- For embedded markers, propagate the consuming_encoder + the marker's arg position
|
-- For embedded markers, propagate the consuming_encoder + the marker's arg position
|
||||||
-- (1-based) so `passes/offsets.lua` can dispatch per-consuming-instruction offset encoding.
|
-- (1-based) so `passes/offsets.lua` can dispatch per-consuming-instruction offset encoding.
|
||||||
-- Top-level markers (no consuming_encoder) get nil for both — the offsets pass treats
|
-- Offset markers are emitted only when a consuming encoder is present.
|
||||||
-- them as branch-equivalent for backward compatibility.
|
|
||||||
local arg_pos = nil
|
local arg_pos = nil
|
||||||
if consuming_encoder and consuming_paren then
|
if consuming_encoder and consuming_paren then
|
||||||
arg_pos = count_top_level_commas(tok, consuming_paren + 1, pos) + 1
|
arg_pos = count_top_level_commas(tok, consuming_paren + 1, pos) + 1
|
||||||
end
|
end
|
||||||
local args = split_call_args(inner)
|
local args = split_call_args(inner)
|
||||||
if ident == "atom_label" then emit_marker("label", args[1] or "", nil, tok_line, nil, nil, consuming_encoder, arg_pos)
|
if ident == "atom_label" then emit_marker("label", args[1] or "", nil, tok_line, nil, nil, consuming_encoder, arg_pos)
|
||||||
else emit_marker("offset", args[1] or "", args[2] or "", tok_line, nil, nil, consuming_encoder, arg_pos)
|
elseif consuming_encoder then emit_marker("offset", args[1] or "", args[2] or "", tok_line, nil, nil, consuming_encoder, arg_pos)
|
||||||
end
|
end
|
||||||
pos = after_paren
|
pos = after_paren
|
||||||
::continue_loop::
|
::continue_loop::
|
||||||
@@ -545,7 +546,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local wc = ctx_table.word_counts
|
local wc = ctx_table.word_counts
|
||||||
if wc and wc[ident] then return wc[ident] end
|
if wc and wc[ident] then return wc[ident] end
|
||||||
local canon = M.gte_canon(ident)
|
local canon = M.gte_canon(ident)
|
||||||
if canon ~= ident and wc and wc[canon] then return wc[canon] end
|
if canon ~= ident and wc and wc[canon] then return wc[canon] end
|
||||||
warnings[#warnings + 1] = {
|
warnings[#warnings + 1] = {
|
||||||
kind = "uncounted",
|
kind = "uncounted",
|
||||||
line = tok_line,
|
line = tok_line,
|
||||||
@@ -572,6 +573,18 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- "opaque word" emit handles direct encoders + mac_X-without-component.
|
-- "opaque word" emit handles direct encoders + mac_X-without-component.
|
||||||
local function process_token(bt)
|
local function process_token(bt)
|
||||||
local tok = M.trim(bt.tok or "")
|
local tok = M.trim(bt.tok or "")
|
||||||
|
-- Substituted MipsCode args can carry // comments from the call site.
|
||||||
|
while tok ~= "" do
|
||||||
|
if tok:sub(1, 2) == "//" then
|
||||||
|
local nl = tok:find("\n")
|
||||||
|
tok = M.trim(nl and tok:sub(nl + 1) or "")
|
||||||
|
elseif tok:sub(1, 2) == "/*" then
|
||||||
|
local close = tok:find("*/", 3, true)
|
||||||
|
tok = M.trim(close and tok:sub(close + 2) or "")
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
if tok == "" then return end
|
if tok == "" then return end
|
||||||
local ident, after = M.read_ident(tok, 1)
|
local ident, after = M.read_ident(tok, 1)
|
||||||
if not ident then ident = "?" end
|
if not ident then ident = "?" end
|
||||||
@@ -582,10 +595,16 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local rest = tok:sub(after or (#tok + 1))
|
local rest = tok:sub(after or (#tok + 1))
|
||||||
while true do
|
while true do
|
||||||
rest = M.trim(rest)
|
rest = M.trim(rest)
|
||||||
if rest:sub(1, 2) ~= "/*" then break end
|
if rest:sub(1, 2) == "//" then
|
||||||
local close = rest:find("*/", 3, true)
|
local nl = rest:find("\n")
|
||||||
if not close then rest = ""; break end
|
rest = nl and rest:sub(nl + 1) or ""
|
||||||
rest = rest:sub(close + 2)
|
elseif rest:sub(1, 2) == "/*" then
|
||||||
|
local close = rest:find("*/", 3, true)
|
||||||
|
if not close then rest = ""; break end
|
||||||
|
rest = rest:sub(close + 2)
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if rest ~= "" then
|
if rest ~= "" then
|
||||||
process_token({ tok = rest, rel = bt.rel })
|
process_token({ tok = rest, rel = bt.rel })
|
||||||
@@ -601,12 +620,19 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
emit_embedded_markers(tok, tok_line, consuming_encoder_for_markers)
|
emit_embedded_markers(tok, tok_line, consuming_encoder_for_markers)
|
||||||
end
|
end
|
||||||
-- atom_label / atom_offset: terminal markers, no further descent.
|
-- atom_label / atom_offset: terminal markers, no further descent.
|
||||||
-- Top-level markers (the marker IS the entire token) have no consuming instruction;
|
-- A lone top-level atom_label is an anchor and is emitted with no consuming encoder.
|
||||||
-- nil for both `consuming_encoder` and `consuming_arg_pos`.
|
-- A lone top-level atom_offset has no consuming encoder and is not emitted.
|
||||||
-- The offsets pass treats these as branch-equivalent for backward compatibility.
|
if ident == "atom_label" then emit_marker("label", args[1] or "", nil, tok_line); return
|
||||||
-- TODO(Ed): Review this don't want legacy cruft here..
|
elseif ident == "atom_offset" then return
|
||||||
if ident == "atom_label" then emit_marker("label", args[1] or "", nil, tok_line); return
|
end
|
||||||
elseif ident == "atom_offset" then emit_marker("offset", args[1] or "", args[2] or "", tok_line); return
|
-- MipsCode formals (nop_slot1, …): the ident is a sub_map key.
|
||||||
|
-- Re-process the replacement token so load_word(...) becomes a real encoder.
|
||||||
|
if sub_map and type(sub_map[ident]) == "string" and sub_map[ident] ~= ident then
|
||||||
|
local repl = M.trim(sub_map[ident])
|
||||||
|
if repl ~= "" then
|
||||||
|
process_token({ tok = repl, rel = bt.rel })
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if ident:sub(1, 4) == "mac_" then
|
if ident:sub(1, 4) == "mac_" then
|
||||||
local bare = ident:sub(5)
|
local bare = ident:sub(5)
|
||||||
@@ -738,7 +764,8 @@ end
|
|||||||
--- * Metadata-backed N-word tokens (`nop2`, `mask_upper`, ...): N `word` items, all sharing the same encoder + word_count = 1.
|
--- * Metadata-backed N-word tokens (`nop2`, `mask_upper`, ...): N `word` items, all sharing the same encoder + word_count = 1.
|
||||||
--- `nop2` is normalized to encoder `nop` (per the spec).
|
--- `nop2` is normalized to encoder `nop` (per the spec).
|
||||||
--- * `atom_label(F)` markers: one `label` item with `name = "F"`, `word_index = current word_idx`; zero-width (does NOT advance word_idx).
|
--- * `atom_label(F)` markers: one `label` item with `name = "F"`, `word_index = current word_idx`; zero-width (does NOT advance word_idx).
|
||||||
--- * `atom_offset(B, T)` markers: one `offset` item with `name = "B"`, `target = "T"`, `word_index = current word_idx`; zero-width.
|
--- * `atom_offset(B, T)` nested in a consuming instruction: one `offset` item with `name = "B"`, `target = "T"`, `word_index = current word_idx`; zero-width.
|
||||||
|
--- A lone top-level `atom_offset` is not emitted.
|
||||||
--- * Delay markers (`GteDelay_` / `LdSlot_` / `BdSlot_` / `DmaSlot_`): one `delay` item; zero-width. The following encoder is the next token.
|
--- * Delay markers (`GteDelay_` / `LdSlot_` / `BdSlot_` / `DmaSlot_`): one `delay` item; zero-width. The following encoder is the next token.
|
||||||
--- * `mac_X(...)` calls: emit `invoke_begin` (zero-width), recurse into the component body, emit `invoke_end` (zero-width).
|
--- * `mac_X(...)` calls: emit `invoke_begin` (zero-width), recurse into the component body, emit `invoke_end` (zero-width).
|
||||||
--- The component body's words land between the begin/end pair; one invocation record is allocated per call (monotonic ID per atom).
|
--- The component body's words land between the begin/end pair; one invocation record is allocated per call (monotonic ID per atom).
|
||||||
|
|||||||
+56
-41
@@ -13,7 +13,7 @@ local lfs = require("lfs")
|
|||||||
|
|
||||||
-- scripts/elf32.lua contains format-constant tables + the byte-level walker.
|
-- scripts/elf32.lua contains format-constant tables + the byte-level walker.
|
||||||
-- The this file re-exports `read_u32_le` / `read_u16_le` (and the DWARF32 terminator).
|
-- The this file re-exports `read_u32_le` / `read_u16_le` (and the DWARF32 terminator).
|
||||||
-- TODO(Ed): Remove re-export.
|
-- read_u32_le is this module's reader; implementation in elf32.lua.
|
||||||
local E = require("elf32")
|
local E = require("elf32")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -113,7 +113,6 @@ M.MIPS_BYTES_PER_WORD = 0x04
|
|||||||
|
|
||||||
--- spec: DWARF4 spec §7.4 — 32-bit DWARF initial-length terminator
|
--- spec: DWARF4 spec §7.4 — 32-bit DWARF initial-length terminator
|
||||||
M.dw_dwarf32_terminator = E.dw_dwarf32_terminator
|
M.dw_dwarf32_terminator = E.dw_dwarf32_terminator
|
||||||
-- TODO(Ed): Remove re-export.
|
|
||||||
|
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
-- DWARF4 .debug_aranges (per DWARF5 spec §7.4 — Address Range Table)
|
-- DWARF4 .debug_aranges (per DWARF5 spec §7.4 — Address Range Table)
|
||||||
@@ -372,48 +371,70 @@ end
|
|||||||
-- For DW_FORM_strp we return the inline string resolved from `str_buf`.
|
-- For DW_FORM_strp we return the inline string resolved from `str_buf`.
|
||||||
-- For DW_FORM_ref4 we return the absolute CU-relative offset.
|
-- For DW_FORM_ref4 we return the absolute CU-relative offset.
|
||||||
-- The caller decides whether to interpret that as a section offset.
|
-- The caller decides whether to interpret that as a section offset.
|
||||||
local function read_form_value(buf, str_buf, pos, form)
|
local FORM_READERS = {
|
||||||
if form == M.DW_FORM.addr then
|
[M.DW_FORM.addr] = function(buf, _, pos)
|
||||||
return M.read_u32_le(buf, pos), pos + 4
|
return M.read_u32_le(buf, pos), pos + 4
|
||||||
elseif form == M.DW_FORM.string then
|
end,
|
||||||
|
[M.DW_FORM.string] = function(buf, _, pos)
|
||||||
local s = read_c_string_at(buf, pos)
|
local s = read_c_string_at(buf, pos)
|
||||||
return s, pos + #s + 1
|
return s, pos + #s + 1
|
||||||
elseif form == M.DW_FORM.strp then
|
end,
|
||||||
|
[M.DW_FORM.strp] = function(buf, str_buf, pos)
|
||||||
-- DW_FORM_strp: 4-byte offset into .debug_str.
|
-- DW_FORM_strp: 4-byte offset into .debug_str.
|
||||||
local strp_off = M.read_u32_le(buf, pos)
|
local strp_off = M.read_u32_le(buf, pos)
|
||||||
return read_c_string_at(str_buf, strp_off), pos + 4
|
return read_c_string_at(str_buf, strp_off), pos + 4
|
||||||
elseif form == M.DW_FORM.udata then return M.read_uleb128_at(buf, pos)
|
end,
|
||||||
elseif form == M.DW_FORM.data1 then return buf:byte(pos + 1), pos + 1
|
[M.DW_FORM.udata] = function(buf, _, pos)
|
||||||
elseif form == M.DW_FORM.data2 then return M.read_u16_le(buf, pos), pos + 2
|
return M.read_uleb128_at(buf, pos)
|
||||||
elseif form == M.DW_FORM.data4 then return M.read_u32_le(buf, pos), pos + 4
|
end,
|
||||||
elseif form == M.DW_FORM.ref4 then return M.read_u32_le(buf, pos), pos + 4
|
[M.DW_FORM.data1] = function(buf, _, pos)
|
||||||
elseif form == M.DW_FORM.sec_offset then
|
return buf:byte(pos + 1), pos + 1
|
||||||
|
end,
|
||||||
|
[M.DW_FORM.data2] = function(buf, _, pos)
|
||||||
|
return M.read_u16_le(buf, pos), pos + 2
|
||||||
|
end,
|
||||||
|
[M.DW_FORM.data4] = function(buf, _, pos)
|
||||||
|
return M.read_u32_le(buf, pos), pos + 4
|
||||||
|
end,
|
||||||
|
[M.DW_FORM.ref4] = function(buf, _, pos)
|
||||||
|
return M.read_u32_le(buf, pos), pos + 4
|
||||||
|
end,
|
||||||
|
[M.DW_FORM.sec_offset] = function(buf, _, pos)
|
||||||
-- DW_FORM_sec_offset: 4-byte offset (size depends on DWARF version;
|
-- DW_FORM_sec_offset: 4-byte offset (size depends on DWARF version;
|
||||||
-- on DWARF5 32-bit it's always 4 bytes).
|
-- on DWARF5 32-bit it's always 4 bytes).
|
||||||
return M.read_u32_le(buf, pos), pos + 4
|
return M.read_u32_le(buf, pos), pos + 4
|
||||||
elseif form == M.DW_FORM.flag_present then
|
end,
|
||||||
|
[M.DW_FORM.flag_present] = function(_, _, pos)
|
||||||
return 1, pos
|
return 1, pos
|
||||||
elseif form == M.DW_FORM.exprloc then
|
end,
|
||||||
|
[M.DW_FORM.exprloc] = function(buf, _, pos)
|
||||||
-- DW_FORM_exprloc: ULEB byte count + that many bytes of DW_OP_*.
|
-- DW_FORM_exprloc: ULEB byte count + that many bytes of DW_OP_*.
|
||||||
local len, ne = M.read_uleb128_at(buf, pos)
|
local len, ne = M.read_uleb128_at(buf, pos)
|
||||||
if not len then return nil, pos end
|
if not len then return nil, pos end
|
||||||
return nil, ne + len
|
return nil, ne + len
|
||||||
elseif form == DW_FORM_implicit_const then
|
end,
|
||||||
|
[DW_FORM_implicit_const] = function(_, _, pos)
|
||||||
-- The constant is declared in the abbrev; no value bytes in the DIE.
|
-- The constant is declared in the abbrev; no value bytes in the DIE.
|
||||||
return nil, pos
|
return nil, pos
|
||||||
elseif form == M.DW_FORM.ref_sig8 then
|
end,
|
||||||
|
[M.DW_FORM.ref_sig8] = function(buf, _, pos)
|
||||||
-- DW_FORM_ref_sig8 (DWARF5 §7.4.2): An 8-byte value identifying a type by signature.
|
-- DW_FORM_ref_sig8 (DWARF5 §7.4.2): An 8-byte value identifying a type by signature.
|
||||||
-- The low 4 bytes (LE) are the type signature (content hash);
|
-- The low 4 bytes (LE) are the type signature (content hash);
|
||||||
-- The high 4 bytes (LE) are a CU-relative offset into the matching type unit.
|
-- The high 4 bytes (LE) are a CU-relative offset into the matching type unit.
|
||||||
-- Consumers use the low 4 to look up the type unit (see M.find_type_unit_by_signature)
|
-- Consumers use the low 4 to look up the type unit (see M.find_type_unit_by_signature)
|
||||||
-- then the high 4 to resolve the specific type within it.
|
-- then the high 4 to resolve the specific type within it.
|
||||||
-- Return the low 4 as the primary value to preserve the (value, next_pos) shape;
|
-- Return the low 4 as the primary value to preserve the (value, next_pos) shape;
|
||||||
-- the high 4 is exposed via M.read_ref_sig8 (which returns both halves).
|
-- the high 4 is exposed via M.read_ref_sig8 (which returns both halves).
|
||||||
local _, _, next_pos = M.read_ref_sig8(buf, pos)
|
local _, _, next_pos = M.read_ref_sig8(buf, pos)
|
||||||
return M.read_u32_le(buf, pos), next_pos
|
return M.read_u32_le(buf, pos), next_pos
|
||||||
else
|
end,
|
||||||
|
}
|
||||||
|
local function read_form_value(buf, str_buf, pos, form)
|
||||||
|
local r = FORM_READERS[form]
|
||||||
|
if not r then
|
||||||
return nil, pos
|
return nil, pos
|
||||||
end
|
end
|
||||||
|
return r(buf, str_buf, pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Read a `DW_FORM_ref_sig8` value at 0-based offset `pos` from `buf`.
|
--- Read a `DW_FORM_ref_sig8` value at 0-based offset `pos` from `buf`.
|
||||||
@@ -426,9 +447,7 @@ end
|
|||||||
--- @return integer -- low 4 bytes (LE), the type signature
|
--- @return integer -- low 4 bytes (LE), the type signature
|
||||||
--- @return integer -- high 4 bytes (LE), the offset within the matching type unit
|
--- @return integer -- high 4 bytes (LE), the offset within the matching type unit
|
||||||
--- @return integer -- cursor after the 8-byte value
|
--- @return integer -- cursor after the 8-byte value
|
||||||
function M.read_ref_sig8(buf, pos)
|
function M.read_ref_sig8(buf, pos) return M.read_u32_le(buf, pos), M.read_u32_le(buf, pos + 4), pos + 8 end
|
||||||
return M.read_u32_le(buf, pos), M.read_u32_le(buf, pos + 4), pos + 8
|
|
||||||
end
|
|
||||||
|
|
||||||
--- DWARF5 §7.5.6 (Type Entries).
|
--- DWARF5 §7.5.6 (Type Entries).
|
||||||
--- Walk all units in `info` and return the 0-based offset of the first unit whose `DW_AT_type_signature`
|
--- Walk all units in `info` and return the 0-based offset of the first unit whose `DW_AT_type_signature`
|
||||||
@@ -459,23 +478,23 @@ function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi)
|
|||||||
return nil, nil -- malformed
|
return nil, nil -- malformed
|
||||||
end
|
end
|
||||||
-- Per DWARF5 §7.5.6, the type_unit (DW_UT_type = 0x02) body layout is:
|
-- Per DWARF5 §7.5.6, the type_unit (DW_UT_type = 0x02) body layout is:
|
||||||
-- 0: version (2)
|
-- 0: version (2)
|
||||||
-- 2: unit_type (1) -- DW_UT_type = 0x02
|
-- 2: unit_type (1) -- DW_UT_type = 0x02
|
||||||
-- 3: address_size (1)
|
-- 3: address_size (1)
|
||||||
-- 4: debug_abbrev_offset (4)
|
-- 4: debug_abbrev_offset (4)
|
||||||
-- 8: type_signature (8)
|
-- 8: type_signature (8)
|
||||||
-- 16: type_offset (4)
|
-- 16: type_offset (4)
|
||||||
-- 20: <children>
|
-- 20: <children>
|
||||||
if body_end - body_start >= 20 then
|
if body_end - body_start >= 20 then
|
||||||
-- read_ref_sig8 / write_u32_le / etc. are 1-indexed (string:byte);
|
-- read_ref_sig8 / write_u32_le / etc. are 1-indexed (string:byte);
|
||||||
-- pos / body_start / body_end are 0-based wire offsets, so the 1-indexed byte at 0-based wire offset X is string:byte(X + 1).
|
-- pos / body_start / body_end are 0-based wire offsets, so the 1-indexed byte at 0-based wire offset X is string:byte(X + 1).
|
||||||
-- Per DWARF5 §7.5.6, the type_unit body is laid out as:
|
-- Per DWARF5 §7.5.6, the type_unit body is laid out as:
|
||||||
-- byte 0-1: version (2)
|
-- byte 0-1: version (2)
|
||||||
-- byte 2: unit_type (1) -- DW_UT_type = 0x02
|
-- byte 2: unit_type (1) -- DW_UT_type = 0x02
|
||||||
-- byte 3: address_size (1)
|
-- byte 3: address_size (1)
|
||||||
-- byte 4-7: debug_abbrev_offset (4)
|
-- byte 4-7: debug_abbrev_offset (4)
|
||||||
-- byte 8-15: type_signature (8)
|
-- byte 8-15: type_signature (8)
|
||||||
-- byte 16-19: type_offset (4)
|
-- byte 16-19: type_offset (4)
|
||||||
local unit_type = info:byte(body_start + 2 + 1) -- 0-based +2 = unit_type in 1-indexed
|
local unit_type = info:byte(body_start + 2 + 1) -- 0-based +2 = unit_type in 1-indexed
|
||||||
if unit_type == 0x02 then -- DW_UT_type
|
if unit_type == 0x02 then -- DW_UT_type
|
||||||
local sig_lo, sig_hi, _ = M.read_ref_sig8(info, body_start + 8) -- 0-based +8 = type_signature in 1-indexed
|
local sig_lo, sig_hi, _ = M.read_ref_sig8(info, body_start + 8) -- 0-based +8 = type_signature in 1-indexed
|
||||||
@@ -626,14 +645,10 @@ function M.read_nm(elf_path)
|
|||||||
local addrs = {}
|
local addrs = {}
|
||||||
|
|
||||||
-- Existence check first; an empty or missing ELF returns an empty map.
|
-- Existence check first; an empty or missing ELF returns an empty map.
|
||||||
if lfs.attributes(elf_path, "mode") ~= "file" then
|
if lfs.attributes(elf_path, "mode") ~= "file" then return addrs end
|
||||||
return addrs
|
|
||||||
end
|
|
||||||
|
|
||||||
local f = io.open(elf_path, "rb")
|
local f = io.open(elf_path, "rb")
|
||||||
if not f then
|
if not f then return addrs end
|
||||||
return addrs
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build the file adapter for E.*.
|
-- Build the file adapter for E.*.
|
||||||
local file_size
|
local file_size
|
||||||
@@ -775,7 +790,7 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
function M.sleb128(n)
|
function M.sleb128(n)
|
||||||
local bytes = {}
|
local bytes = {}
|
||||||
local more = true
|
local more = true
|
||||||
while more do
|
while more do
|
||||||
local b = n % (LEB_DATA_MASK + 1) -- extract low 7 bits
|
local b = n % (LEB_DATA_MASK + 1) -- extract low 7 bits
|
||||||
n = (n - b) / (LEB_DATA_MASK + 1) -- arithmetic shift right by 7
|
n = (n - b) / (LEB_DATA_MASK + 1) -- arithmetic shift right by 7
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
|||||||
--- @field warnings table[]
|
--- @field warnings table[]
|
||||||
|
|
||||||
--- @class AtomAnnotation
|
--- @class AtomAnnotation
|
||||||
--- @field line integer -- Source line of the atom_info call
|
--- @field atom_name string -- Atom name (scan.atom_infos row)
|
||||||
--- @field macro string -- Macro name (always "atom_info" in the new shape)
|
--- @field info_line integer -- Source line of the atom_info call
|
||||||
--- @field name string -- Atom name
|
--- @field binds string|nil -- Binds_X name if any
|
||||||
--- @field kind string -- Always "info"
|
--- @field reads string[] -- R_* names (read targets)
|
||||||
--- @field binds string|nil -- Binds_X name if any
|
--- @field writes string[] -- R_* names (write targets)
|
||||||
--- @field reads string[] -- R_* names (read targets)
|
--- @field errors string[]|nil -- Parse-time errors from scan_source (atom_info body malformed)
|
||||||
--- @field writes string[] -- R_* names (write targets)
|
|
||||||
--- @field errors string[]|nil -- Parse-time errors from scan_source (atom_info body malformed)
|
|
||||||
|
|
||||||
--- @class DebugSkipMarker -- Sub-shape of scan_source.lua's @class DebugSkipMarker
|
--- @class DebugSkipMarker -- Sub-shape of scan_source.lua's @class DebugSkipMarker
|
||||||
--- @field marker_kind string -- Exact marker ident read from source. Only "atom_dbg_skip" (bare) is positive.
|
--- @field marker_kind string -- Exact marker ident read from source. Only "atom_dbg_skip" (bare) is positive.
|
||||||
@@ -74,7 +72,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
|||||||
--- @field info Finding[]
|
--- @field info Finding[]
|
||||||
|
|
||||||
--- @class PipeCtx
|
--- @class PipeCtx
|
||||||
--- @field atom_index table<string, AtomAnnotation> -- Name -> AtomAnnotation (only kind=="atom")
|
--- @field atom_index table<string, AtomEntry> -- raw_name or name -> scan.atoms row (kind atom/atom_proc)
|
||||||
--- @field binds_index table<string, BindsStruct> -- Name -> BindsStruct
|
--- @field binds_index table<string, BindsStruct> -- Name -> BindsStruct
|
||||||
--- @field annot_counts table<string, integer> -- Name -> annotation count (for unique_annotation check)
|
--- @field annot_counts table<string, integer> -- Name -> annotation count (for unique_annotation check)
|
||||||
--- @field types table<string, RegTypeDefault> -- From scan_source
|
--- @field types table<string, RegTypeDefault> -- From scan_source
|
||||||
@@ -99,14 +97,14 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
|||||||
--- `macro_word_drift` writes errors[] for missing or mismatched metadata and info[] for a match.
|
--- `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.
|
--- Check: Every annotated atom must have a matching MipsAtom_(name) declaration.
|
||||||
--- @param a AtomAnnotation
|
--- @param info AtomAnnotation
|
||||||
--- @param pipe_ctx PipeCtx
|
--- @param pipe_ctx PipeCtx
|
||||||
--- @param findings Findings
|
--- @param findings Findings
|
||||||
local function check_atom_decl_exists(a, pipe_ctx, findings)
|
local function check_atom_decl_exists(info, pipe_ctx, findings)
|
||||||
if not pipe_ctx.atom_index[a.name] then
|
if not pipe_ctx.atom_index[info.atom_name] then
|
||||||
findings.errors[#findings.errors + 1] = {
|
findings.errors[#findings.errors + 1] = {
|
||||||
line = a.line,
|
line = info.info_line,
|
||||||
msg = string.format("annotation for '%s' has no matching MipsAtom_(%s) { ... }", a.name, a.name),
|
msg = string.format("annotation for '%s' has no matching MipsAtom_(%s) { ... }", info.atom_name, info.atom_name),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -128,17 +126,17 @@ end
|
|||||||
|
|
||||||
--- Check: BIND atoms must reference a real Binds_* struct.
|
--- 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.
|
--- 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 a AtomAnnotation
|
--- @param info AtomAnnotation
|
||||||
--- @param pipe_ctx PipeCtx
|
--- @param pipe_ctx PipeCtx
|
||||||
--- @param findings Findings
|
--- @param findings Findings
|
||||||
local function check_binds_struct_exists(a, pipe_ctx, findings)
|
local function check_binds_struct_exists(info, pipe_ctx, findings)
|
||||||
if not a.binds then return end
|
if not info.binds then return end
|
||||||
if pipe_ctx.binds_index[a.binds] then return end
|
if pipe_ctx.binds_index[info.binds] then return end
|
||||||
findings.warnings[#findings.warnings + 1] = {
|
findings.warnings[#findings.warnings + 1] = {
|
||||||
line = a.line,
|
line = info.info_line,
|
||||||
msg = string.format("'%s' binds '%s' but no Struct_(%s) { ... } "
|
msg = string.format("'%s' binds '%s' but no Struct_(%s) { ... } "
|
||||||
.. "declaration found (also flagged as an error by check_abi_handoff in the static-analysis pass)"
|
.. "declaration found (also flagged as an error by check_abi_handoff in the static-analysis pass)"
|
||||||
, a.name, a.binds, a.binds),
|
, info.atom_name, info.binds, info.binds),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -315,7 +313,7 @@ end
|
|||||||
--- 6. unsupported target_kind -> marker precedes an unrelated declaration
|
--- 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.
|
--- Valid markers stamp `debug_skip` on whole-atom, bare-component, and proc-component declaration records in scan_source.lua.
|
||||||
--- @param marker DebugSkipMarker
|
--- @param marker DebugSkipMarker
|
||||||
--- @param _pipe_ctx PipeCtx -- Unused; kept for consistency with per_annot // TODO(Ed): Remove?
|
--- @param _pipe_ctx PipeCtx -- Unused; kept for consistency with per_annot
|
||||||
--- @param findings Findings
|
--- @param findings Findings
|
||||||
local function check_skip_marker(marker, _pipe_ctx, findings)
|
local function check_skip_marker(marker, _pipe_ctx, findings)
|
||||||
local kind = marker.marker_kind
|
local kind = marker.marker_kind
|
||||||
@@ -400,7 +398,7 @@ end
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
--
|
--
|
||||||
-- Each rule entry picks one of four "shapes" of dispatch:
|
-- Each rule entry picks one of four "shapes" of dispatch:
|
||||||
-- per_annot(annot, pipe_ctx, findings) -- runs once per AtomAnnotation
|
-- per_annot(info, pipe_ctx, findings) -- runs once per scan.atom_infos row
|
||||||
-- post(pipe_ctx, findings) -- runs once after all per_annot calls complete (full-corpus aggregation)
|
-- post(pipe_ctx, findings) -- runs once after all per_annot calls complete (full-corpus aggregation)
|
||||||
-- per_macro(macro, wc, findings) -- runs once per TAPE_WORDS / _Pragma macro declaration
|
-- per_macro(macro, wc, findings) -- runs once per TAPE_WORDS / _Pragma macro declaration
|
||||||
-- per_skip_marker(marker, pipe_ctx, findings) -- runs once per src.scan.debug_skip_markers entry
|
-- per_skip_marker(marker, pipe_ctx, findings) -- runs once per src.scan.debug_skip_markers entry
|
||||||
@@ -430,7 +428,7 @@ local CHECK_RULES = {
|
|||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @return PipeCtx
|
--- @return PipeCtx
|
||||||
local function build_corpus_pipe_ctx(ctx)
|
local function build_corpus_pipe_ctx(ctx)
|
||||||
local view = duffle.corpus_view(ctx)
|
local view = duffle.corpus_view(ctx)
|
||||||
local annot_counts = {}
|
local annot_counts = {}
|
||||||
for _, info in ipairs(view.atom_infos) do
|
for _, info in ipairs(view.atom_infos) do
|
||||||
if info and info.atom_name then
|
if info and info.atom_name then
|
||||||
@@ -452,29 +450,6 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
corpus_pipe_ctx = corpus_pipe_ctx or build_corpus_pipe_ctx(ctx)
|
corpus_pipe_ctx = corpus_pipe_ctx or build_corpus_pipe_ctx(ctx)
|
||||||
local scan = src.scan
|
local scan = src.scan
|
||||||
|
|
||||||
-- Project the pre-scanned atoms to the AtomEntry shape this pass needs.
|
|
||||||
local atoms = {}
|
|
||||||
for _, a in ipairs(scan.atoms) do
|
|
||||||
if a.kind == "atom" or a.kind == "atom_proc" then
|
|
||||||
atoms[#atoms + 1] = { line = a.line, name = a.raw_name or a.name }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Project the pre-scanned atom_infos to AtomAnnotation shape.
|
|
||||||
local annots = {}
|
|
||||||
for _, info in ipairs(scan.atom_infos) do
|
|
||||||
annots[#annots + 1] = {
|
|
||||||
line = info.info_line,
|
|
||||||
macro = "atom_info",
|
|
||||||
name = info.atom_name,
|
|
||||||
kind = "info",
|
|
||||||
binds = info.binds,
|
|
||||||
reads = info.reads or {},
|
|
||||||
writes = info.writes or {},
|
|
||||||
errors = info.errors,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build a per-source pipe_ctx: shared lookups come from `corpus_pipe_ctx`, while declarations, bodies, types, views, defaults, and occurrences come from `src.scan`.
|
-- Build a per-source pipe_ctx: shared lookups come from `corpus_pipe_ctx`, while declarations, bodies, types, views, defaults, and occurrences come from `src.scan`.
|
||||||
local seen_defaults = {}; for reg, _ in pairs (scan.types or {}) do seen_defaults[reg] = (seen_defaults[reg] or 0) + 1 end
|
local seen_defaults = {}; for reg, _ in pairs (scan.types or {}) do seen_defaults[reg] = (seen_defaults[reg] or 0) + 1 end
|
||||||
local atom_infos_list = {}; for _, ai in ipairs(scan.atom_infos or {}) do atom_infos_list[#atom_infos_list + 1] = ai end
|
local atom_infos_list = {}; for _, ai in ipairs(scan.atom_infos or {}) do atom_infos_list[#atom_infos_list + 1] = ai end
|
||||||
@@ -493,7 +468,13 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
register_alias_registry = corpus_pipe_ctx.register_alias_registry,
|
register_alias_registry = corpus_pipe_ctx.register_alias_registry,
|
||||||
type_name_registry = corpus_pipe_ctx.type_name_registry,
|
type_name_registry = corpus_pipe_ctx.type_name_registry,
|
||||||
}
|
}
|
||||||
for _, a in ipairs(atoms) do pipe_ctx.atom_index [a.name] = a end
|
local atoms = {}
|
||||||
|
for _, a in ipairs(scan.atoms) do
|
||||||
|
if a.kind == "atom" or a.kind == "atom_proc" then
|
||||||
|
atoms[#atoms + 1] = a
|
||||||
|
pipe_ctx.atom_index[a.raw_name or a.name] = a
|
||||||
|
end
|
||||||
|
end
|
||||||
for _, b in ipairs(scan.binds) do pipe_ctx.binds_index[b.name] = b end
|
for _, b in ipairs(scan.binds) do pipe_ctx.binds_index[b.name] = b end
|
||||||
|
|
||||||
-- Findings live in a single struct with three lists (errors / warnings / info).
|
-- Findings live in a single struct with three lists (errors / warnings / info).
|
||||||
@@ -501,20 +482,20 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
local findings = { errors = {}, warnings = {}, info = {} }
|
local findings = { errors = {}, warnings = {}, info = {} }
|
||||||
|
|
||||||
-- Lift parse-time errors already recorded in scan_source's atom_info payload into this pass's findings list.
|
-- Lift parse-time errors already recorded in scan_source's atom_info payload into this pass's findings list.
|
||||||
for _, a in ipairs(annots) do
|
for _, info in ipairs(scan.atom_infos) do
|
||||||
if a.errors then
|
if info.errors then
|
||||||
for _, msg in ipairs(a.errors) do
|
for _, msg in ipairs(info.errors) do
|
||||||
findings.errors[#findings.errors + 1] = {
|
findings.errors[#findings.errors + 1] = {
|
||||||
line = a.line,
|
line = info.info_line,
|
||||||
msg = string.format("'%s': %s", a.name, msg),
|
msg = string.format("'%s': %s", info.atom_name, msg),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- THE per-annotation pipeline. ONE loop. CHECK_RULES dispatches per_annot rules.
|
-- THE per-annotation pipeline. ONE loop. CHECK_RULES dispatches per_annot rules.
|
||||||
for _, a in ipairs(annots) do
|
for _, info in ipairs(scan.atom_infos) do
|
||||||
duffle.run_check_rules(CHECK_RULES, "per_annot", a, pipe_ctx, findings)
|
duffle.run_check_rules(CHECK_RULES, "per_annot", info, pipe_ctx, findings)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Post-loop rules (one-shot checks that need full-corpus aggregation in pipe_ctx).
|
-- Post-loop rules (one-shot checks that need full-corpus aggregation in pipe_ctx).
|
||||||
@@ -541,12 +522,12 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
findings.info[#findings.info + 1] = {
|
findings.info[#findings.info + 1] = {
|
||||||
line = 0,
|
line = 0,
|
||||||
msg = string.format("scanned: %d atom(s), %d annotation(s), %d macro-word-decl(s), %d binds struct(s)"
|
msg = string.format("scanned: %d atom(s), %d annotation(s), %d macro-word-decl(s), %d binds struct(s)"
|
||||||
, #atoms, #annots, #scan.macros, #scan.binds),
|
, #atoms, #scan.atom_infos, #scan.macros, #scan.binds),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
atoms = atoms,
|
atoms = atoms,
|
||||||
annots = annots,
|
annots = scan.atom_infos,
|
||||||
macros = scan.macros,
|
macros = scan.macros,
|
||||||
binds = scan.binds,
|
binds = scan.binds,
|
||||||
errors = findings.errors,
|
errors = findings.errors,
|
||||||
@@ -576,7 +557,7 @@ function M.run(ctx)
|
|||||||
-- Build the shared pipe_ctx once for this run; every validate() call sees the same cross-source registries.
|
-- 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.
|
-- The corpus owns the canonical cross-source registries; per-source scans retain body / declaration ownership.
|
||||||
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx)
|
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx)
|
||||||
local corpus = ctx.shared.corpus
|
local corpus = ctx.shared.corpus
|
||||||
|
|
||||||
-- Group `corpus.sources_by_dir` by module, validate every source in each bucket, and emit one errors.h per directory.
|
-- Group `corpus.sources_by_dir` by module, validate every source in each bucket, and emit one errors.h per directory.
|
||||||
local by_dir = (corpus and corpus.sources_by_dir) or {}
|
local by_dir = (corpus and corpus.sources_by_dir) or {}
|
||||||
|
|||||||
@@ -526,13 +526,11 @@ function M.render_atom_provenance(atom, wc, rel_path)
|
|||||||
local lines = {}
|
local lines = {}
|
||||||
lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total)
|
lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total)
|
||||||
for _, entry in ipairs(entries) do
|
for _, entry in ipairs(entries) do
|
||||||
local inv = entry.invocation
|
local inv = entry.invocation
|
||||||
local macro_count = inv and wc and wc["mac_" .. inv.component_name]
|
local macro_count = inv and wc and wc["mac_" .. inv.component_name]
|
||||||
if inv and macro_count ~= nil then
|
if inv and macro_count ~= nil then
|
||||||
lines[#lines + 1] = string.format(
|
lines[#lines + 1] = string.format('WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d'
|
||||||
'WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d',
|
, entry.pos, rel_path, entry.line, inv.component_name, inv.def_path or "", inv.def_line or 0, entry.body_line)
|
||||||
entry.pos, rel_path, entry.line, inv.component_name,
|
|
||||||
inv.def_path or "", inv.def_line or 0, entry.body_line)
|
|
||||||
else
|
else
|
||||||
lines[#lines + 1] = string.format(
|
lines[#lines + 1] = string.format(
|
||||||
"WORD %d CALL %s:%d RAW", entry.pos, rel_path, entry.line)
|
"WORD %d CALL %s:%d RAW", entry.pos, rel_path, entry.line)
|
||||||
|
|||||||
+26
-32
@@ -9,8 +9,7 @@
|
|||||||
--- These GPRs are unavailable to EVERY atom's source pool.
|
--- These GPRs are unavailable to EVERY atom's source pool.
|
||||||
--- Carriers are preserved across atoms by context discipline and must never be reallocated.
|
--- Carriers are preserved across atoms by context discipline and must never be reallocated.
|
||||||
--- Per-atom body parsing also catches alias references (R_<Alias>) and hardcoded R_Tn references,
|
--- Per-atom body parsing also catches alias references (R_<Alias>) and hardcoded R_Tn references,
|
||||||
--- so the user can write either `R_T4` or `R_ResolveScratch` in an atom body and the pass will
|
--- so the user can write either `R_T4` or `R_ResolveScratch` in an atom body and the pass will exclude R_T4 from that atom's pool.
|
||||||
--- exclude R_T4 from that atom's pool.
|
|
||||||
---
|
---
|
||||||
--- Conflict detection: If the user hardcodes `R_Tn` in an atom body that shares a phase with an auto-reg that picked `R_Tn`,
|
--- Conflict detection: If the user hardcodes `R_Tn` in an atom body that shares a phase with an auto-reg that picked `R_Tn`,
|
||||||
--- emit `phase_register_clash` as an info finding (no build stop).
|
--- emit `phase_register_clash` as an info finding (no build stop).
|
||||||
@@ -27,30 +26,26 @@
|
|||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
--- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY
|
--- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
--- ════════════════════════════════════════════════════════════════════════════
|
||||||
--
|
---
|
||||||
-- The auto-reg pass picks physical GPRs for `atom_auto_reg(...)` / `phase_auto_reg(...)` markers.
|
--- The auto-reg pass picks physical GPRs for `atom_auto_reg(...)` / `phase_auto_reg(...)` markers.
|
||||||
-- The 24-register pool covers R2-R25 (the user/atom allocatable surface):
|
--- The 24-register pool covers R2-R25 (the user/atom allocatable surface):
|
||||||
-- R_T0..R_T7, R_V0..R_V1, R_A0..A3, R_S0..S7, R_T8..T9.
|
--- R_T0..R_T7, R_V0..R_V1, R_A0..A3, R_S0..S7, R_T8..T9.
|
||||||
-- Excluded (and never added to the pool):
|
--- Excluded (and never added to the pool):
|
||||||
-- R_0 (code 0) — hardwired zero. Cannot be written.
|
--- R_0 (code 0) — Hardwired zero. Cannot be written.
|
||||||
-- R_AT (code 1) — assembler temporary. Reserved by the MIPS O32 ABI.
|
--- R_AT (code 1) — Assembler temporary. Reserved by the MIPS O32 ABI.
|
||||||
-- R_A0..A3 — explicitly omitted above even though their integer codes
|
--- R_A0..A3 — Explicitly omitted above even though their integer codes map to POOL entries;
|
||||||
-- map to POOL entries; the pool-construction loop below
|
--- the pool-construction loop below only references the POOL string literals, never the integer codes, so they are NOT auto-allocated by default.
|
||||||
-- only references the POOL string literals, never the
|
--- (A0-A3 become available when the user adds them to POOL or hardcodes an R_A0 reference in the atom body.)
|
||||||
-- integer codes, so they are NOT auto-allocated by default.
|
--- R_K0/K1 (codes 26-27) — Kernel / interrupt handler reserves. Never touched by user code.
|
||||||
-- (A0-A3 become available when the user adds them to
|
--- 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.
|
||||||
-- POOL or hardcodes an R_A0 reference in the atom body.)
|
|
||||||
-- 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 = {
|
local POOL = {
|
||||||
|
"R_V0", "R_V1",
|
||||||
"R_T0", "R_T1", "R_T2", "R_T3",
|
"R_T0", "R_T1", "R_T2", "R_T3",
|
||||||
"R_T4", "R_T5", "R_T6", "R_T7",
|
"R_T4", "R_T5", "R_T6", "R_T7",
|
||||||
"R_V0", "R_V1",
|
|
||||||
"R_A0", "R_A1", "R_A2", "R_A3",
|
"R_A0", "R_A1", "R_A2", "R_A3",
|
||||||
"R_S0", "R_S1", "R_S2", "R_S3",
|
"R_S0", "R_S1", "R_S2", "R_S3",
|
||||||
"R_S4", "R_S5", "R_S6", "R_S7",
|
"R_S4", "R_S5", "R_S6", "R_S7",
|
||||||
@@ -131,13 +126,13 @@ local function build_user_pins(corpus)
|
|||||||
return user_pinned, alias_to_gpr
|
return user_pinned, alias_to_gpr
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Find every physical GPR referenced in the atom body, via EITHER:
|
--- Find every physical GPR referenced in the atom body, via EITHER:
|
||||||
-- (a) A hardcoded physical GPR ident (R_T\d+|R_V\d+|R_A\d+|R_S\d+) — the existing regex;
|
--- (a) A hardcoded physical GPR ident (R_T\d+|R_V\d+|R_A\d+|R_S\d+) — the existing regex;
|
||||||
-- (b) An alias ident (R_<Alias>) resolved via alias_to_gpr back to its physical GPR ident.
|
--- (b) An alias ident (R_<Alias>) resolved via alias_to_gpr back to its physical GPR ident.
|
||||||
-- Returns { [physical_gpr_ident] = count }. Clash-detection and source-pool-exclusion logic
|
--- Returns { [physical_gpr_ident] = count }.
|
||||||
-- only needs the presence of each GPR (boolean test), but keeping count preserves the
|
--- Clash-detection and source-pool-exclusion logic only needs the presence of each GPR (boolean test),
|
||||||
-- original find_hardcoded_rn shape so callers can switch without churn.
|
--- but keeping count preserves the original find_hardcoded_rn shape so callers can switch without churn.
|
||||||
-- The alias pattern is sorted lexicographically to keep the regex deterministic.
|
--- The alias pattern is sorted lexicographically to keep the regex deterministic.
|
||||||
local function find_used_gprs(body_text, alias_to_gpr)
|
local function find_used_gprs(body_text, alias_to_gpr)
|
||||||
local found = {}
|
local found = {}
|
||||||
-- (a) Hardcoded physical GPRs (R_T0..R_T7, R_V0..R_V1, R_A0..R_A3, R_S0..R_S7).
|
-- (a) Hardcoded physical GPRs (R_T0..R_T7, R_V0..R_V1, R_A0..R_A3, R_S0..R_S7).
|
||||||
@@ -265,8 +260,7 @@ function M.run(ctx)
|
|||||||
end
|
end
|
||||||
local source_pool = {}
|
local source_pool = {}
|
||||||
for _, gpr in ipairs(POOL) do
|
for _, gpr in ipairs(POOL) do
|
||||||
-- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers
|
-- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers declared via atom_reg + _Code defs, preserved across atoms globally).
|
||||||
-- declared via atom_reg + _Code defs, preserved across atoms globally).
|
|
||||||
if not used[gpr] and not user_pinned[gpr] then
|
if not used[gpr] and not user_pinned[gpr] then
|
||||||
source_pool[#source_pool + 1] = gpr
|
source_pool[#source_pool + 1] = gpr
|
||||||
end
|
end
|
||||||
@@ -297,7 +291,7 @@ function M.run(ctx)
|
|||||||
-- For each resolved (scope, sym) -> R_Tn mapping, scan the atom body source for used GPRs.
|
-- For each resolved (scope, sym) -> R_Tn mapping, scan the atom body source for used GPRs.
|
||||||
for atom_scope, decls in pairs(atom_allocations) do
|
for atom_scope, decls in pairs(atom_allocations) do
|
||||||
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope]
|
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope]
|
||||||
if atom and atom.body then
|
if atom and atom.body then
|
||||||
local used_in_body = find_used_gprs(atom.body, alias_to_gpr)
|
local used_in_body = find_used_gprs(atom.body, alias_to_gpr)
|
||||||
for sym, allocated_gpr in pairs(decls) do
|
for sym, allocated_gpr in pairs(decls) do
|
||||||
if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then
|
if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then
|
||||||
|
|||||||
+72
-107
@@ -100,9 +100,8 @@ local M = {}
|
|||||||
--- Returns the args string (e.g., `"U4 off, U4 code, U1 r, U1 g, U1 b"`) or nil if no function declaration is found.
|
--- Returns the args string (e.g., `"U4 off, U4 code, U1 r, U1 g, U1 b"`) or nil if no function declaration is found.
|
||||||
---
|
---
|
||||||
--- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name
|
--- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name
|
||||||
--- and the args both come from the preceding `FI_ Slice_MipsCode ac_X(args)`
|
--- and the args both come from the preceding `FI_ Slice_MipsCode ac_X(args)` declaration.
|
||||||
--- declaration. The shared `duffle.find_function_decl_for` helper does the
|
--- The shared `duffle.find_function_decl_for` helper does the backward walk; this function returns just the args.
|
||||||
--- backward walk; this function returns just the args.
|
|
||||||
---
|
---
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param name string (retained for signature stability; unused — the walk derives the name)
|
--- @param name string (retained for signature stability; unused — the walk derives the name)
|
||||||
@@ -336,11 +335,11 @@ end
|
|||||||
--- @param tok string
|
--- @param tok string
|
||||||
--- @return string
|
--- @return string
|
||||||
local function strip_leading_delay_marker(tok)
|
local function strip_leading_delay_marker(tok)
|
||||||
local ident = duffle.read_ident(tok, 1)
|
local ident = duffle.read_ident(tok, 1)
|
||||||
if not ident or not duffle.DELAY_MARKERS[ident] then return tok end
|
if not ident or not duffle.DELAY_MARKERS[ident] then return tok end
|
||||||
local rest = tok:sub(#ident + 1):match("^%s*(.*)$") or ""
|
local rest = tok:sub(#ident + 1):match("^%s*(.*)$") or ""
|
||||||
while rest:sub(1, 2) == "/*" do
|
while rest:sub(1, 2) == "/*" do
|
||||||
local close = rest:find("*/", 3, true)
|
local close = rest:find("*/", 3, true)
|
||||||
if not close then return "" end
|
if not close then return "" end
|
||||||
rest = rest:sub(close + 2):match("^%s*(.*)$") or ""
|
rest = rest:sub(close + 2):match("^%s*(.*)$") or ""
|
||||||
end
|
end
|
||||||
@@ -428,115 +427,87 @@ end
|
|||||||
-- Always walk the original `MipsAtomComp_` body via `cc.body_tokens`.
|
-- Always walk the original `MipsAtomComp_` body via `cc.body_tokens`.
|
||||||
-- ═══════════════════════════════════════════
|
-- ═══════════════════════════════════════════
|
||||||
|
|
||||||
--- (internal) Recursive cycle-cost derivation. Sum `latency[ident]` per emitted instruction in the component body,
|
--- (internal) One walk of a component body that fills both `cycle_cost` and `gp0_contrib`.
|
||||||
--- recursing through nested `mac_*` calls (so `mac_format_g4_color`'s cost = 4 × `mac_pack_color_word`'s cost).
|
--- Cycle: sum `isa.cycles` / `gte.cycles` / `latency[ident]` / 1 per leaf, recurse `mac_*`.
|
||||||
--- Special rule: `mac_yield`'s cost = 0 (per `lottes_tape.h:125-130` "the runtime cost lands in the next atom's prologue").
|
--- `mac_yield` cycle_cost is 0 (runtime cost lands in the next atom's prologue); its gp0 still comes from the token walk.
|
||||||
|
--- GP0: count `gte_sw` and `store_word` / `store_half` / `store_byte` that target `R_PrimCursor` / `O_(Poly_` / `r_prim_cursor` / `r_primitive_cursor` / `r_base`.
|
||||||
|
--- `insert_ot_tag*` gp0_contrib is 0; cycle still comes from the body walk.
|
||||||
|
--- Missing component: cycle 1, gp0 0.
|
||||||
--- @param name string -- component bare name (e.g. "yield", "pack_color_word")
|
--- @param name string -- component bare name (e.g. "yield", "pack_color_word")
|
||||||
--- @param comp_by_name table<string, Component>
|
--- @param comp_by_name table<string, Component>
|
||||||
--- @param latency table<string, integer>
|
--- @param latency table<string, integer>
|
||||||
--- @param cache table<string, integer> -- shared memoization; `-1` sentinel detects cycles
|
--- @param cache table<string, {cycle_cost=integer, gp0_contrib=integer}>
|
||||||
--- @return integer
|
--- @return {cycle_cost=integer, gp0_contrib=integer}
|
||||||
local function cycle_cost_rec(name, comp_by_name, latency, cache)
|
local function component_meta_rec(name, comp_by_name, latency, cache)
|
||||||
if cache[name] ~= nil then return cache[name] end
|
if cache[name] ~= nil then return cache[name] end
|
||||||
cache[name] = -1
|
cache[name] = { cycle_cost = -1, gp0_contrib = -1 }
|
||||||
local cc = comp_by_name[name]
|
local cc = comp_by_name[name]
|
||||||
local n
|
local cycle_cost
|
||||||
|
local gp0_contrib
|
||||||
if cc then
|
if cc then
|
||||||
if name == "yield" then
|
local skip_cycle = (name == "yield")
|
||||||
-- mac_yield's cost is 0 by convention (the runtime cost lands in the next atom's prologue).
|
local skip_gp0 = name:match("^insert_ot_tag") ~= nil
|
||||||
n = 0
|
cycle_cost = 0
|
||||||
else
|
gp0_contrib = 0
|
||||||
n = 0
|
if not skip_cycle or not skip_gp0 then
|
||||||
local tokens = cc.body_tokens
|
local tokens = cc.body_tokens
|
||||||
for _, t in ipairs(tokens) do
|
for _, t in ipairs(tokens) do
|
||||||
local trimmed = t.tok
|
local trimmed = t.tok
|
||||||
if trimmed ~= "" then
|
if trimmed ~= "" then
|
||||||
local ident = duffle.read_ident(trimmed, 1)
|
local ident = duffle.read_ident(trimmed, 1)
|
||||||
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
||||||
-- Nested `mac_X(...)` call: recurse.
|
|
||||||
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
||||||
n = n + cycle_cost_rec(nested, comp_by_name, latency, cache)
|
local nested_meta = component_meta_rec(nested, comp_by_name, latency, cache)
|
||||||
|
if not skip_cycle then
|
||||||
|
cycle_cost = cycle_cost + nested_meta.cycle_cost
|
||||||
|
end
|
||||||
|
if not skip_gp0 then
|
||||||
|
gp0_contrib = gp0_contrib + nested_meta.gp0_contrib
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Leaf instruction or pseudo-macro.
|
if not skip_cycle then
|
||||||
local isa = duffle.instr(ident)
|
local isa = duffle.instr(ident)
|
||||||
local gte = duffle.gte(ident)
|
local gte = duffle.gte(ident)
|
||||||
n = n + ((isa and isa.cycles) or (gte and gte.cycles) or latency[ident] or 1)
|
cycle_cost = cycle_cost + ((isa and isa.cycles) or (gte and gte.cycles) or latency[ident] or 1)
|
||||||
|
end
|
||||||
|
if not skip_gp0 then
|
||||||
|
if ident == "gte_sw" then
|
||||||
|
gp0_contrib = gp0_contrib + 1
|
||||||
|
elseif ident == "store_word" or ident == "store_half" or ident == "store_byte" then
|
||||||
|
if trimmed:find("R_PrimCursor", 1, true)
|
||||||
|
or trimmed:find("O_(Poly_", 1, true)
|
||||||
|
or trimmed:find("r_prim_cursor", 1, true)
|
||||||
|
or trimmed:find("r_primitive_cursor", 1, true)
|
||||||
|
or trimmed:find("r_base", 1, true)
|
||||||
|
then
|
||||||
|
gp0_contrib = gp0_contrib + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
n = 1
|
cycle_cost = 1
|
||||||
|
gp0_contrib = 0
|
||||||
end
|
end
|
||||||
cache[name] = n
|
cache[name] = { cycle_cost = cycle_cost, gp0_contrib = gp0_contrib }
|
||||||
return n
|
return cache[name]
|
||||||
end
|
|
||||||
|
|
||||||
--- (internal) Recursive GP0 prim-buffer contribution. Count `store_word` / `store_half` / `store_byte`
|
|
||||||
--- calls in the component body that target `R_PrimCursor` (these are the RAM-side prim-buffer words the macro contributes), recursing through nested `mac_*` calls.
|
|
||||||
--- Only `R_PrimCursor`-targeting stores count. Stores targeting other registers (e.g. `R_OtBase`, heap pointers) are not prim-buffer contributions.
|
|
||||||
--- @param name string
|
|
||||||
--- @param comp_by_name table<string, Component>
|
|
||||||
--- @param cache table<string, integer>
|
|
||||||
--- @return integer
|
|
||||||
local function gp0_contrib_rec(name, comp_by_name, cache)
|
|
||||||
if name:match("^insert_ot_tag") then
|
|
||||||
cache[name] = 0
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if cache[name] ~= nil then return cache[name] end
|
|
||||||
cache[name] = -1
|
|
||||||
local cc = comp_by_name[name]
|
|
||||||
local n
|
|
||||||
if cc then
|
|
||||||
n = 0
|
|
||||||
local tokens = cc.body_tokens
|
|
||||||
for _, t in ipairs(tokens) do
|
|
||||||
local trimmed = t.tok
|
|
||||||
if trimmed ~= "" then
|
|
||||||
local ident = duffle.read_ident(trimmed, 1)
|
|
||||||
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
|
||||||
-- Nested `mac_X(...)` call: recurse.
|
|
||||||
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
|
||||||
n = n + gp0_contrib_rec(nested, comp_by_name, cache)
|
|
||||||
elseif ident == "gte_sw" then
|
|
||||||
n = n + 1
|
|
||||||
elseif ident == "store_word" or ident == "store_half" or ident == "store_byte" then
|
|
||||||
if trimmed:find("R_PrimCursor", 1, true)
|
|
||||||
or trimmed:find("O_(Poly_", 1, true)
|
|
||||||
or trimmed:find("r_prim_cursor", 1, true)
|
|
||||||
or trimmed:find("r_primitive_cursor", 1, true)
|
|
||||||
or trimmed:find("r_base", 1, true)
|
|
||||||
then
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
n = 0
|
|
||||||
end
|
|
||||||
cache[name] = n
|
|
||||||
return n
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Compute `cycle_cost` + `gp0_contrib` for every component in `components` in a single pass.
|
--- Compute `cycle_cost` + `gp0_contrib` for every component in `components` in a single pass.
|
||||||
--- Memoization cache is built ONCE (per source) and shared across both helpers so that
|
--- One memoization cache; a nested `mac_Y` inside a `mac_X` body computes both fields once.
|
||||||
--- a nested `mac_Y` reference inside a `mac_X` body computes its values once.
|
|
||||||
--- @param components Component[]
|
--- @param components Component[]
|
||||||
--- @param latency table<string, integer>
|
--- @param latency table<string, integer>
|
||||||
--- @return table<string, {cycle_cost=integer, gp0_contrib=integer}>
|
--- @return table<string, {cycle_cost=integer, gp0_contrib=integer}>
|
||||||
local function compute_components_metadata(components, latency)
|
local function compute_components_metadata(components, latency)
|
||||||
local comp_by_name = {}
|
local comp_by_name = {}
|
||||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end
|
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end
|
||||||
local cc_cache = {}
|
local cache = {}
|
||||||
local gc_cache = {}
|
local out = {}
|
||||||
local out = {}
|
|
||||||
for _, c in ipairs(components) do
|
for _, c in ipairs(components) do
|
||||||
out[c.name] = {
|
out[c.name] = component_meta_rec(c.name, comp_by_name, latency, cache)
|
||||||
cycle_cost = cycle_cost_rec(c.name, comp_by_name, latency, cc_cache),
|
|
||||||
gp0_contrib = gp0_contrib_rec(c.name, comp_by_name, gc_cache),
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
return out
|
return out
|
||||||
end
|
end
|
||||||
@@ -589,29 +560,23 @@ local function strip_trailing_continuation(lines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Classify a token as a "pure delay marker token" (a delay-marker identifier
|
--- Classify a token as a "pure delay marker token" (a delay-marker identifier with no following instruction — only whitespace and/or block comments).
|
||||||
--- with no following instruction — only whitespace and/or block comments).
|
|
||||||
--- Examples that match:
|
--- Examples that match:
|
||||||
--- * `GteDelay_` → marker alone
|
--- * `GteDelay_` → marker alone
|
||||||
--- * `GteDelay_ /* RT diagonal: D1 = a.x... */` → marker + block comment
|
--- * `GteDelay_ /* RT diagonal: D1 = a.x... */` → marker + block comment
|
||||||
--- * `GteDelay_ /* RT diagonal: ... */\n\t` → marker + comment + trailing whitespace
|
--- * `GteDelay_ /* RT diagonal: ... */\n\t` → marker + comment + trailing whitespace
|
||||||
--- Examples that DO NOT match (these contain a real instruction after the marker
|
--- Examples that DO NOT match (these contain a real instruction after the marker and must be preserved verbatim so the instruction still gets emitted):
|
||||||
--- and must be preserved verbatim so the instruction still gets emitted):
|
|
||||||
--- * `GteDelay_ nop2`
|
--- * `GteDelay_ nop2`
|
||||||
--- * `GteDelay_ add_si(r.dst_ptr, r.scratch, dst_offset)`
|
--- * `GteDelay_ add_si(r.dst_ptr, r.scratch, dst_offset)`
|
||||||
---
|
---
|
||||||
--- Why this classification matters: the metaprogram emits tokens separated by `,`
|
--- Why this classification matters: the metaprogram emits tokens separated by `,` and joins them with `\<newline>` line continuations. After C preprocessor
|
||||||
--- and joins them with `\<newline>` line continuations. After C preprocessor
|
|
||||||
--- phase 2 (line splicing), the macro body collapses to a single logical line.
|
--- phase 2 (line splicing), the macro body collapses to a single logical line.
|
||||||
--- Each delay-marker identifier expands to empty (its definition
|
--- Each delay-marker identifier expands to empty (its definition `#define GteDelay_ // ...` consumes the `//` line comment during preprocessing
|
||||||
--- `#define GteDelay_ // ...` consumes the `//` line comment during preprocessing
|
--- of the definition itself, leaving an empty replacement list).
|
||||||
--- of the definition itself, leaving an empty replacement list). When a token
|
--- When a token is purely a delay marker with only a trailing comment, the `,` the metaprogram normally adds before
|
||||||
--- is purely a delay marker with only a trailing comment, the `,` the metaprogram
|
--- each token-after-the-first brackets empty content and produces the syntax error `,,` (`expected expression before ',' token`) at C compile.
|
||||||
--- normally adds before each token-after-the-first brackets empty content and
|
--- The metaprogram therefore emits such tokens WITHOUT the leading `,` (see `token_skips_leading_comma`) —
|
||||||
--- produces the syntax error `,,` (`expected expression before ',' token`) at
|
--- but the marker + trailing comment are still emitted verbatim so the annotation is preserved in `gen/macs.h`.
|
||||||
--- C compile. The metaprogram therefore emits such tokens WITHOUT the leading
|
|
||||||
--- `,` (see `token_skips_leading_comma`) — but the marker + trailing comment
|
|
||||||
--- are still emitted verbatim so the annotation is preserved in `gen/macs.h`.
|
|
||||||
--- @param tok string -- a single token from split_top_level_commas (already trimmed at the start, may contain trailing whitespace + block comment)
|
--- @param tok string -- a single token from split_top_level_commas (already trimmed at the start, may contain trailing whitespace + block comment)
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
local function is_pure_delay_marker_token(tok)
|
local function is_pure_delay_marker_token(tok)
|
||||||
@@ -655,17 +620,14 @@ end
|
|||||||
--- followed by whitespace + optional block comment and NOTHING ELSE) expand
|
--- followed by whitespace + optional block comment and NOTHING ELSE) expand
|
||||||
--- to empty at C preprocessor time. Emitting them WITHOUT the leading `,`
|
--- to empty at C preprocessor time. Emitting them WITHOUT the leading `,`
|
||||||
--- separator that the metaprogram normally adds before each token after the
|
--- separator that the metaprogram normally adds before each token after the
|
||||||
--- first keeps exactly one `,` between the surrounding real expressions in
|
--- first keeps exactly one `,` between the surrounding real expressions in the spliced macro body:
|
||||||
--- the spliced macro body:
|
|
||||||
---
|
|
||||||
--- * before this rule: `<tok1> ,\t<gdelay> ,\t<tok3>` → after expansion
|
--- * before this rule: `<tok1> ,\t<gdelay> ,\t<tok3>` → after expansion
|
||||||
--- `<tok1> , /* comment */ , <tok3>` → `,,` syntax error.
|
--- `<tok1> , /* comment */ , <tok3>` → `,,` syntax error.
|
||||||
--- * after this rule: `<tok1> \t<gdelay> ,\t<tok3>` → after expansion
|
--- * after this rule: `<tok1> \t<gdelay> ,\t<tok3>` → after expansion
|
||||||
--- `<tok1> /* comment */ , <tok3>` → `<tok1>, <tok3>` — valid.
|
--- `<tok1> /* comment */ , <tok3>` → `<tok1>, <tok3>` — valid.
|
||||||
---
|
---
|
||||||
--- Tokens like `GteDelay_ nop2` keep the leading `,` (the marker is followed
|
--- Tokens like `GteDelay_ nop2` keep the leading `,`
|
||||||
--- by a real instruction, so the marker + instruction together need the
|
--- (the marker is followed by a real instruction, so the marker + instruction together need the separator on the LEFT to land between two real expressions).
|
||||||
--- separator on the LEFT to land between two real expressions).
|
|
||||||
--- @param tok string
|
--- @param tok string
|
||||||
--- @return boolean -- true if the token needs NO leading `,` separator.
|
--- @return boolean -- true if the token needs NO leading `,` separator.
|
||||||
local function token_skips_leading_comma(tok)
|
local function token_skips_leading_comma(tok)
|
||||||
@@ -675,7 +637,10 @@ end
|
|||||||
--- Emit the `#define mac_X(sig) \<newline>\t<tok1> \<newline>,\t<tok2> ...` block.
|
--- Emit the `#define mac_X(sig) \<newline>\t<tok1> \<newline>,\t<tok2> ...` block.
|
||||||
--- Converts `//` line comments to `/* */` block comments in each token so they don't break the C macro `\` line continuations.
|
--- Converts `//` line comments to `/* */` block comments in each token so they don't break the C macro `\` line continuations.
|
||||||
---
|
---
|
||||||
--- Pure delay-marker tokens (`GteDelay_` / `LdSlot_` / `BdSlot_` / `DmaSlot_` with only a trailing block comment, no real instruction) are emitted WITHOUT a leading `,` separator; the annotation IS preserved in the generated header (so the comment + marker remain visible to anyone reading `gen/macs.h`), but the C preprocessor expands the marker to empty, so leaving the `,` separator out is what stops the `,,` syntax error. See `token_skips_leading_comma` for the contract.
|
--- Pure delay-marker tokens (`GteDelay_` / `LdSlot_` / `BdSlot_` / `DmaSlot_` with only a trailing block comment, no real instruction) are emitted WITHOUT a leading `,` separator;
|
||||||
|
--- the annotation IS preserved in the generated header
|
||||||
|
--- (so the comment + marker remain visible to anyone reading `gen/macs.h`), but the C preprocessor expands the marker to empty, so leaving the `,`
|
||||||
|
--- separator out is what stops the `,,` syntax error. See `token_skips_leading_comma` for the contract.
|
||||||
local function emit_macro_body(lines, c, sig, tokens)
|
local function emit_macro_body(lines, c, sig, tokens)
|
||||||
for tok_idx = 1, #tokens do
|
for tok_idx = 1, #tokens do
|
||||||
tokens[tok_idx] = convert_line_comments_to_block(tokens[tok_idx])
|
tokens[tok_idx] = convert_line_comments_to_block(tokens[tok_idx])
|
||||||
|
|||||||
@@ -1661,24 +1661,24 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
S.bytes[#S.bytes + 1] = s
|
S.bytes[#S.bytes + 1] = s
|
||||||
S.next_offset = S.next_offset + #s
|
S.next_offset = S.next_offset + #s
|
||||||
end
|
end
|
||||||
|
local FORM_WRITERS = {
|
||||||
|
string = function(emit, v) emit(v .. "\0") end,
|
||||||
|
data1 = function(emit, v) emit(string.char(v)) end,
|
||||||
|
udata = function(emit, v) emit(uleb128(v)) end,
|
||||||
|
addr = function(emit, v) emit(elf_dwarf.write_u32_le(v)) end,
|
||||||
|
ref4 = function(emit, v) emit(elf_dwarf.write_u32_le(v)) end,
|
||||||
|
sec_offset = function(emit, v) emit(elf_dwarf.write_u32_le(v)) end,
|
||||||
|
data2 = function(emit, v) emit(elf_dwarf.write_u16_le(v)) end,
|
||||||
|
exprloc = function(emit, v) emit(v) end,
|
||||||
|
}
|
||||||
local function emit_die(schema_name, values)
|
local function emit_die(schema_name, values)
|
||||||
local row = DIE_SCHEMA[schema_name]
|
local row = DIE_SCHEMA[schema_name]
|
||||||
emit(uleb128(row.abbrev))
|
emit(uleb128(row.abbrev))
|
||||||
for _, attr in ipairs(row.attrs) do
|
for _, attr in ipairs(row.attrs) do
|
||||||
local v = values[attr.key]
|
local v = values[attr.key]
|
||||||
if attr.form == "string" then
|
local w = FORM_WRITERS[attr.form]
|
||||||
emit(v .. "\0")
|
if not w then error("emit_die: unknown form " .. tostring(attr.form)) end
|
||||||
elseif attr.form == "data1" then
|
w(emit, v)
|
||||||
emit(string.char(v))
|
|
||||||
elseif attr.form == "udata" then
|
|
||||||
emit(uleb128(v))
|
|
||||||
elseif attr.form == "addr" or attr.form == "ref4" or attr.form == "sec_offset" then
|
|
||||||
emit(elf_dwarf.write_u32_le(v))
|
|
||||||
elseif attr.form == "data2" then
|
|
||||||
emit(elf_dwarf.write_u16_le(v))
|
|
||||||
elseif attr.form == "exprloc" then
|
|
||||||
emit(v)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function ref4_of(section_offset)
|
local function ref4_of(section_offset)
|
||||||
@@ -1737,55 +1737,35 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- then a single DW_TAG_pointer_type pointing at the structure_type. gdb's
|
-- then a single DW_TAG_pointer_type pointing at the structure_type. gdb's
|
||||||
-- `print *<var>` then expands the struct and lists its members (x, y, z, w for V4_S2; etc.).
|
-- `print *<var>` then expands the struct and lists its members (x, y, z, w for V4_S2; etc.).
|
||||||
--
|
--
|
||||||
-- Hardcoded member-info table for the 6 typed views the prototype uses (V2_S2 / V3_S2 / V4_S2 / V2_S4 / V3_S4 / V4_S4).
|
-- Layout for V* / Rect_* / Reg_* / Slice / … comes from corpus.type_name_registry.
|
||||||
-- Each row is {byte_size, members}, where each member is {name, offset, byte_size, type_name}.
|
-- scan_source parses Struct_() in math.h, math.atom.h, memory.h and fills field offset + byte_size.
|
||||||
-- `type_name` is the base type name from `type_name_registry`; "S2" / "S4" need a signed base_type emit too.
|
-- U1–U4 / S1–S4 / B1–B4 stay authored fundamentals (BUILTIN_BYTE_SIZES + base_type DIEs below).
|
||||||
--
|
local type_reg = registries.type_name_registry or {}
|
||||||
-- The table is small + explicit — the prototype principle treats the typed-view struct layout as data, not derived state.
|
local function layout_from_registry(tn)
|
||||||
local STRUCT_MEMBER_TABLE = {
|
local entry = type_reg[tn]
|
||||||
-- TODO(Ed): This hardcoding is brittle...
|
if not entry or entry.kind ~= "struct" or not entry.fields or #entry.fields == 0 then
|
||||||
-- TODO(Ed): Better to just have a table for the fundamental types in duffle/dsl.h, we can derive the rest via typedef parsing...
|
return nil
|
||||||
-- 2-element signed short vector (rare; placeholder for future use).
|
end
|
||||||
V2_S2 = { byte_size = 4, members = {
|
if entry.byte_size == nil then return nil end
|
||||||
{ name = "x", offset = 0, byte_size = 2 },
|
local members = {}
|
||||||
{ name = "y", offset = 2, byte_size = 2 },
|
for _, f in ipairs(entry.fields) do
|
||||||
}},
|
if f.offset == nil or f.byte_size == nil then return nil end
|
||||||
-- 4-element signed short vector (the dominant face-cursor type).
|
members[#members + 1] = {
|
||||||
V4_S2 = { byte_size = 8, members = {
|
name = f.name,
|
||||||
{ name = "x", offset = 0, byte_size = 2 },
|
offset = f.offset,
|
||||||
{ name = "y", offset = 2, byte_size = 2 },
|
byte_size = f.byte_size,
|
||||||
{ name = "z", offset = 4, byte_size = 2 },
|
type_name = f.type_name,
|
||||||
{ name = "w", offset = 6, byte_size = 2 },
|
pointer_depth = f.pointer_depth or 0,
|
||||||
}},
|
}
|
||||||
-- 3-element signed short vector (the floor face-cursor type; includes explicit pad byte to match math.h's S2 pad member).
|
end
|
||||||
V3_S2 = { byte_size = 8, members = {
|
return { byte_size = entry.byte_size, members = members }
|
||||||
{ name = "x", offset = 0, byte_size = 2 },
|
end
|
||||||
{ name = "y", offset = 2, byte_size = 2 },
|
local function encoding_for_type(tn)
|
||||||
{ name = "z", offset = 4, byte_size = 2 },
|
if type(tn) == "string" and tn:match("^S[124]$") then return 5 end
|
||||||
{ name = "pad", offset = 6, byte_size = 2 },
|
return 7
|
||||||
}},
|
end
|
||||||
-- 2-element signed int vector.
|
local S2_TYPE_BYTE_SIZE = 2
|
||||||
V2_S4 = { byte_size = 8, members = {
|
local S4_TYPE_BYTE_SIZE = 4
|
||||||
{ name = "x", offset = 0, byte_size = 4 },
|
|
||||||
{ name = "y", offset = 4, byte_size = 4 },
|
|
||||||
}},
|
|
||||||
-- 3-element signed int vector.
|
|
||||||
V3_S4 = { byte_size = 16, members = {
|
|
||||||
{ name = "x", offset = 0, byte_size = 4 },
|
|
||||||
{ name = "y", offset = 4, byte_size = 4 },
|
|
||||||
{ name = "z", offset = 8, byte_size = 4 },
|
|
||||||
{ name = "pad", offset = 12, byte_size = 4 },
|
|
||||||
}},
|
|
||||||
-- 4-element signed int vector.
|
|
||||||
V4_S4 = { byte_size = 16, members = {
|
|
||||||
{ name = "x", offset = 0, byte_size = 4 },
|
|
||||||
{ name = "y", offset = 4, byte_size = 4 },
|
|
||||||
{ name = "z", offset = 8, byte_size = 4 },
|
|
||||||
{ name = "w", offset = 12, byte_size = 4 },
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
local S2_TYPE_BYTE_SIZE = 2 -- S2 = signed 16-bit (see dsl.h)
|
|
||||||
local S4_TYPE_BYTE_SIZE = 4 -- S4 = signed 32-bit (see dsl.h)
|
|
||||||
|
|
||||||
-- Pre-emit the signed base types (S2, S4) once if any typed view's member needs them.
|
-- Pre-emit the signed base types (S2, S4) once if any typed view's member needs them.
|
||||||
-- We emit per-typed-view lazily below; build a member-base-type offset cache (idempotent).
|
-- We emit per-typed-view lazily below; build a member-base-type offset cache (idempotent).
|
||||||
@@ -1804,17 +1784,54 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
-- Always emit S2 + S4 (the v*_S2 / v*_S4 family base types) once before any typed-view,
|
-- Always emit S2 + S4 (the v*_S2 / v*_S4 family base types) once before any typed-view,
|
||||||
-- even if no atom currently declares a v*_S2 field, so future atoms pick them up without rewiring.
|
-- even if no atom currently declares a v*_S2 field, so future atoms pick them up without rewiring.
|
||||||
ensure_member_base_type("S2", S2_TYPE_BYTE_SIZE, 5) -- DW_ATE_signed = 5
|
ensure_member_base_type("S2", S2_TYPE_BYTE_SIZE, 5)
|
||||||
ensure_member_base_type("S4", S4_TYPE_BYTE_SIZE, 5)
|
ensure_member_base_type("S4", S4_TYPE_BYTE_SIZE, 5)
|
||||||
|
|
||||||
local type_chain_offsets = {}
|
local type_chain_offsets = {}
|
||||||
|
local struct_die_offsets = {}
|
||||||
|
local function emit_struct_layout(tn)
|
||||||
|
if struct_die_offsets[tn] then return struct_die_offsets[tn] end
|
||||||
|
local type_info = layout_from_registry(tn)
|
||||||
|
if not type_info then return nil end
|
||||||
|
for _, m in ipairs(type_info.members) do
|
||||||
|
if (m.pointer_depth or 0) == 0 and layout_from_registry(m.type_name) then
|
||||||
|
emit_struct_layout(m.type_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local struct_offset = next_offset()
|
||||||
|
struct_die_offsets[tn] = struct_offset
|
||||||
|
emit_die("structure_type", {
|
||||||
|
name = tn,
|
||||||
|
byte_size = type_info.byte_size,
|
||||||
|
})
|
||||||
|
for _, m in ipairs(type_info.members) do
|
||||||
|
local member_type_off
|
||||||
|
if (m.pointer_depth or 0) > 0 then
|
||||||
|
member_type_off = type_chain_offsets[m.type_name .. "|" .. m.pointer_depth]
|
||||||
|
elseif layout_from_registry(m.type_name) then
|
||||||
|
member_type_off = struct_die_offsets[m.type_name]
|
||||||
|
else
|
||||||
|
member_type_off = ensure_member_base_type(
|
||||||
|
m.type_name, m.byte_size, encoding_for_type(m.type_name))
|
||||||
|
end
|
||||||
|
if not member_type_off then
|
||||||
|
member_type_off = ensure_member_base_type(
|
||||||
|
m.type_name or "U4", m.byte_size or U4_BYTE_SIZE, 7)
|
||||||
|
end
|
||||||
|
emit_die("member", {
|
||||||
|
name = m.name,
|
||||||
|
data_member_location = m.offset,
|
||||||
|
type = ref4_of(member_type_off),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
emit(string.char(DIE_CHILDREN_TERMINATOR))
|
||||||
|
return struct_offset
|
||||||
|
end
|
||||||
for _, tn in ipairs(sorted_typed_types) do
|
for _, tn in ipairs(sorted_typed_types) do
|
||||||
if tn ~= "U4" then
|
if tn ~= "U4" then
|
||||||
local depth = used_typed_views[tn]
|
local depth = used_typed_views[tn]
|
||||||
local type_info = STRUCT_MEMBER_TABLE[tn]
|
local struct_offset = emit_struct_layout(tn)
|
||||||
if not type_info then
|
if not struct_offset then
|
||||||
-- Unknown typed view: fall back to a generic 4-byte unsigned base_type to keep the wire valid.
|
|
||||||
-- gdb renders as the typename but `print *ptr` only sees the first 4 bytes.
|
|
||||||
local innermost_offset = next_offset()
|
local innermost_offset = next_offset()
|
||||||
emit_die("base_type", {
|
emit_die("base_type", {
|
||||||
name = tn,
|
name = tn,
|
||||||
@@ -1824,40 +1841,12 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
local outermost_offset = next_offset()
|
local outermost_offset = next_offset()
|
||||||
emit_die("pointer_type", { type = ref4_of(innermost_offset) })
|
emit_die("pointer_type", { type = ref4_of(innermost_offset) })
|
||||||
type_chain_offsets[tn .. "|" .. depth] = outermost_offset
|
type_chain_offsets[tn .. "|" .. depth] = outermost_offset
|
||||||
|
elseif depth == 1 then
|
||||||
|
local outermost_offset = next_offset()
|
||||||
|
emit_die("pointer_type", { type = ref4_of(struct_offset) })
|
||||||
|
type_chain_offsets[tn .. "|" .. depth] = outermost_offset
|
||||||
else
|
else
|
||||||
-- Emit a proper structure_type DIE for this typed view.
|
error("typed-view: pointer_depth > 1 is not yet supported in this emission path")
|
||||||
local struct_offset = next_offset()
|
|
||||||
emit_die("structure_type", {
|
|
||||||
name = tn,
|
|
||||||
byte_size = type_info.byte_size,
|
|
||||||
})
|
|
||||||
for _, m in ipairs(type_info.members) do
|
|
||||||
local member_type_name, member_type_encoding
|
|
||||||
if m.byte_size == 2 then
|
|
||||||
member_type_name = "S2"
|
|
||||||
member_type_encoding = 5
|
|
||||||
elseif m.byte_size == 4 then
|
|
||||||
member_type_name = "S4"
|
|
||||||
member_type_encoding = 5
|
|
||||||
else
|
|
||||||
member_type_name = "U4"
|
|
||||||
member_type_encoding = 7
|
|
||||||
end
|
|
||||||
local member_base_off = ensure_member_base_type(member_type_name, m.byte_size, member_type_encoding)
|
|
||||||
emit_die("member", {
|
|
||||||
name = m.name,
|
|
||||||
data_member_location = m.offset,
|
|
||||||
type = ref4_of(member_base_off),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
emit(string.char(DIE_CHILDREN_TERMINATOR))
|
|
||||||
if depth == 1 then
|
|
||||||
local outermost_offset = next_offset()
|
|
||||||
emit_die("pointer_type", { type = ref4_of(struct_offset) })
|
|
||||||
type_chain_offsets[tn .. "|" .. depth] = outermost_offset
|
|
||||||
else
|
|
||||||
error("typed-view: pointer_depth > 1 is not yet supported in this emission path")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1991,9 +1980,6 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local atom_view = (registries.atom_views or {})[atom.name]
|
local atom_view = (registries.atom_views or {})[atom.name]
|
||||||
-- Build the atom-name lookup table once (cheap; O(atom_table)) so step (b) and step (d) can resolve rbind_atom names.
|
|
||||||
-- TODO(Ed): Bad assignment?
|
|
||||||
local atom_by_name = atom_by_name or (function() local m = {}; for _, a in ipairs(atom_table) do if a.name then m[a.name] = a end end; return m end)()
|
|
||||||
-- step (b) inputs: this atom's `atom_ctx(<rbind_atom>)` (resolved from the registries' atom_ctxs)
|
-- step (b) inputs: this atom's `atom_ctx(<rbind_atom>)` (resolved from the registries' atom_ctxs)
|
||||||
local this_ctx = registries.atom_ctxs and registries.atom_ctxs[atom.name]
|
local this_ctx = registries.atom_ctxs and registries.atom_ctxs[atom.name]
|
||||||
if this_ctx and this_ctx.rbind_atom then
|
if this_ctx and this_ctx.rbind_atom then
|
||||||
@@ -2073,9 +2059,9 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end,
|
end,
|
||||||
-- (e) enum-site atom_type(<T>) default on the registry entry.
|
-- (e) enum-site atom_type(<T>) default on the registry entry.
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
-- TODO(Ed): Bad definition?
|
local a = by_alias[r_name]
|
||||||
if alias and alias.default_type and alias.default_depth and alias.default_depth > 0 then
|
if a and a.default_type and a.default_depth and a.default_depth > 0 then
|
||||||
return type_chain_offsets[alias.default_type .. "|" .. alias.default_depth]
|
return type_chain_offsets[a.default_type .. "|" .. a.default_depth]
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,6 @@
|
|||||||
--- Per-directory aggregation: every source in the same directory contributes to the same `gen/offsets.h`.
|
--- Per-directory aggregation: every source in the same directory contributes to the same `gen/offsets.h`.
|
||||||
--- The directory itself is the namespace; the filename does not repeat the module name.
|
--- The directory itself is the namespace; the filename does not repeat the module name.
|
||||||
---
|
---
|
||||||
--- (Task 12.16 note: atom-namespaced enum names — e.g., `atom_offset__normalize_v3s4__srav_path__aligned_done` —
|
|
||||||
--- were considered to prevent cross-atom label collisions, but the C-side `atom_offset(F, T)` macro in
|
|
||||||
--- `code/duffle/dsl.atom.h` doesn't know the current atom_name at expansion time, so any namespacing
|
|
||||||
--- on the metaprogram side breaks the C build. Reverted. The C-side would need a per-atom
|
|
||||||
--- `CURRENT_ATOM` #define (set by `MipsAtom_`/`MipsAtom_Proc_` macros) plus an updated `atom_offset`
|
|
||||||
--- macro that uses it. That's a coordinated refactor — deferred to a future track.)
|
|
||||||
---
|
|
||||||
--- The offset is `target_word - branch_word - 1` (the standard MIPS branch-immediate encoding: branch_offset = relative_pc_in_words - 1).
|
--- The offset is `target_word - branch_word - 1` (the standard MIPS branch-immediate encoding: branch_offset = relative_pc_in_words - 1).
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -124,9 +117,7 @@ end
|
|||||||
--- For cross-module `j`/`jal` (atom body in one module, target in another), the linker emits a `R_MIPS_26` relocation against the lower 26 bits; the upper 4 bits come from the PC of the delay slot following the `j`.
|
--- For cross-module `j`/`jal` (atom body in one module, target in another), the linker emits a `R_MIPS_26` relocation against the lower 26 bits; the upper 4 bits come from the PC of the delay slot following the `j`.
|
||||||
--- The metaprogram doesn't know either at compile time, so the emitted value is the relative word offset that the duffle `enc_i` macro places in the immediate field; the toolchain handles the rest.
|
--- The metaprogram doesn't know either at compile time, so the emitted value is the relative word offset that the duffle `enc_i` macro places in the immediate field; the toolchain handles the rest.
|
||||||
--- `jump_reg` / `call_reg` / `jump_link` -> ERROR. Register-form jumps have no offset field; `atom_offset` is invalid.
|
--- `jump_reg` / `call_reg` / `jump_link` -> ERROR. Register-form jumps have no offset field; `atom_offset` is invalid.
|
||||||
---
|
--- missing `consuming_encoder` -> ERROR. A lone top-level `atom_offset` is not a branch.
|
||||||
--- Top-level `atom_offset(F, T)` markers (where the marker is the entire token — `consuming_encoder` == nil) default to `branch_*` behavior (relative offset).
|
|
||||||
--- This preserves backward compatibility for any top-level marker that may exist outside a control-transfer instruction.
|
|
||||||
--- @param labels table<string, integer>
|
--- @param labels table<string, integer>
|
||||||
--- @param branches table[]
|
--- @param branches table[]
|
||||||
--- @param errors table[]
|
--- @param errors table[]
|
||||||
@@ -142,14 +133,19 @@ local function compute_offsets(labels, branches, errors)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
local consuming = br.consuming_encoder
|
local consuming = br.consuming_encoder
|
||||||
if consuming == "jump_reg" or consuming == "call_reg" or consuming == "jump_link" then
|
if consuming == nil or consuming == "" then
|
||||||
|
errors[#errors + 1] = {
|
||||||
|
line = br.line or 0,
|
||||||
|
msg = "atom_offset requires a consuming encoder (branch_*, jump, call_addr); top-level atom_offset is invalid; at word " .. br.branch_word,
|
||||||
|
}
|
||||||
|
elseif consuming == "jump_reg" or consuming == "call_reg" or consuming == "jump_link" then
|
||||||
errors[#errors + 1] = {
|
errors[#errors + 1] = {
|
||||||
line = br.line or 0,
|
line = br.line or 0,
|
||||||
msg = "atom_offset cannot be used with " .. consuming
|
msg = "atom_offset cannot be used with " .. consuming
|
||||||
.. " (register-form jumps have no offset field); at word " .. br.branch_word,
|
.. " (register-form jumps have no offset field); at word " .. br.branch_word,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
-- All other consuming instructions (including `branch_*`, `jump`, `call_addr`, and nil for top-level markers) use the same relative offset value.
|
-- Consuming instructions with an offset field (`branch_*`, `jump`, `call_addr`) use the same relative offset value.
|
||||||
-- The MIPS encoding differs per opcode but the duffle `enc_i` macro handles the truncation to the immediate-field width.
|
-- The MIPS encoding differs per opcode but the duffle `enc_i` macro handles the truncation to the immediate-field width.
|
||||||
results[#results + 1] = {
|
results[#results + 1] = {
|
||||||
target = br.target,
|
target = br.target,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
--- passes/report.lua — Per-MODULE annotation report renderer + project-wide summary writer.
|
--- passes/report.lua — Per-MODULE annotation report renderer + project-wide summary writer.
|
||||||
---
|
---
|
||||||
--- Two output files per build:
|
--- Per-module markdown plus one project summary:
|
||||||
--- - `build/gen/<dir_basename>.annotations.txt` — one per source-directory containing atoms; aggregates across all sources in the directory.
|
--- - `build/<dir_basename>.atom_meta_report.md` — one per source-directory containing atoms
|
||||||
--- - `build/gen/annotation_validation.txt` — the project summary.
|
--- - `build/<dir_basename>.atoms.md` — verbose source map
|
||||||
|
--- - `build/atom_meta_report.summary.md` — the project summary
|
||||||
---
|
---
|
||||||
--- The canonical `corpus.sources_by_dir` projection groups sources by directory.
|
--- The canonical `corpus.sources_by_dir` projection groups sources by directory.
|
||||||
--- This pass builds one ModuleView per directory and walks SECTION_RENDERERS.
|
--- This pass builds one ModuleView per directory and walks SECTION_RENDERERS.
|
||||||
@@ -305,7 +306,6 @@ local function build_module_view(dir, dir_sources, corpus)
|
|||||||
decls = decls,
|
decls = decls,
|
||||||
schemas = schemas,
|
schemas = schemas,
|
||||||
findings = sa.findings or {},
|
findings = sa.findings or {},
|
||||||
sa = sa,
|
|
||||||
corpus = corpus,
|
corpus = corpus,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
+290
-193
@@ -179,7 +179,7 @@ end
|
|||||||
local function read_parens_after(source, ident_end, fallback)
|
local function read_parens_after(source, ident_end, fallback)
|
||||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
if source:sub(open_paren, open_paren) ~= "(" then return nil, fallback or open_paren + 1 end
|
if source:sub(open_paren, open_paren) ~= "(" then return nil, fallback or open_paren + 1 end
|
||||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||||
return inner, after_paren, open_paren
|
return inner, after_paren, open_paren
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ end
|
|||||||
local function find_body_braces(source, after_paren, fallback)
|
local function find_body_braces(source, after_paren, fallback)
|
||||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||||
if not brace then return nil, fallback or (after_paren + 1) end
|
if not brace then return nil, fallback or (after_paren + 1) end
|
||||||
local body, after_brace = duffle.read_braces(source, brace)
|
local body, after_brace = duffle.read_braces(source, brace)
|
||||||
return body, after_brace, brace + 1
|
return body, after_brace, brace + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ local function preceding_comment_walk_backward(source, start_pos)
|
|||||||
line_start = line_start - 1
|
line_start = line_start - 1
|
||||||
end
|
end
|
||||||
local line = source:sub(line_start, non_ws)
|
local line = source:sub(line_start, non_ws)
|
||||||
if line:sub(1, 2) ~= "//" then break end
|
if line:sub(1, 2) ~= "//" then break end
|
||||||
table.insert(pieces, 1, line)
|
table.insert(pieces, 1, line)
|
||||||
scan_pos = line_start - 1
|
scan_pos = line_start - 1
|
||||||
end
|
end
|
||||||
@@ -303,8 +303,8 @@ local function register_atom(out, kind, declaration_line, name, body, body_off,
|
|||||||
-- Capture the pending marker BEFORE attaching so the walker can anchor the backward comment walk on the marker's marker_pos
|
-- Capture the pending marker BEFORE attaching so the walker can anchor the backward comment walk on the marker's marker_pos
|
||||||
-- (which is the correct anchor even when an `FI_ MipsAtom ac_X(args)` proc-prelude separates the marker from the declaration).
|
-- (which is the correct anchor even when an `FI_ MipsAtom ac_X(args)` proc-prelude separates the marker from the declaration).
|
||||||
local pending_marker = nil
|
local pending_marker = nil
|
||||||
local markers = out.debug_skip_markers
|
local markers = out.debug_skip_markers
|
||||||
local m = markers[#markers]
|
local m = markers[#markers]
|
||||||
if m and m.pending then pending_marker = m end
|
if m and m.pending then pending_marker = m end
|
||||||
|
|
||||||
local positive = attach_debug_skip_marker(out, kind)
|
local positive = attach_debug_skip_marker(out, kind)
|
||||||
@@ -314,7 +314,7 @@ local function register_atom(out, kind, declaration_line, name, body, body_off,
|
|||||||
-- The walker does not need to detect marker shape.
|
-- The walker does not need to detect marker shape.
|
||||||
-- A pending_marker record (or the declaration ident_pos fallback) supplies the anchor position.
|
-- A pending_marker record (or the declaration ident_pos fallback) supplies the anchor position.
|
||||||
local start_pos = comment_walk_start(pending_marker, pos)
|
local start_pos = comment_walk_start(pending_marker, pos)
|
||||||
comment = preceding_comment_walk_backward(source, start_pos)
|
comment = preceding_comment_walk_backward(source, start_pos)
|
||||||
end
|
end
|
||||||
out.atoms[#out.atoms + 1] = {
|
out.atoms[#out.atoms + 1] = {
|
||||||
line = declaration_line,
|
line = declaration_line,
|
||||||
@@ -335,7 +335,7 @@ end
|
|||||||
local function register_raw_atom(out, declaration_line, name, body, body_off, raw_name, pos)
|
local function register_raw_atom(out, declaration_line, name, body, body_off, raw_name, pos)
|
||||||
out.raw_atoms[#out.raw_atoms + 1] = {
|
out.raw_atoms[#out.raw_atoms + 1] = {
|
||||||
line = declaration_line, name = name, body = body, body_off = body_off,
|
line = declaration_line, name = name, body = body, body_off = body_off,
|
||||||
kind = "raw_atom", raw_name = raw_name,
|
kind = "raw_atom", raw_name = raw_name,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,7 +344,7 @@ end
|
|||||||
local function parse_type_chain(text, pos)
|
local function parse_type_chain(text, pos)
|
||||||
if pos > #text then return nil end
|
if pos > #text then return nil end
|
||||||
-- Skip leading whitespace before the type ident.
|
-- Skip leading whitespace before the type ident.
|
||||||
local start = duffle.skip_ws_and_cmt(text, pos)
|
local start = duffle.skip_ws_and_cmt(text, pos)
|
||||||
local ident, after = duffle.read_ident(text, start)
|
local ident, after = duffle.read_ident(text, start)
|
||||||
if not ident then return nil end
|
if not ident then return nil end
|
||||||
local depth = 0
|
local depth = 0
|
||||||
@@ -367,6 +367,9 @@ local BUILTIN_BYTE_SIZES = {
|
|||||||
["S1"] = 1,
|
["S1"] = 1,
|
||||||
["S2"] = 2,
|
["S2"] = 2,
|
||||||
["S4"] = 4,
|
["S4"] = 4,
|
||||||
|
["B1"] = 1,
|
||||||
|
["B2"] = 2,
|
||||||
|
["B4"] = 4,
|
||||||
-- GCC __UINT*/__INT*_TYPE__ family (used by the duffle TSet_ convention in dsl.h).
|
-- GCC __UINT*/__INT*_TYPE__ family (used by the duffle TSet_ convention in dsl.h).
|
||||||
-- MIPS32 has no 64-bit types; __UINT64_TYPE__/__INT64_TYPE__ are excluded.
|
-- MIPS32 has no 64-bit types; __UINT64_TYPE__/__INT64_TYPE__ are excluded.
|
||||||
["__UINT8_TYPE__"] = 1,
|
["__UINT8_TYPE__"] = 1,
|
||||||
@@ -422,7 +425,7 @@ end
|
|||||||
-- Returns the raw fields array with `{name, type_name, pointer_depth}` only (NO offset / byte_size).
|
-- Returns the raw fields array with `{name, type_name, pointer_depth}` only (NO offset / byte_size).
|
||||||
-- The propagation pass `resolve_struct_field_sizes` walks each struct's fields AFTER type resolution and populates offset + byte_size in place.
|
-- The propagation pass `resolve_struct_field_sizes` walks each struct's fields AFTER type resolution and populates offset + byte_size in place.
|
||||||
local function parse_struct_body_fields(body)
|
local function parse_struct_body_fields(body)
|
||||||
local fields = {}
|
local fields = {}
|
||||||
local body_pos = 1
|
local body_pos = 1
|
||||||
local body_len = #body
|
local body_len = #body
|
||||||
while body_pos <= body_len do
|
while body_pos <= body_len do
|
||||||
@@ -563,6 +566,16 @@ local function propagate_type_sizes(out)
|
|||||||
for _ = 1, TYPE_CHAIN_MAX_DEPTH do
|
for _ = 1, TYPE_CHAIN_MAX_DEPTH do
|
||||||
local any_change = false
|
local any_change = false
|
||||||
for _, entry in pairs(reg) do
|
for _, entry in pairs(reg) do
|
||||||
|
if entry.kind == "array" and entry.byte_size == nil and entry.counts then
|
||||||
|
local elem_size = BUILTIN_BYTE_SIZES[entry.elem]
|
||||||
|
or (reg[entry.elem] and reg[entry.elem].byte_size)
|
||||||
|
if elem_size then
|
||||||
|
local n = 1
|
||||||
|
for _, c in ipairs(entry.counts) do n = n * c end
|
||||||
|
entry.byte_size = elem_size * n
|
||||||
|
any_change = true
|
||||||
|
end
|
||||||
|
end
|
||||||
if entry.kind == "struct" and entry.fields then
|
if entry.kind == "struct" and entry.fields then
|
||||||
local byte_off = 0
|
local byte_off = 0
|
||||||
local gap_seen = false
|
local gap_seen = false
|
||||||
@@ -710,7 +723,7 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
-- reads/writes arrays contain ONLY register idents;
|
-- reads/writes arrays contain ONLY register idents;
|
||||||
-- `atom_type(...)` sub-entry (when present and well-formed) is recorded as a per-atom reg_type_override.
|
-- `atom_type(...)` sub-entry (when present and well-formed) is recorded as a per-atom reg_type_override.
|
||||||
local entries = duffle.split_top_level_commas(sub_inner)
|
local entries = duffle.split_top_level_commas(sub_inner)
|
||||||
local regs = {}
|
local regs = {}
|
||||||
for _, entry in ipairs(entries) do
|
for _, entry in ipairs(entries) do
|
||||||
local reg_name, override, malformed = parse_atom_info_reg_entry(entry)
|
local reg_name, override, malformed = parse_atom_info_reg_entry(entry)
|
||||||
if reg_name then
|
if reg_name then
|
||||||
@@ -742,9 +755,9 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function reg_types_handler(sub_inner, info_line)
|
local function reg_types_handler(sub_inner, info_line)
|
||||||
local args = duffle.split_top_level_commas(sub_inner)
|
local args = duffle.split_top_level_commas(sub_inner)
|
||||||
if not args[1] then return end
|
if not args[1] then return end
|
||||||
reg_overrides = reg_overrides or {}
|
reg_overrides = reg_overrides or {}
|
||||||
local reg_name = duffle.trim(args[1])
|
local reg_name = duffle.trim(args[1])
|
||||||
local type_name, depth = nil, 0
|
local type_name, depth = nil, 0
|
||||||
if args[2] then
|
if args[2] then
|
||||||
@@ -763,13 +776,13 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
local SUBCALL_HANDLERS = {
|
local SUBCALL_HANDLERS = {
|
||||||
atom_bind = function(sub_inner) binds = duffle.trim(sub_inner) end, -- scan: atom_bind(<Binds_X>)
|
atom_bind = function(sub_inner) binds = duffle.trim(sub_inner) end, -- scan: atom_bind(<Binds_X>)
|
||||||
atom_reads = function(sub_inner, info_line) rw_handler(sub_inner, info_line, "atom_reads") end, -- scan: atom_reads(<R_X [atom_type(<T>)], ...>)
|
atom_reads = function(sub_inner, info_line) rw_handler(sub_inner, info_line, "atom_reads") end, -- scan: atom_reads(<R_X [atom_type(<T>)], ...>)
|
||||||
atom_writes = function(sub_inner, info_line) rw_handler(sub_inner, info_line, "atom_writes") end, -- scan: atom_writes(<R_X [atom_type(<T>)], ...>)
|
atom_writes = function(sub_inner, info_line) rw_handler(sub_inner, info_line, "atom_writes") end, -- scan: atom_writes(<R_X [atom_type(<T>)], ...>)
|
||||||
atom_view = function(sub_inner) view_binds = duffle.trim(sub_inner) end, -- scan: atom_view(<Binds_X>)
|
atom_view = function(sub_inner) view_binds = duffle.trim(sub_inner) end, -- scan: atom_view(<Binds_X>)
|
||||||
atom_reg_types = reg_types_handler, -- scan: atom_reg_types(<R_X>, <T>)
|
atom_reg_types = reg_types_handler, -- scan: atom_reg_types(<R_X>, <T>)
|
||||||
atom_ctx = function(sub_inner, info_line) ident_handler(sub_inner, info_line, "ctx_atom_name") end, -- scan: atom_ctx(<atom_name>)
|
atom_ctx = function(sub_inner, info_line) ident_handler(sub_inner, info_line, "ctx_atom_name") end, -- scan: atom_ctx(<atom_name>)
|
||||||
atom_phase = function(sub_inner, info_line) ident_handler(sub_inner, info_line, "phase_label") end, -- scan: atom_phase(<label>)
|
atom_phase = function(sub_inner, info_line) ident_handler(sub_inner, info_line, "phase_label") end, -- scan: atom_phase(<label>)
|
||||||
}
|
}
|
||||||
|
|
||||||
local sub_pos = 1
|
local sub_pos = 1
|
||||||
@@ -783,7 +796,7 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end)
|
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end)
|
||||||
if info_inner:sub(sub_open, sub_open) == "(" then
|
if info_inner:sub(sub_open, sub_open) == "(" then
|
||||||
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
||||||
local handler = SUBCALL_HANDLERS[sub_ident]
|
local handler = SUBCALL_HANDLERS[sub_ident]
|
||||||
if handler then handler(sub_inner, info_line) end
|
if handler then handler(sub_inner, info_line) end
|
||||||
sub_pos = sub_after2
|
sub_pos = sub_after2
|
||||||
else
|
else
|
||||||
@@ -798,7 +811,7 @@ end
|
|||||||
local function scan_skip_qualifiers(source, pos)
|
local function scan_skip_qualifiers(source, pos)
|
||||||
while true do
|
while true do
|
||||||
pos = duffle.skip_ws_and_cmt(source, pos)
|
pos = duffle.skip_ws_and_cmt(source, pos)
|
||||||
local ident, after = duffle.read_ident(source, pos)
|
local ident, after = duffle.read_ident(source, pos)
|
||||||
if not ident then return pos end
|
if not ident then return pos end
|
||||||
if QUALIFIER_KEYWORDS[ident] then pos = after else return pos end
|
if QUALIFIER_KEYWORDS[ident] then pos = after else return pos end
|
||||||
end
|
end
|
||||||
@@ -870,11 +883,11 @@ local function read_trailing_cmt_after(body, pos)
|
|||||||
local body_len = #body
|
local body_len = #body
|
||||||
while pos <= body_len do
|
while pos <= body_len do
|
||||||
local b = body:byte(pos)
|
local b = body:byte(pos)
|
||||||
if b == BYTE_SPACE or b == BYTE_TAB or b == BYTE_NEWLINE or b == BYTE_CR then
|
if b == BYTE_SPACE or b == BYTE_TAB or b == BYTE_NEWLINE or b == BYTE_CR then
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
elseif b == BYTE_SLASH then
|
elseif b == BYTE_SLASH then
|
||||||
local b2 = body:byte(pos + 1)
|
local b2 = body:byte(pos + 1)
|
||||||
if b2 == BYTE_STAR then
|
if b2 == BYTE_STAR then
|
||||||
-- Block comment /* ... */
|
-- Block comment /* ... */
|
||||||
local i = pos + 2
|
local i = pos + 2
|
||||||
while i < body_len do
|
while i < body_len do
|
||||||
@@ -909,7 +922,7 @@ end
|
|||||||
parse_enum_int_literal = function(text, start)
|
parse_enum_int_literal = function(text, start)
|
||||||
local pos = start
|
local pos = start
|
||||||
local len = #text
|
local len = #text
|
||||||
if pos > len then return nil, start end
|
if pos > len then return nil, start end
|
||||||
|
|
||||||
local sign = 1
|
local sign = 1
|
||||||
if text:byte(pos) == BYTE_DASH then
|
if text:byte(pos) == BYTE_DASH then
|
||||||
@@ -927,7 +940,7 @@ parse_enum_int_literal = function(text, start)
|
|||||||
local value = 0
|
local value = 0
|
||||||
local has_digit = false
|
local has_digit = false
|
||||||
while pos <= len do
|
while pos <= len do
|
||||||
local d = hex_digit_value(text:byte(pos))
|
local d = hex_digit_value(text:byte(pos))
|
||||||
if not d then break end
|
if not d then break end
|
||||||
value = value * 16 + d
|
value = value * 16 + d
|
||||||
has_digit = true
|
has_digit = true
|
||||||
@@ -1024,17 +1037,17 @@ end
|
|||||||
--- Always saves the raw RHS text into `code_macro_bodies` (for cross-source fallback during chain resolution),
|
--- Always saves the raw RHS text into `code_macro_bodies` (for cross-source fallback during chain resolution),
|
||||||
--- then (if resolvable) stores the resolved integer code into `code_macros` keyed by the macro name.
|
--- then (if resolvable) stores the resolved integer code into `code_macros` keyed by the macro name.
|
||||||
--- `directive_start` points at the `#` byte. The function is silent on non-matching directives, the caller skips the line in any case.
|
--- `directive_start` points at the `#` byte. The function is silent on non-matching directives, the caller skips the line in any case.
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param directive_start integer -- byte position of `#`
|
--- @param directive_start integer -- byte position of `#`
|
||||||
--- @param code_macros table -- out._code_macros / ctx.shared._code_macros
|
--- @param code_macros table -- out._code_macros / ctx.shared._code_macros
|
||||||
--- @param code_macro_bodies table -- out._code_macro_bodies / ctx.shared._code_macro_bodies
|
--- @param code_macro_bodies table -- out._code_macro_bodies / ctx.shared._code_macro_bodies
|
||||||
local function try_extract_code_macro(source, directive_start, code_macros, code_macro_bodies)
|
local function try_extract_code_macro(source, directive_start, code_macros, code_macro_bodies)
|
||||||
local rest = duffle.skip_ws_and_cmt(source, directive_start + 1)
|
local rest = duffle.skip_ws_and_cmt(source, directive_start + 1)
|
||||||
local kw, kw_end = duffle.read_ident(source, rest)
|
local kw, kw_end = duffle.read_ident(source, rest)
|
||||||
if kw ~= "define" then return end
|
if kw ~= "define" then return end
|
||||||
|
|
||||||
local after_kw = duffle.skip_ws_and_cmt(source, kw_end)
|
local after_kw = duffle.skip_ws_and_cmt(source, kw_end)
|
||||||
local macro_name, macro_end = duffle.read_ident(source, after_kw)
|
local macro_name, macro_end = duffle.read_ident(source, after_kw)
|
||||||
if not macro_name then return end
|
if not macro_name then return end
|
||||||
if not is_r_code_macro(macro_name) then return end
|
if not is_r_code_macro(macro_name) then return end
|
||||||
|
|
||||||
@@ -1043,7 +1056,7 @@ local function try_extract_code_macro(source, directive_start, code_macros, code
|
|||||||
local rhs_pos = duffle.skip_ws_and_cmt(source, macro_end)
|
local rhs_pos = duffle.skip_ws_and_cmt(source, macro_end)
|
||||||
local rhs_end = duffle.find_byte(source, BYTE_NEWLINE, rhs_pos) or (#source + 1)
|
local rhs_end = duffle.find_byte(source, BYTE_NEWLINE, rhs_pos) or (#source + 1)
|
||||||
local rhs_text = duffle.trim(source:sub(rhs_pos, rhs_end - 1))
|
local rhs_text = duffle.trim(source:sub(rhs_pos, rhs_end - 1))
|
||||||
if rhs_text ~= "" then
|
if rhs_text ~= "" then
|
||||||
code_macro_bodies[macro_name] = rhs_text
|
code_macro_bodies[macro_name] = rhs_text
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1081,8 +1094,7 @@ local function scan_source_pre_pass(source, code_macros, code_macro_bodies)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Check whether `atom_reg` appears as a BARE token at byte position `pos`.
|
-- Check whether `atom_reg` appears as a BARE token at byte position `pos`.
|
||||||
-- `pos` should be at the first non-whitespace byte after the value position
|
-- `pos` should be at the first non-whitespace byte after the value position (caller is responsible for skipping whitespace before calling).
|
||||||
-- (caller is responsible for skipping whitespace before calling).
|
|
||||||
-- Returns (true, end_pos) iff the identifier at `pos` is exactly `atom_reg` AND the byte immediately before `pos` is a non-word char
|
-- Returns (true, end_pos) iff the identifier at `pos` is exactly `atom_reg` AND the byte immediately before `pos` is a non-word char
|
||||||
-- (whitespace, `,`, `=`, `{`, `}`, `(`, `)`, `[`, `]`, etc.) AND the byte immediately after the ident is a non-word char or end-of-input.
|
-- (whitespace, `,`, `=`, `{`, `}`, `(`, `)`, `[`, `]`, etc.) AND the byte immediately after the ident is a non-word char or end-of-input.
|
||||||
-- Returns (false, pos) otherwise.
|
-- Returns (false, pos) otherwise.
|
||||||
@@ -1090,7 +1102,7 @@ local function check_bare_atom_reg(body, pos)
|
|||||||
if pos > #body then return false, pos end
|
if pos > #body then return false, pos end
|
||||||
|
|
||||||
local ident, ident_end = duffle.read_ident(body, pos)
|
local ident, ident_end = duffle.read_ident(body, pos)
|
||||||
if ident ~= "atom_reg" then return false, pos end
|
if ident ~= "atom_reg" then return false, pos end
|
||||||
|
|
||||||
-- Word-boundary check on the LEFT side: the byte at `pos - 1` must NOT be an alphanumeric/underscore byte (otherwise `atom_reg` is a suffix of `_not_atom_reg` or similar).
|
-- Word-boundary check on the LEFT side: the byte at `pos - 1` must NOT be an alphanumeric/underscore byte (otherwise `atom_reg` is a suffix of `_not_atom_reg` or similar).
|
||||||
if pos > 1 then
|
if pos > 1 then
|
||||||
@@ -1277,22 +1289,22 @@ end
|
|||||||
local function parse_atom_info_after_decl(source, after_paren, raw_name, line_of, out, dest)
|
local function parse_atom_info_after_decl(source, after_paren, raw_name, line_of, out, dest)
|
||||||
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
||||||
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
||||||
if look_ident ~= "atom_info" then return after_paren end
|
if look_ident ~= "atom_info" then return after_paren end
|
||||||
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
||||||
if source:sub(info_open, info_open) ~= "(" then return after_paren end
|
if source:sub(info_open, info_open) ~= "(" then return after_paren end
|
||||||
local info_inner, info_after = duffle.read_parens(source, info_open)
|
local info_inner, info_after = duffle.read_parens(source, info_open)
|
||||||
if not info_inner then return after_paren end
|
if not info_inner then return after_paren end
|
||||||
local info_line = line_of(info_open)
|
local info_line = line_of(info_open)
|
||||||
local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line)
|
local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line)
|
||||||
dest = dest or out.atom_infos
|
dest = dest or out.atom_infos
|
||||||
dest[#dest + 1] = {
|
dest[#dest + 1] = {
|
||||||
atom_name = raw_name or "?", binds = ai_binds,
|
atom_name = raw_name or "?", binds = ai_binds,
|
||||||
reads = ai_reads or {}, writes = ai_writes or {},
|
reads = ai_reads or {}, writes = ai_writes or {},
|
||||||
view = ai_view,
|
view = ai_view,
|
||||||
reg_type_overrides = ai_overrides,
|
reg_type_overrides = ai_overrides,
|
||||||
ctx_atom = ai_ctx,
|
ctx_atom = ai_ctx,
|
||||||
phase = ai_phase,
|
phase = ai_phase,
|
||||||
info_line = line_of(lookahead),
|
info_line = line_of(lookahead),
|
||||||
}
|
}
|
||||||
if ai_view and raw_name then
|
if ai_view and raw_name then
|
||||||
out.atom_views[raw_name] = {
|
out.atom_views[raw_name] = {
|
||||||
@@ -1321,24 +1333,41 @@ end
|
|||||||
|
|
||||||
local DECL_FORMS = {
|
local DECL_FORMS = {
|
||||||
MipsAtom_ = {
|
MipsAtom_ = {
|
||||||
kind = "atom", name = "paren_ident", body = "braces_after",
|
kind = "atom",
|
||||||
info_dest = "atom_infos", strip = false,
|
name = "paren_ident",
|
||||||
|
body = "braces_after",
|
||||||
|
info_dest = "atom_infos",
|
||||||
|
strip = false,
|
||||||
},
|
},
|
||||||
MipsAtom_Proc_ = {
|
MipsAtom_Proc_ = {
|
||||||
kind = "atom_proc", name = "backward_atom_proc", body = "last_brace_in_args",
|
kind = "atom_proc",
|
||||||
info_dest = "atom_infos", strip = false, after = "reguse_hook",
|
name = "backward_atom_proc",
|
||||||
|
body = "last_brace_in_args",
|
||||||
|
info_dest = "atom_infos",
|
||||||
|
strip = false,
|
||||||
|
after = "reguse_hook",
|
||||||
},
|
},
|
||||||
MipsAtomComp_ = {
|
MipsAtomComp_ = {
|
||||||
kind = "comp_bare", name = "paren_ident", body = "braces_after",
|
kind = "comp_bare",
|
||||||
info_dest = "component_atom_infos", strip = "ac_",
|
name = "paren_ident",
|
||||||
|
body = "braces_after",
|
||||||
|
info_dest = "component_atom_infos",
|
||||||
|
strip = "ac_",
|
||||||
},
|
},
|
||||||
MipsAtomComp_Proc_ = {
|
MipsAtomComp_Proc_ = {
|
||||||
kind = "comp_proc", name = "backward_fi", body = "last_brace_in_args",
|
kind = "comp_proc",
|
||||||
info_dest = nil, strip = "ac_",
|
name = "backward_fi",
|
||||||
|
body = "last_brace_in_args",
|
||||||
|
info_dest = nil,
|
||||||
|
strip = "ac_",
|
||||||
},
|
},
|
||||||
MipsAtomComp_ProcMap_ = {
|
MipsAtomComp_ProcMap_ = {
|
||||||
kind = "comp_proc", name = "backward_fi", body = "comma_arg_2",
|
kind = "comp_proc",
|
||||||
info_dest = nil, strip = "ac_", after = "map_command_hook",
|
name = "backward_fi",
|
||||||
|
body = "comma_arg_2",
|
||||||
|
info_dest = nil,
|
||||||
|
strip = "ac_",
|
||||||
|
after = "map_command_hook",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1357,7 +1386,7 @@ local function last_brace_body(inner, open_paren)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function reguse_hook(source, pos, line_of, out, extras)
|
local function reguse_hook(source, pos, line_of, out, extras)
|
||||||
local entry = out.atoms[#out.atoms]
|
local entry = out.atoms[#out.atoms]
|
||||||
if not entry then return end
|
if not entry then return end
|
||||||
local reg_use_schema_name, reg_use_param_name
|
local reg_use_schema_name, reg_use_param_name
|
||||||
if extras.args_inner then
|
if extras.args_inner then
|
||||||
@@ -1396,10 +1425,10 @@ end
|
|||||||
|
|
||||||
local function parse_decl_form(source, pos, ident_end, line_of, out)
|
local function parse_decl_form(source, pos, ident_end, line_of, out)
|
||||||
local ident = duffle.read_ident(source, pos)
|
local ident = duffle.read_ident(source, pos)
|
||||||
local form = ident and DECL_FORMS[ident]
|
local form = ident and DECL_FORMS[ident]
|
||||||
if not form then return ident_end end
|
if not form then return ident_end end
|
||||||
|
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
local extras = {}
|
local extras = {}
|
||||||
@@ -1428,8 +1457,7 @@ local function parse_decl_form(source, pos, ident_end, line_of, out)
|
|||||||
if form.body == "braces_after" then
|
if form.body == "braces_after" then
|
||||||
local brace_search = after_paren
|
local brace_search = after_paren
|
||||||
if info_dest then
|
if info_dest then
|
||||||
brace_search = parse_atom_info_after_decl(
|
brace_search = parse_atom_info_after_decl(source, after_paren, name, line_of, out, info_dest)
|
||||||
source, after_paren, name, line_of, out, info_dest)
|
|
||||||
end
|
end
|
||||||
local after_brace
|
local after_brace
|
||||||
body, after_brace, body_off = find_body_braces(source, brace_search, open_paren + 1)
|
body, after_brace, body_off = find_body_braces(source, brace_search, open_paren + 1)
|
||||||
@@ -1440,11 +1468,8 @@ local function parse_decl_form(source, pos, ident_end, line_of, out)
|
|||||||
if not body then return after_paren end
|
if not body then return after_paren end
|
||||||
resume = after_paren
|
resume = after_paren
|
||||||
if form.info_dest then
|
if form.info_dest then
|
||||||
if extras.after_func_paren then
|
if extras.after_func_paren then parse_atom_info_after_decl(source, extras.after_func_paren, name, line_of, out, out.atom_infos)
|
||||||
parse_atom_info_after_decl(
|
else parse_atom_info_after_decl(source, pos, name, line_of, out, out.atom_infos)
|
||||||
source, extras.after_func_paren, name, line_of, out, out.atom_infos)
|
|
||||||
else
|
|
||||||
parse_atom_info_after_decl(source, pos, name, line_of, out, out.atom_infos)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif form.body == "comma_arg_2" then
|
elseif form.body == "comma_arg_2" then
|
||||||
@@ -1489,8 +1514,8 @@ local function parse_mips_code(source, pos, ident_end, line_of, out)
|
|||||||
return ident_end
|
return ident_end
|
||||||
end
|
end
|
||||||
|
|
||||||
local atom_name = next_ident:sub(6)
|
local atom_name = next_ident:sub(6)
|
||||||
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end)
|
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
register_raw_atom(out, line_of(pos), atom_name, body, body_off, atom_name, pos)
|
register_raw_atom(out, line_of(pos), atom_name, body, body_off, atom_name, pos)
|
||||||
|
|
||||||
@@ -1579,11 +1604,24 @@ local function register_typedef_alias(underlying, name, pos, line_of, out)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function register_array_type(name, elem, counts, pos, line_of, out)
|
||||||
|
out.type_name_registry[name] = {
|
||||||
|
name = name,
|
||||||
|
kind = "array",
|
||||||
|
elem = elem,
|
||||||
|
counts = counts,
|
||||||
|
byte_size = nil,
|
||||||
|
source_line = line_of(pos),
|
||||||
|
source_file = out._source_file,
|
||||||
|
pointer_depth = 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
local parse_reg_use_schema_body
|
local parse_reg_use_schema_body
|
||||||
|
|
||||||
local function fields_for_reg_type(type_name, type_registry)
|
local function fields_for_reg_type(type_name, type_registry)
|
||||||
local reg_name = "Reg_" .. type_name
|
local reg_name = "Reg_" .. type_name
|
||||||
local entry = type_registry and type_registry[reg_name]
|
local entry = type_registry and type_registry[reg_name]
|
||||||
if entry and entry.fields and #entry.fields > 0 then
|
if entry and entry.fields and #entry.fields > 0 then
|
||||||
local names = {}
|
local names = {}
|
||||||
for _, field in ipairs(entry.fields) do
|
for _, field in ipairs(entry.fields) do
|
||||||
@@ -1607,11 +1645,11 @@ end
|
|||||||
parse_reg_use_schema_body = function(body, type_registry, opts)
|
parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local require_types = opts.require_types == true
|
local require_types = opts.require_types == true
|
||||||
local pending = false
|
local pending = false
|
||||||
local slots = {}
|
local slots = {}
|
||||||
local alias_to_slot = {}
|
local alias_to_slot = {}
|
||||||
local slot_names = {}
|
local slot_names = {}
|
||||||
local errors = {}
|
local errors = {}
|
||||||
|
|
||||||
local function add_alias(path, slot)
|
local function add_alias(path, slot)
|
||||||
if alias_to_slot[path] then
|
if alias_to_slot[path] then
|
||||||
@@ -1637,7 +1675,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
local names = {}
|
local names = {}
|
||||||
while pos <= #text do
|
while pos <= #text do
|
||||||
pos = duffle.skip_ws_and_cmt(text, pos)
|
pos = duffle.skip_ws_and_cmt(text, pos)
|
||||||
local name, name_end = duffle.read_ident(text, pos)
|
local name, name_end = duffle.read_ident(text, pos)
|
||||||
if not name then return nil, pos end
|
if not name then return nil, pos end
|
||||||
names[#names + 1] = name
|
names[#names + 1] = name
|
||||||
pos = duffle.skip_ws_and_cmt(text, name_end)
|
pos = duffle.skip_ws_and_cmt(text, name_end)
|
||||||
@@ -1674,9 +1712,9 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local views = {}
|
local views = {}
|
||||||
local union_readonly = nil
|
local union_readonly = nil
|
||||||
local inner_pos = 1
|
local inner_pos = 1
|
||||||
|
|
||||||
local function note_readonly(flag)
|
local function note_readonly(flag)
|
||||||
if union_readonly == nil then
|
if union_readonly == nil then
|
||||||
@@ -1761,7 +1799,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
while s_pos <= #struct_inner do
|
while s_pos <= #struct_inner do
|
||||||
s_pos = duffle.skip_ws_and_cmt(struct_inner, s_pos)
|
s_pos = duffle.skip_ws_and_cmt(struct_inner, s_pos)
|
||||||
if s_pos > #struct_inner then break end
|
if s_pos > #struct_inner then break end
|
||||||
local s_ty, s_ty_end = duffle.read_ident(struct_inner, s_pos)
|
local s_ty, s_ty_end = duffle.read_ident(struct_inner, s_pos)
|
||||||
if not s_ty then
|
if not s_ty then
|
||||||
s_pos = s_pos + 1
|
s_pos = s_pos + 1
|
||||||
goto continue_struct
|
goto continue_struct
|
||||||
@@ -1772,14 +1810,14 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local type_inner, after_paren = duffle.read_parens(struct_inner, after_ty)
|
local type_inner, after_paren = duffle.read_parens(struct_inner, after_ty)
|
||||||
if not type_inner then
|
if not type_inner then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local typed_fields = fields_for_reg_type(duffle.trim(type_inner), type_registry)
|
local typed_fields = fields_for_reg_type(duffle.trim(type_inner), type_registry)
|
||||||
after_paren = duffle.skip_ws_and_cmt(struct_inner, after_paren)
|
after_paren = duffle.skip_ws_and_cmt(struct_inner, after_paren)
|
||||||
local inst_names, new_s = parse_reg_names(struct_inner, after_paren)
|
local inst_names, new_s = parse_reg_names(struct_inner, after_paren)
|
||||||
if not inst_names or #inst_names == 0 then
|
if not inst_names or #inst_names == 0 then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
@@ -1800,7 +1838,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
end
|
end
|
||||||
s_pos = new_s
|
s_pos = new_s
|
||||||
elseif s_ty == "Reg" then
|
elseif s_ty == "Reg" then
|
||||||
local s_after = duffle.skip_ws_and_cmt(struct_inner, s_ty_end)
|
local s_after = duffle.skip_ws_and_cmt(struct_inner, s_ty_end)
|
||||||
local s_readonly = false
|
local s_readonly = false
|
||||||
local maybe_const, maybe_end = duffle.read_ident(struct_inner, s_after)
|
local maybe_const, maybe_end = duffle.read_ident(struct_inner, s_after)
|
||||||
if maybe_const == "const" then
|
if maybe_const == "const" then
|
||||||
@@ -1808,7 +1846,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
s_after = duffle.skip_ws_and_cmt(struct_inner, maybe_end)
|
s_after = duffle.skip_ws_and_cmt(struct_inner, maybe_end)
|
||||||
end
|
end
|
||||||
if not note_readonly(s_readonly) then return nil, errors end
|
if not note_readonly(s_readonly) then return nil, errors end
|
||||||
local names, new_s = parse_reg_names(struct_inner, s_after)
|
local names, new_s = parse_reg_names(struct_inner, s_after)
|
||||||
if not names or #names == 0 then
|
if not names or #names == 0 then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
@@ -1832,12 +1870,12 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
if inner:sub(inner_pos, inner_pos) == ";" then inner_pos = inner_pos + 1 end
|
if inner:sub(inner_pos, inner_pos) == ";" then inner_pos = inner_pos + 1 end
|
||||||
|
|
||||||
elseif m_type == "Reg" then
|
elseif m_type == "Reg" then
|
||||||
local m_after = duffle.skip_ws_and_cmt(inner, m_type_end)
|
local m_after = duffle.skip_ws_and_cmt(inner, m_type_end)
|
||||||
local m_readonly = false
|
local m_readonly = false
|
||||||
local maybe_const, maybe_end = duffle.read_ident(inner, m_after)
|
local maybe_const, maybe_end = duffle.read_ident(inner, m_after)
|
||||||
if maybe_const == "const" then
|
if maybe_const == "const" then
|
||||||
m_readonly = true
|
m_readonly = true
|
||||||
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
|
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
|
||||||
end
|
end
|
||||||
if not note_readonly(m_readonly) then return nil, errors end
|
if not note_readonly(m_readonly) then return nil, errors end
|
||||||
local names, new_inner = parse_reg_names(inner, m_after)
|
local names, new_inner = parse_reg_names(inner, m_after)
|
||||||
@@ -1854,7 +1892,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
::continue_inner::
|
::continue_inner::
|
||||||
end
|
end
|
||||||
|
|
||||||
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
||||||
local inst_name, inst_end = duffle.read_ident(body, after_close)
|
local inst_name, inst_end = duffle.read_ident(body, after_close)
|
||||||
|
|
||||||
if #views == 0 then
|
if #views == 0 then
|
||||||
@@ -1932,7 +1970,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local type_inner, after_paren = duffle.read_parens(body, after)
|
local type_inner, after_paren = duffle.read_parens(body, after)
|
||||||
if not type_inner then
|
if not type_inner then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
@@ -1954,7 +1992,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
readonly = true
|
readonly = true
|
||||||
after = duffle.skip_ws_and_cmt(body, maybe_end)
|
after = duffle.skip_ws_and_cmt(body, maybe_end)
|
||||||
end
|
end
|
||||||
local names, new_pos = parse_reg_names(body, after)
|
local names, new_pos = parse_reg_names(body, after)
|
||||||
if not names or #names == 0 then
|
if not names or #names == 0 then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
@@ -1992,6 +2030,86 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
return { slots = slots, alias_to_slot = alias_to_slot, pending = pending }, errors
|
return { slots = slots, alias_to_slot = alias_to_slot, pending = pending }, errors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ── Shape 1: `typedef Struct_(<name>) { <body> } <alias>;` ────────────
|
||||||
|
local function parse_typedef_struct(source, pos, id2_end, line_of, out, after_typedef)
|
||||||
|
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
|
if not inner then return id2_end end
|
||||||
|
local name = duffle.trim(inner)
|
||||||
|
|
||||||
|
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
|
if not body then return after_brace end
|
||||||
|
register_struct_type(body, name, pos, line_of, out)
|
||||||
|
if name:sub(1, 7) == "RegUse_" then
|
||||||
|
local schema, schema_errors = parse_reg_use_schema_body(body, out.type_name_registry)
|
||||||
|
if schema then
|
||||||
|
schema.name = name
|
||||||
|
schema.source_file = out._source_file
|
||||||
|
schema.source_line = line_of(pos)
|
||||||
|
out.reg_use_schemas[name] = schema
|
||||||
|
end
|
||||||
|
for _, err in ipairs(schema_errors or {}) do
|
||||||
|
err.schema_name = name
|
||||||
|
err.source_file = out._source_file
|
||||||
|
err.source_line = line_of(pos)
|
||||||
|
out.reg_use_errors[#out.reg_use_errors + 1] = err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
|
return after_brace
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ── Shape 2: `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
||||||
|
local function parse_typedef_enum (source, pos, id2_end, line_of, out, after_typedef)
|
||||||
|
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
|
if not inner then return id2_end end
|
||||||
|
-- Split `inner` on the first top-level comma into (<underlying>, <name>).
|
||||||
|
local args = duffle.split_top_level_commas(inner)
|
||||||
|
if #args < 2 then return after_paren end
|
||||||
|
local underlying = duffle.trim(args[1])
|
||||||
|
local name = duffle.trim(args[2])
|
||||||
|
|
||||||
|
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
|
if not body then return after_brace end
|
||||||
|
register_enum_type(underlying, name, body, pos, line_of, out)
|
||||||
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
|
return after_brace
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shape 4 (TSet_ at id2 position): no preceding underlying span.
|
||||||
|
local function parse_typedef_tset (source, pos, id2_end, line_of, out, after_typedef)
|
||||||
|
local inner, after_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
|
if not inner then return id2_end end
|
||||||
|
local tset_name = duffle.trim(inner)
|
||||||
|
-- Empty underlying span is acceptable; the TSet_ wrapper itself encodes the alias identity (per the duffle TSet_ convention).
|
||||||
|
register_typedef_alias("", tset_name, pos, line_of, out)
|
||||||
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
|
return after_paren
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_typedef_array(source, pos, id2_end, line_of, out, after_typedef)
|
||||||
|
local inner, after_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
|
if not inner then return id2_end end
|
||||||
|
local args = duffle.split_top_level_commas(inner)
|
||||||
|
if #args < 2 then return after_paren end
|
||||||
|
local elem = duffle.trim(args[1])
|
||||||
|
local len = tonumber(duffle.trim(args[2]), 10)
|
||||||
|
if type(elem) ~= "string" or elem == "" or not len or len < 1 or len ~= math.floor(len) then
|
||||||
|
return after_paren
|
||||||
|
end
|
||||||
|
local name = "A" .. tostring(len) .. "_" .. elem
|
||||||
|
register_array_type(name, elem, { len }, pos, line_of, out)
|
||||||
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
|
local semi = duffle.find_byte(source, BYTE_SEMI, after_paren)
|
||||||
|
return semi and (semi + 1) or after_paren
|
||||||
|
end
|
||||||
|
|
||||||
|
local TYPE_FORMS = {
|
||||||
|
Struct_ = parse_typedef_struct,
|
||||||
|
Enum_ = parse_typedef_enum,
|
||||||
|
TSet_ = parse_typedef_tset,
|
||||||
|
Array_ = parse_typedef_array,
|
||||||
|
}
|
||||||
|
|
||||||
--- Parse: `typedef` declarations.
|
--- Parse: `typedef` declarations.
|
||||||
---
|
---
|
||||||
--- Recognizes four shapes:
|
--- Recognizes four shapes:
|
||||||
@@ -2015,49 +2133,9 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
||||||
if not id2 then return ident_end end
|
if not id2 then return ident_end end
|
||||||
|
local form = TYPE_FORMS[id2]
|
||||||
-- ── Shape 1: `typedef Struct_(<name>) { <body> } <alias>;` ────────────
|
if form then
|
||||||
if id2 == "Struct_" then
|
return form(source, pos, id2_end, line_of, out, after_typedef)
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
|
||||||
if not inner then return id2_end end
|
|
||||||
local name = duffle.trim(inner)
|
|
||||||
|
|
||||||
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
|
||||||
if not body then return after_brace end
|
|
||||||
register_struct_type(body, name, pos, line_of, out)
|
|
||||||
if name:sub(1, 7) == "RegUse_" then
|
|
||||||
local schema, schema_errors = parse_reg_use_schema_body(body, out.type_name_registry)
|
|
||||||
if schema then
|
|
||||||
schema.name = name
|
|
||||||
schema.source_file = out._source_file
|
|
||||||
schema.source_line = line_of(pos)
|
|
||||||
out.reg_use_schemas[name] = schema
|
|
||||||
end
|
|
||||||
for _, err in ipairs(schema_errors or {}) do
|
|
||||||
err.schema_name = name
|
|
||||||
err.source_file = out._source_file
|
|
||||||
err.source_line = line_of(pos)
|
|
||||||
out.reg_use_errors[#out.reg_use_errors + 1] = err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
|
||||||
return after_brace
|
|
||||||
|
|
||||||
-- ── Shape 2: `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
|
||||||
elseif id2 == "Enum_" then
|
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
|
||||||
if not inner then return id2_end end
|
|
||||||
-- Split `inner` on the first top-level comma into (<underlying>, <name>).
|
|
||||||
local args = duffle.split_top_level_commas(inner)
|
|
||||||
if #args < 2 then return after_paren end
|
|
||||||
local underlying = duffle.trim(args[1])
|
|
||||||
local name = duffle.trim(args[2])
|
|
||||||
|
|
||||||
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
|
||||||
if not body then return after_brace end
|
|
||||||
register_enum_type(underlying, name, body, pos, line_of, out)
|
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
|
||||||
return after_brace
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ── Shapes 3 + 4: `typedef <span> <alias>;` or
|
-- ── Shapes 3 + 4: `typedef <span> <alias>;` or
|
||||||
@@ -2084,17 +2162,6 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
local semi_pos = duffle.find_byte(source, BYTE_SEMI, id2_end)
|
local semi_pos = duffle.find_byte(source, BYTE_SEMI, id2_end)
|
||||||
if not semi_pos then return id2_end end
|
if not semi_pos then return id2_end end
|
||||||
|
|
||||||
-- Shape 4 (TSet_ at id2 position): no preceding underlying span.
|
|
||||||
if id2 == "TSet_" then
|
|
||||||
local inner, after_paren = read_parens_after(source, id2_end, id2_end)
|
|
||||||
if not inner then return id2_end end
|
|
||||||
local tset_name = duffle.trim(inner)
|
|
||||||
-- Empty underlying span is acceptable; the TSet_ wrapper itself encodes the alias identity (per the duffle TSet_ convention).
|
|
||||||
register_typedef_alias("", tset_name, pos, line_of, out)
|
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
|
||||||
return after_paren
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Walk idents forward to find the alias ident (last ident before `;`), or the TSet_(<arg>) form (capture the arg, use it as the alias).
|
-- Walk idents forward to find the alias ident (last ident before `;`), or the TSet_(<arg>) form (capture the arg, use it as the alias).
|
||||||
local last_ident = nil
|
local last_ident = nil
|
||||||
local last_ident_pos = nil
|
local last_ident_pos = nil
|
||||||
@@ -2129,6 +2196,33 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- C-array suffix: `typedef S2 A3x3_S2[3][3];` → kind=array, not a typedef alias.
|
||||||
|
-- Malformed `[` / non-decimal dims fall through to the typedef-alias path.
|
||||||
|
if last_ident and not tset_arg then
|
||||||
|
local dims = {}
|
||||||
|
local dim_scan = duffle.skip_ws_and_cmt(source, last_ident_end)
|
||||||
|
while dim_scan < semi_pos and source:sub(dim_scan, dim_scan) == "[" do
|
||||||
|
local close = source:find("]", dim_scan + 1, true)
|
||||||
|
if not close or close >= semi_pos then
|
||||||
|
dims = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local n = tonumber(duffle.trim(source:sub(dim_scan + 1, close - 1)), 10)
|
||||||
|
if not n or n < 1 or n ~= math.floor(n) then
|
||||||
|
dims = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
dims[#dims + 1] = n
|
||||||
|
dim_scan = duffle.skip_ws_and_cmt(source, close + 1)
|
||||||
|
end
|
||||||
|
if dims and #dims > 0 then
|
||||||
|
local elem = duffle.trim(source:sub(after_typedef, last_ident_pos - 1))
|
||||||
|
register_array_type(last_ident, elem, dims, pos, line_of, out)
|
||||||
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
|
return semi_pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if tset_arg then
|
if tset_arg then
|
||||||
-- Shape 4: alias is the TSet_ argument; the underlying span is the trimmed text from the start of id2 up to (but not including) the TSet_ ident.
|
-- Shape 4: alias is the TSet_ argument; the underlying span is the trimmed text from the start of id2 up to (but not including) the TSet_ ident.
|
||||||
local underlying_span = source:sub(after_typedef, tset_pos - 1)
|
local underlying_span = source:sub(after_typedef, tset_pos - 1)
|
||||||
@@ -2361,12 +2455,12 @@ local function parse_addrs_assign(source, pos, ident_end, line_of, out)
|
|||||||
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
if source:sub(after, after) ~= "[" then return ident_end end
|
if source:sub(after, after) ~= "[" then return ident_end end
|
||||||
local inner, after_br = duffle.read_brackets(source, after)
|
local inner, after_br = duffle.read_brackets(source, after)
|
||||||
local idx = inner and tonumber(duffle.trim(inner))
|
local idx = inner and tonumber(duffle.trim(inner))
|
||||||
after_br = duffle.skip_ws_and_cmt(source, after_br or after)
|
after_br = duffle.skip_ws_and_cmt(source, after_br or after)
|
||||||
if not (idx and source:sub(after_br, after_br) == "=") then
|
if not (idx and source:sub(after_br, after_br) == "=") then
|
||||||
return after_br or (after + 1)
|
return after_br or (after + 1)
|
||||||
end
|
end
|
||||||
local rhs = duffle.skip_ws_and_cmt(source, after_br + 1)
|
local rhs = duffle.skip_ws_and_cmt(source, after_br + 1)
|
||||||
local rhs_ident = duffle.read_ident(source, rhs)
|
local rhs_ident = duffle.read_ident(source, rhs)
|
||||||
if rhs_ident then out._addrs[idx] = rhs_ident end
|
if rhs_ident then out._addrs[idx] = rhs_ident end
|
||||||
return rhs
|
return rhs
|
||||||
@@ -2376,7 +2470,7 @@ local function parse_tb_emit_(source, pos, ident_end, line_of, out)
|
|||||||
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
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)
|
local inner, after_p = duffle.read_parens(source, after)
|
||||||
local name = duffle.trim(inner or ""):match("^([%w_]+)")
|
local name = duffle.trim(inner or ""):match("^([%w_]+)")
|
||||||
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
|
||||||
@@ -2388,14 +2482,12 @@ local function parse_tb_emit(source, pos, ident_end, line_of, out)
|
|||||||
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
local after = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
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)
|
local inner, after_p = duffle.read_parens(source, after)
|
||||||
local args = duffle.split_top_level_commas(inner or "")
|
local args = duffle.split_top_level_commas(inner or "")
|
||||||
local last = duffle.trim(args[#args] or "")
|
local last = duffle.trim(args[#args] or "")
|
||||||
local idx = last:match("^addrs%s*%[%s*(%d+)%s*%]$")
|
local idx = last:match("^addrs%s*%[%s*(%d+)%s*%]$")
|
||||||
local name
|
local name
|
||||||
if idx then
|
if idx then name = out._addrs[tonumber(idx)]
|
||||||
name = out._addrs[tonumber(idx)]
|
else name = last:match("([%w_]+)$")
|
||||||
else
|
|
||||||
name = last:match("([%w_]+)$")
|
|
||||||
end
|
end
|
||||||
if name then
|
if name then
|
||||||
out._chain = out._chain or {}
|
out._chain = out._chain or {}
|
||||||
@@ -2419,18 +2511,17 @@ local C_STMT_PARSERS = {
|
|||||||
-- Adding a new construct = 1 row here + 1 parser function above.
|
-- Adding a new construct = 1 row here + 1 parser function above.
|
||||||
|
|
||||||
local DECL_PARSERS = {
|
local DECL_PARSERS = {
|
||||||
MipsAtom_ = parse_decl_form,
|
MipsAtom_ = parse_decl_form,
|
||||||
MipsAtom_Proc_ = parse_decl_form,
|
MipsAtom_Proc_ = parse_decl_form,
|
||||||
MipsAtomComp_ = parse_decl_form,
|
MipsAtomComp_ = parse_decl_form,
|
||||||
MipsAtomComp_Proc_ = parse_decl_form,
|
MipsAtomComp_Proc_ = parse_decl_form,
|
||||||
MipsAtomComp_ProcMap_ = parse_decl_form,
|
MipsAtomComp_ProcMap_ = parse_decl_form,
|
||||||
-- `atom_dbg_skip` is the only debug-skip parser entry. Every other
|
-- `atom_dbg_skip` is the only debug-skip parser entry.
|
||||||
-- identifier follows the ordinary unrelated-token path; there is no alias.
|
-- Every other identifier follows the ordinary unrelated-token path; there is no alias.
|
||||||
atom_dbg_skip = parse_dbg_skip_marker,
|
atom_dbg_skip = parse_dbg_skip_marker,
|
||||||
atom_dbg_reg_default = parse_atom_dbg_reg_default,
|
atom_dbg_reg_default = parse_atom_dbg_reg_default,
|
||||||
-- `atom_auto_reg(atom, R_<Sym>)` and `phase_auto_reg(phase, R_<Sym>)` populate per-source
|
-- `atom_auto_reg(atom, R_<Sym>)` and `phase_auto_reg(phase, R_<Sym>)` populate per-source `out.atom_auto_regs` / `out.phase_auto_regs`;
|
||||||
-- `out.atom_auto_regs` / `out.phase_auto_regs`; the cross-source merge lands in
|
-- The cross-source merge lands in `corpus.atom_auto_regs` / `corpus.phase_auto_regs` (first-wins).
|
||||||
-- `corpus.atom_auto_regs` / `corpus.phase_auto_regs` (first-wins).
|
|
||||||
atom_auto_reg = parse_auto_reg_marker,
|
atom_auto_reg = parse_auto_reg_marker,
|
||||||
phase_auto_reg = parse_auto_reg_marker,
|
phase_auto_reg = parse_auto_reg_marker,
|
||||||
MipsCode = parse_mips_code,
|
MipsCode = parse_mips_code,
|
||||||
@@ -2457,50 +2548,48 @@ local DECL_PARSERS = {
|
|||||||
local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
||||||
local line_of = duffle.LineIndex(source)
|
local line_of = duffle.LineIndex(source)
|
||||||
local out = {
|
local out = {
|
||||||
atoms = {},
|
atoms = {},
|
||||||
raw_atoms = {},
|
raw_atoms = {},
|
||||||
binds = {},
|
binds = {},
|
||||||
atom_infos = {},
|
atom_infos = {},
|
||||||
component_atom_infos = {},
|
component_atom_infos = {},
|
||||||
macros = {},
|
macros = {},
|
||||||
-- Raw marker evidence for annotation validation. The `debug_skip` boolean
|
-- Raw marker evidence for annotation validation. The `debug_skip` boolean is stamped on the declaration record itself; the projection lives on AtomEntry.debug_skip.
|
||||||
-- is stamped on the declaration record itself; the projection lives on AtomEntry.debug_skip.
|
debug_skip_markers = {},
|
||||||
debug_skip_markers = {},
|
types = {},
|
||||||
types = {},
|
atom_views = {},
|
||||||
atom_views = {},
|
|
||||||
-- Per-source projection for `atom_auto_reg(<atom>, R_<Sym>)` markers.
|
-- Per-source projection for `atom_auto_reg(<atom>, R_<Sym>)` markers.
|
||||||
-- Each entry is keyed by atom_name; the inner table maps `R_<Sym>` -> `R_<Sym>` (raw LHS sym).
|
-- Each entry is keyed by atom_name; the inner table maps `R_<Sym>` -> `R_<Sym>` (raw LHS sym).
|
||||||
-- Merged cross-source into `corpus.atom_auto_regs` (first-wins).
|
-- Merged cross-source into `corpus.atom_auto_regs` (first-wins).
|
||||||
atom_auto_regs = {},
|
atom_auto_regs = {},
|
||||||
-- Per-source projection for `phase_auto_reg(<phase>, R_<Sym>)` markers.
|
-- Per-source projection for `phase_auto_reg(<phase>, R_<Sym>)` markers.
|
||||||
-- Each entry is keyed by phase_label; the inner table maps `R_<Sym>` -> `R_<Sym>` (raw LHS sym).
|
-- Each entry is keyed by phase_label; the inner table maps `R_<Sym>` -> `R_<Sym>` (raw LHS sym).
|
||||||
-- Merged cross-source into `corpus.phase_auto_regs` (first-wins).
|
-- Merged cross-source into `corpus.phase_auto_regs` (first-wins).
|
||||||
phase_auto_regs = {},
|
phase_auto_regs = {},
|
||||||
line_of = line_of,
|
line_of = line_of,
|
||||||
-- Source-derived register-alias registry (atom_reg opt-in entries).
|
-- Source-derived register-alias registry (atom_reg opt-in entries).
|
||||||
-- Keys are full R_* idents (never stripped); see parse_enum / parse_enum_body.
|
-- Keys are full R_* idents (never stripped); see parse_enum / parse_enum_body.
|
||||||
register_alias_registry = {},
|
register_alias_registry = {},
|
||||||
-- Source-derived type-name registry.
|
-- Source-derived type-name registry.
|
||||||
-- Populated from `typedef Struct_(...)`, `typedef Enum_(...)`, `typedef <type> <alias>`, and `typedef <type> TSet_(<name>)` declarations.
|
-- Populated from `typedef Struct_(...)`, `typedef Enum_(...)`, `typedef <type> <alias>`, and `typedef <type> TSet_(<name>)` declarations.
|
||||||
-- The propagation pass at the end of `scan_source()` resolves byte_size via the builtin map,
|
-- The propagation pass at the end of `scan_source()` resolves byte_size via the builtin map,
|
||||||
-- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums.
|
-- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums. See `propagate_type_sizes()` below.
|
||||||
-- See `propagate_type_sizes()` below.
|
type_name_registry = {},
|
||||||
type_name_registry = {},
|
reg_use_schemas = {},
|
||||||
reg_use_schemas = {},
|
tape_chains = {},
|
||||||
tape_chains = {},
|
_addrs = {},
|
||||||
_addrs = {},
|
_chain = nil,
|
||||||
_chain = nil,
|
_brace_depth = 0,
|
||||||
_brace_depth = 0,
|
reg_use_errors = {},
|
||||||
reg_use_errors = {},
|
|
||||||
-- Shared `R_*_Code -> integer code` registry
|
-- Shared `R_*_Code -> integer code` registry
|
||||||
-- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver).
|
-- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver).
|
||||||
-- Stripped from `src.scan` before return.
|
-- Stripped from `src.scan` before return.
|
||||||
_code_macros = code_macros or {},
|
_code_macros = code_macros or {},
|
||||||
-- Shared raw RHS body table (passed in from M.run pass 1a;
|
-- Shared raw RHS body table (passed in from M.run pass 1a;
|
||||||
-- same reference so preprocessor intercept writes are visible to the cross-source chain walker in resolve_code_macro_value).
|
-- same reference so preprocessor intercept writes are visible to the cross-source chain walker in resolve_code_macro_value).
|
||||||
-- Stripped from `src.scan` before return.
|
-- Stripped from `src.scan` before return.
|
||||||
_code_macro_bodies = code_macro_bodies or {},
|
_code_macro_bodies = code_macro_bodies or {},
|
||||||
_source_file = source_file,
|
_source_file = source_file,
|
||||||
}
|
}
|
||||||
local pos = 1
|
local pos = 1
|
||||||
local src_len = #source
|
local src_len = #source
|
||||||
@@ -2526,9 +2615,9 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
if parser then
|
if parser then
|
||||||
pos = parser(source, pos, ident_end, line_of, out)
|
pos = parser(source, pos, ident_end, line_of, out)
|
||||||
else
|
else
|
||||||
-- Unsupported identifiers follow the unrelated-token path. If a
|
-- Unsupported identifiers follow the unrelated-token path.
|
||||||
-- pending marker is still open, consume it so it cannot drift to a
|
-- If a pending marker is still open, consume it so it cannot drift to a later declaration.
|
||||||
-- later declaration. Unsupported identifiers never create marker records.
|
-- Unsupported identifiers never create marker records.
|
||||||
local markers = out.debug_skip_markers
|
local markers = out.debug_skip_markers
|
||||||
local marker = markers[#markers]
|
local marker = markers[#markers]
|
||||||
if marker and marker.pending then
|
if marker and marker.pending then
|
||||||
@@ -2543,7 +2632,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
else
|
else
|
||||||
local markers = out.debug_skip_markers
|
local markers = out.debug_skip_markers
|
||||||
local marker = markers[#markers]
|
local marker = markers[#markers]
|
||||||
local c = source:sub(pos, pos)
|
local c = source:sub(pos, pos)
|
||||||
if marker and marker.pending and marker.proc_prelude then
|
if marker and marker.pending and marker.proc_prelude then
|
||||||
if c == "{" or c == ";" then
|
if c == "{" or c == ";" then
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
@@ -2572,8 +2661,8 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
if out._chain and #out._chain > 0 then
|
if out._chain and #out._chain > 0 then
|
||||||
out.tape_chains[#out.tape_chains + 1] = out._chain
|
out.tape_chains[#out.tape_chains + 1] = out._chain
|
||||||
end
|
end
|
||||||
out._addrs = nil
|
out._addrs = nil
|
||||||
out._chain = nil
|
out._chain = nil
|
||||||
out._brace_depth = nil
|
out._brace_depth = nil
|
||||||
|
|
||||||
return out
|
return out
|
||||||
@@ -2698,7 +2787,7 @@ end
|
|||||||
-- * conflicting shape: Keep first entry, append ONE typed collision record with shape diff.
|
-- * conflicting shape: Keep first entry, append ONE typed collision record with shape diff.
|
||||||
local function merge_named_with_sites(registry, name, new_entry, site, collisions, kind, shape_fn)
|
local function merge_named_with_sites(registry, name, new_entry, site, collisions, kind, shape_fn)
|
||||||
if registry[name] == nil then
|
if registry[name] == nil then
|
||||||
registry[name] = new_entry
|
registry[name] = new_entry
|
||||||
registry[name].sites = { site }
|
registry[name].sites = { site }
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -2715,8 +2804,7 @@ local function merge_named_with_sites(registry, name, new_entry, site, collision
|
|||||||
collisions[#collisions + 1] = {
|
collisions[#collisions + 1] = {
|
||||||
kind = kind,
|
kind = kind,
|
||||||
name = name,
|
name = name,
|
||||||
first_site = existing.sites and existing.sites[1]
|
first_site = existing.sites and existing.sites[1] or build_site(existing.source_file, existing.source_line),
|
||||||
or build_site(existing.source_file, existing.source_line),
|
|
||||||
conflicting_site = site,
|
conflicting_site = site,
|
||||||
first_shape = old_shape,
|
first_shape = old_shape,
|
||||||
conflicting_shape = new_shape,
|
conflicting_shape = new_shape,
|
||||||
@@ -2748,9 +2836,18 @@ local function merge_corpus_registries(corpus)
|
|||||||
-- 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.
|
||||||
for _, key in ipairs({
|
for _, key in ipairs({
|
||||||
"register_alias_registry", "type_name_registry", "binds_by_name",
|
"register_alias_registry",
|
||||||
"atoms_by_name", "atom_views", "atom_ctxs", "atom_phases",
|
"type_name_registry",
|
||||||
"atom_infos", "component_atom_infos", "collisions", "reg_use_schemas", "reg_use_errors",
|
"binds_by_name",
|
||||||
|
"atoms_by_name",
|
||||||
|
"atom_views",
|
||||||
|
"atom_ctxs",
|
||||||
|
"atom_phases",
|
||||||
|
"atom_infos",
|
||||||
|
"component_atom_infos",
|
||||||
|
"collisions",
|
||||||
|
"reg_use_schemas",
|
||||||
|
"reg_use_errors",
|
||||||
"tape_chains",
|
"tape_chains",
|
||||||
}) do
|
}) do
|
||||||
corpus[key] = {}
|
corpus[key] = {}
|
||||||
|
|||||||
+834
-527
File diff suppressed because it is too large
Load Diff
@@ -531,7 +531,7 @@ local function build_ctx(args)
|
|||||||
end
|
end
|
||||||
local text = file:read("*a")
|
local text = file:read("*a")
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
local source = {
|
local source = {
|
||||||
path = path,
|
path = path,
|
||||||
text = text,
|
text = text,
|
||||||
@@ -727,7 +727,7 @@ local function main(argv)
|
|||||||
local ok, err = pcall(function()
|
local ok, err = pcall(function()
|
||||||
local args = parse_args(argv)
|
local args = parse_args(argv)
|
||||||
local ctx = build_ctx(args)
|
local ctx = build_ctx(args)
|
||||||
|
|
||||||
local requested = args.requested_set
|
local requested = args.requested_set
|
||||||
local closed = topo_sort(PASSES, requested)
|
local closed = topo_sort(PASSES, requested)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user