mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
auto-column alignment formatting pass on trailing type annotations.
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
local scan = require("duffle_scan") ---@type DuffleExport
|
local scan = require("duffle_scan") ---@type DuffleExport
|
||||||
local isa = require("duffle_isa") ---@type DuffleExport
|
local isa = require("duffle_isa") ---@type DuffleExport
|
||||||
local emit = require("duffle_emit") ---@type DuffleExport
|
local emit = require("duffle_emit") ---@type DuffleExport
|
||||||
local M = {} ---@type DuffleExport
|
local M = {} ---@type DuffleExport
|
||||||
|
|
||||||
--- @alias Path string
|
--- @alias Path string
|
||||||
--- @alias LineNum integer
|
--- @alias LineNum integer
|
||||||
|
|||||||
+148
-294
@@ -1,14 +1,9 @@
|
|||||||
--- duffle_emit.lua — project_emission + decl finders.
|
--- duffle_emit.lua — project_emission + decl finders.
|
||||||
--- @type DuffleScan
|
local scan = require("duffle_scan") ---@type DuffleScan
|
||||||
local scan = require("duffle_scan")
|
local isa = require("duffle_isa") ---@type DuffleIsa
|
||||||
--- @type DuffleIsa
|
local M = {} ---@type DuffleEmit
|
||||||
local isa = require("duffle_isa")
|
for k, v in pairs(scan) do M[k] = v end ---@type string, any
|
||||||
--- @type DuffleEmit
|
for k, v in pairs(isa) do M[k] = v end ---@type string, any
|
||||||
local M = {}
|
|
||||||
--- @type string, any
|
|
||||||
for k, v in pairs(scan) do M[k] = v end
|
|
||||||
--- @type string, any
|
|
||||||
for k, v in pairs(isa) do M[k] = v end
|
|
||||||
|
|
||||||
-- Section 8: Cross-source component-body index + word-event expansion
|
-- Section 8: Cross-source component-body index + word-event expansion
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -51,22 +46,15 @@ for k, v in pairs(isa) do M[k] = v end
|
|||||||
-- Consumers (`passes/static_analysis.lua`, `passes/emission_model.lua`) read it directly; per-pass memoization helpers stay out of scope.
|
-- Consumers (`passes/static_analysis.lua`, `passes/emission_model.lua`) read it directly; per-pass memoization helpers stay out of scope.
|
||||||
|
|
||||||
-- ASCII byte constants used by split_call_args (kept local to keep Section 8 self-contained).
|
-- ASCII byte constants used by split_call_args (kept local to keep Section 8 self-contained).
|
||||||
--- @type integer
|
local E_BYTE_OPEN_PAREN = 0x28 ---@type integer
|
||||||
local E_BYTE_OPEN_PAREN = 0x28
|
local E_BYTE_OPEN_BRACE = 0x7B ---@type integer
|
||||||
--- @type integer
|
local E_BYTE_OPEN_BRACK = 0x5B ---@type integer
|
||||||
local E_BYTE_OPEN_BRACE = 0x7B
|
local E_BYTE_DQUOTE = 0x22 ---@type integer
|
||||||
--- @type integer
|
local E_BYTE_SQUOTE = 0x27 ---@type integer
|
||||||
local E_BYTE_OPEN_BRACK = 0x5B
|
local E_BYTE_COMMA = 0x2C ---@type integer
|
||||||
--- @type integer
|
|
||||||
local E_BYTE_DQUOTE = 0x22
|
|
||||||
--- @type integer
|
|
||||||
local E_BYTE_SQUOTE = 0x27
|
|
||||||
--- @type integer
|
|
||||||
local E_BYTE_COMMA = 0x2C
|
|
||||||
|
|
||||||
-- Map an open-delimiter byte to its matching close string for read_balanced.
|
-- Map an open-delimiter byte to its matching close string for read_balanced.
|
||||||
--- @type table<integer, string> -- bag: open-delimiter byte -> close string
|
local E_OPEN_CLOSE = { ---@type table<integer, string> -- bag: open-delimiter byte -> close string
|
||||||
local E_OPEN_CLOSE = {
|
|
||||||
[E_BYTE_OPEN_PAREN] = ")",
|
[E_BYTE_OPEN_PAREN] = ")",
|
||||||
[E_BYTE_OPEN_BRACE] = "}",
|
[E_BYTE_OPEN_BRACE] = "}",
|
||||||
[E_BYTE_OPEN_BRACK] = "]",
|
[E_BYTE_OPEN_BRACK] = "]",
|
||||||
@@ -79,23 +67,16 @@ local E_OPEN_CLOSE = {
|
|||||||
--- @param inner string
|
--- @param inner string
|
||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function split_call_args(inner)
|
local function split_call_args(inner)
|
||||||
--- @type string[]
|
local args = {} ---@type string[]
|
||||||
local args = {}
|
|
||||||
if not inner or inner == "" then return args end
|
if not inner or inner == "" then return args end
|
||||||
--- @type integer
|
local pos = 1 ---@type integer
|
||||||
local pos = 1
|
local len = #inner ---@type integer
|
||||||
--- @type integer
|
local start = 1 ---@type integer
|
||||||
local len = #inner
|
|
||||||
--- @type integer
|
|
||||||
local start = 1
|
|
||||||
while pos <= len do
|
while pos <= len do
|
||||||
--- @type integer
|
local c = inner:byte(pos) ---@type integer
|
||||||
local c = inner:byte(pos)
|
local close = E_OPEN_CLOSE[c] ---@type string|nil
|
||||||
--- @type string|nil
|
|
||||||
local close = E_OPEN_CLOSE[c]
|
|
||||||
if close then
|
if close then
|
||||||
--- @type integer, integer
|
local _, after = M.read_balanced(inner, string.char(c), close, pos) ---@type integer, integer
|
||||||
local _, after = M.read_balanced(inner, string.char(c), close, pos)
|
|
||||||
pos = after
|
pos = after
|
||||||
elseif c == E_BYTE_DQUOTE or c == E_BYTE_SQUOTE then
|
elseif c == E_BYTE_DQUOTE or c == E_BYTE_SQUOTE then
|
||||||
pos = M.skip_str_or_cmt(inner, pos)
|
pos = M.skip_str_or_cmt(inner, pos)
|
||||||
@@ -116,23 +97,18 @@ end
|
|||||||
--- @param tok string
|
--- @param tok string
|
||||||
--- @return string, string[]
|
--- @return string, string[]
|
||||||
local function token_ident_and_args(tok)
|
local function token_ident_and_args(tok)
|
||||||
--- @type string|nil, integer
|
local ident, after = M.read_ident(tok, 1) ---@type string|nil, integer
|
||||||
local ident, after = M.read_ident(tok, 1)
|
|
||||||
if not ident then return "?", {} end
|
if not ident then return "?", {} end
|
||||||
--- @type integer
|
local paren_pos = M.skip_ws_and_cmt(tok, after) ---@type integer
|
||||||
local paren_pos = M.skip_ws_and_cmt(tok, after)
|
|
||||||
if tok:sub(paren_pos, paren_pos) ~= "(" then return ident, {} end
|
if tok:sub(paren_pos, paren_pos) ~= "(" then return ident, {} end
|
||||||
--- @type string|nil
|
local inner = M.read_parens(tok, paren_pos) ---@type string|nil
|
||||||
local inner = M.read_parens(tok, paren_pos)
|
|
||||||
if not inner then return ident, {} end
|
if not inner then return ident, {} end
|
||||||
return ident, split_call_args(inner)
|
return ident, split_call_args(inner)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The macro-name prefix that marks a `mac_X(...)` component invocation.
|
-- The macro-name prefix that marks a `mac_X(...)` component invocation.
|
||||||
--- @type string
|
local E_MAC_PREFIX = "mac_" ---@type string
|
||||||
local E_MAC_PREFIX = "mac_"
|
local E_MAC_PREFIX_LEN = 4 ---@type integer
|
||||||
--- @type integer
|
|
||||||
local E_MAC_PREFIX_LEN = 4
|
|
||||||
|
|
||||||
--- Expand a body entry into the flat sequence of emitted machine-word events.
|
--- Expand a body entry into the flat sequence of emitted machine-word events.
|
||||||
---
|
---
|
||||||
@@ -217,38 +193,24 @@ local E_MAC_PREFIX_LEN = 4
|
|||||||
--- @param ctx_table EmissionWalkCtx
|
--- @param ctx_table EmissionWalkCtx
|
||||||
--- @return EmissionProjection
|
--- @return EmissionProjection
|
||||||
local function _project_emission_inner(root_body_entry, ctx_table)
|
local function _project_emission_inner(root_body_entry, ctx_table)
|
||||||
--- @type EmissionItem[]
|
local items = {} ---@type EmissionItem[]
|
||||||
local items = {}
|
local word_events = {} ---@type WordEvent[]
|
||||||
--- @type WordEvent[]
|
local markers = {} ---@type EmissionMarker[]
|
||||||
local word_events = {}
|
local invocations = {} ---@type InvocationRecord[]
|
||||||
--- @type EmissionMarker[]
|
local errors = {} ---@type EmitError[]
|
||||||
local markers = {}
|
local warnings = {} ---@type EmitWarning[]
|
||||||
--- @type InvocationRecord[]
|
|
||||||
local invocations = {}
|
|
||||||
--- @type EmitError[]
|
|
||||||
local errors = {}
|
|
||||||
--- @type EmitWarning[]
|
|
||||||
local warnings = {}
|
|
||||||
|
|
||||||
--- @type integer
|
local word_idx = 0 ---@type integer
|
||||||
local word_idx = 0
|
local invocation_stack = {} ---@type InvocationRecord[] -- stack of currently-open invocation records
|
||||||
--- @type InvocationRecord[]
|
local next_inv_id = 0 ---@type integer
|
||||||
local invocation_stack = {} -- stack of currently-open invocation records
|
|
||||||
--- @type integer
|
|
||||||
local next_inv_id = 0
|
|
||||||
|
|
||||||
--- @type RegUseSchema|nil
|
local reg_use_schema = ctx_table.reg_use_schema ---@type RegUseSchema|nil
|
||||||
local reg_use_schema = ctx_table.reg_use_schema
|
local reg_use_param = ctx_table.reg_use_param ---@type string|nil
|
||||||
--- @type string|nil
|
local atom_name = ctx_table.atom_name ---@type AtomName|nil
|
||||||
local reg_use_param = ctx_table.reg_use_param
|
|
||||||
--- @type AtomName|nil
|
|
||||||
local atom_name = ctx_table.atom_name
|
|
||||||
|
|
||||||
--- @type table<string, boolean> -- bag: slot name -> readonly
|
local slot_readonly = {} ---@type table<string, boolean> -- bag: slot name -> readonly
|
||||||
local slot_readonly = {}
|
|
||||||
if reg_use_schema then
|
if reg_use_schema then
|
||||||
--- @type integer, RegUseSlot
|
for _, slot in ipairs(reg_use_schema.slots or {}) do ---@type integer, RegUseSlot
|
||||||
for _, slot in ipairs(reg_use_schema.slots or {}) do
|
|
||||||
slot_readonly[slot.name] = slot.readonly == true
|
slot_readonly[slot.name] = slot.readonly == true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -259,13 +221,10 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local function apply_sub(sub_map, operand)
|
local function apply_sub(sub_map, operand)
|
||||||
if not (sub_map and type(operand) == "string") then return operand end
|
if not (sub_map and type(operand) == "string") then return operand end
|
||||||
if sub_map[operand] then return sub_map[operand] end
|
if sub_map[operand] then return sub_map[operand] end
|
||||||
--- @type integer|nil
|
local dot = operand:find(".", 1, true) ---@type integer|nil
|
||||||
local dot = operand:find(".", 1, true)
|
|
||||||
if dot then
|
if dot then
|
||||||
--- @type string
|
local head = operand:sub(1, dot - 1) ---@type string
|
||||||
local head = operand:sub(1, dot - 1)
|
local mapped = sub_map[head] ---@type string|nil
|
||||||
--- @type string|nil
|
|
||||||
local mapped = sub_map[head]
|
|
||||||
if type(mapped) == "string" then
|
if type(mapped) == "string" then
|
||||||
return mapped .. operand:sub(dot)
|
return mapped .. operand:sub(dot)
|
||||||
end
|
end
|
||||||
@@ -279,23 +238,18 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
if type(operand) ~= "string" then return nil end
|
if type(operand) ~= "string" then return nil end
|
||||||
if operand:sub(1, 2) == "R_" then return operand end
|
if operand:sub(1, 2) == "R_" then return operand end
|
||||||
if not (reg_use_schema and reg_use_param) then return nil end
|
if not (reg_use_schema and reg_use_param) then return nil end
|
||||||
--- @type string
|
local prefix = reg_use_param .. "." ---@type string
|
||||||
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
|
||||||
--- @type string
|
local member_path = operand:sub(#prefix + 1) ---@type string
|
||||||
local member_path = operand:sub(#prefix + 1)
|
local slot = reg_use_schema.alias_to_slot[member_path] ---@type string|nil
|
||||||
--- @type string|nil
|
|
||||||
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
|
||||||
|
|
||||||
--- @return integer[]
|
--- @return integer[]
|
||||||
local function open_invocation_ids_snapshot()
|
local function open_invocation_ids_snapshot()
|
||||||
--- @type integer[]
|
local ids = {} ---@type integer[]
|
||||||
local ids = {}
|
for _, inv in ipairs(invocation_stack) do ---@type integer, InvocationRecord
|
||||||
--- @type integer, InvocationRecord
|
|
||||||
for _, inv in ipairs(invocation_stack) do
|
|
||||||
ids[#ids + 1] = inv.id
|
ids[#ids + 1] = inv.id
|
||||||
end
|
end
|
||||||
return ids
|
return ids
|
||||||
@@ -312,27 +266,19 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
--- @param sub_map table<string, string>|nil
|
--- @param sub_map table<string, string>|nil
|
||||||
--- @return nil
|
--- @return nil
|
||||||
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)
|
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)
|
||||||
--- @type integer[]
|
local inv_ids = open_invocation_ids_snapshot() ---@type integer[]
|
||||||
local inv_ids = open_invocation_ids_snapshot()
|
local outermost = inv_ids[1] or 0 ---@type integer
|
||||||
--- @type integer
|
|
||||||
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.
|
||||||
-- For words emitted inside a component expansion, `immediate_call_text` is the immediate outer `mac_X(...)` token text;
|
-- For words emitted inside a component expansion, `immediate_call_text` is the immediate outer `mac_X(...)` token text;
|
||||||
-- The call that triggered the body expansion we're currently walking.
|
-- The call that triggered the body expansion we're currently walking.
|
||||||
--- @type string|nil
|
local eff_call_text = immediate_call_text or word_call_text ---@type string|nil
|
||||||
local eff_call_text = immediate_call_text or word_call_text
|
local eff_root_call_text = root_call_text_w ---@type string|nil
|
||||||
--- @type string|nil
|
local gpr_keys = nil ---@type string[]|nil
|
||||||
local eff_root_call_text = root_call_text_w
|
|
||||||
--- @type string[]|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 = {}
|
||||||
--- @type integer, string
|
for pos, arg in ipairs(args or {}) do ---@type integer, string
|
||||||
for pos, arg in ipairs(args or {}) do
|
local effective = apply_sub(sub_map, arg) ---@type any
|
||||||
--- @type any
|
local key, unresolved, slot = resolve_gpr_key(effective) ---@type string|nil, string|nil, string|nil
|
||||||
local effective = apply_sub(sub_map, arg)
|
|
||||||
--- @type string|nil, string|nil, string|nil
|
|
||||||
local key, unresolved, slot = resolve_gpr_key(effective)
|
|
||||||
gpr_keys[pos] = key
|
gpr_keys[pos] = key
|
||||||
if unresolved then
|
if unresolved then
|
||||||
errors[#errors + 1] = {
|
errors[#errors + 1] = {
|
||||||
@@ -343,11 +289,9 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
if key and slot and slot_readonly[slot] then
|
if key and slot and slot_readonly[slot] then
|
||||||
--- @type InstructionRow|nil
|
local row = M.instr(encoder) ---@type InstructionRow|nil
|
||||||
local row = M.instr(encoder)
|
|
||||||
if row and row.writes then
|
if row and row.writes then
|
||||||
--- @type integer, integer
|
for _, wpos in ipairs(row.writes) do ---@type integer, integer
|
||||||
for _, wpos in ipairs(row.writes) do
|
|
||||||
if wpos == pos then
|
if wpos == pos then
|
||||||
errors[#errors + 1] = {
|
errors[#errors + 1] = {
|
||||||
kind = "reguse_const_write",
|
kind = "reguse_const_write",
|
||||||
@@ -364,26 +308,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
|
||||||
--- @type InstructionRow|nil
|
local isa = M.instr(encoder) ---@type InstructionRow|nil
|
||||||
local isa = M.instr(encoder)
|
local isa_kind = isa and isa.kind or "unknown" ---@type string
|
||||||
--- @type string
|
local nop_words = (encoder == "nop" and 1) or (encoder == "nop2" and 2) or 0 ---@type integer
|
||||||
local isa_kind = isa and isa.kind or "unknown"
|
local is_yield = (encoder == "mac_yield" or encoder == "mac_yield_tail") ---@type boolean
|
||||||
--- @type integer
|
local gp0_shape = type(encoder) == "string" ---@type string|nil
|
||||||
local nop_words = (encoder == "nop" and 1) or (encoder == "nop2" and 2) or 0
|
|
||||||
--- @type boolean
|
|
||||||
local is_yield = (encoder == "mac_yield" or encoder == "mac_yield_tail")
|
|
||||||
--- @type string|nil
|
|
||||||
local gp0_shape = type(encoder) == "string"
|
|
||||||
and encoder:match("^mac_format_([%w_]+)_color$")
|
and encoder:match("^mac_format_([%w_]+)_color$")
|
||||||
or nil
|
or nil
|
||||||
--- @type boolean
|
local is_load = (isa_kind == "load") ---@type boolean
|
||||||
local is_load = (isa_kind == "load")
|
local is_branch = (isa_kind == "branch") ---@type boolean
|
||||||
--- @type boolean
|
local is_unconditional_jump = (encoder == "jump" or encoder == "call_addr") ---@type boolean
|
||||||
local is_branch = (isa_kind == "branch")
|
local is_terminal_jump = (encoder == "jump_reg" or encoder == "call_reg" or encoder == "jump_link") ---@type boolean
|
||||||
--- @type boolean
|
|
||||||
local is_unconditional_jump = (encoder == "jump" or encoder == "call_addr")
|
|
||||||
--- @type boolean
|
|
||||||
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,
|
||||||
@@ -443,10 +378,8 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local function emit_marker(kind, name, target, line,
|
local function emit_marker(kind, name, target, line,
|
||||||
immediate_call_text, root_call_text_w,
|
immediate_call_text, root_call_text_w,
|
||||||
consuming_encoder, consuming_arg_pos)
|
consuming_encoder, consuming_arg_pos)
|
||||||
--- @type integer[]
|
local inv_ids = open_invocation_ids_snapshot() ---@type integer[]
|
||||||
local inv_ids = open_invocation_ids_snapshot()
|
local outermost = inv_ids[1] or 0 ---@type integer
|
||||||
--- @type integer
|
|
||||||
local outermost = inv_ids[1] or 0
|
|
||||||
-- Markers carry the open invocation stack snapshot. `call_text` / `root_call_text` belong to words, not markers — markers are zero-width and skip per-word call-site attribution.
|
-- Markers carry the open invocation stack snapshot. `call_text` / `root_call_text` belong to words, not markers — markers are zero-width and skip per-word call-site attribution.
|
||||||
-- `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).
|
||||||
@@ -456,8 +389,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
if kind == "offset" and (consuming_encoder == nil or consuming_encoder == "") then
|
if kind == "offset" and (consuming_encoder == nil or consuming_encoder == "") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
--- @type EmissionItem
|
local it = { ---@type EmissionItem
|
||||||
local it = {
|
|
||||||
kind = kind,
|
kind = kind,
|
||||||
name = name,
|
name = name,
|
||||||
line = line,
|
line = line,
|
||||||
@@ -488,28 +420,21 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
--- @param to_pos integer
|
--- @param to_pos integer
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function count_top_level_commas(tok, from_pos, to_pos)
|
local function count_top_level_commas(tok, from_pos, to_pos)
|
||||||
--- @type integer
|
local depth = 0 ---@type integer
|
||||||
local depth = 0
|
local count = 0 ---@type integer
|
||||||
--- @type integer
|
local i = from_pos ---@type integer
|
||||||
local count = 0
|
|
||||||
--- @type integer
|
|
||||||
local i = from_pos
|
|
||||||
while i < to_pos do
|
while i < to_pos do
|
||||||
--- @type string
|
local c = tok:sub(i, i) ---@type string
|
||||||
local c = tok:sub(i, i)
|
|
||||||
if c == "'" or c == '"' then
|
if c == "'" or c == '"' then
|
||||||
--- @type integer
|
local next_pos = M.skip_str_or_cmt(tok, i) ---@type integer
|
||||||
local next_pos = M.skip_str_or_cmt(tok, i)
|
|
||||||
i = (next_pos > i) and next_pos or (i + 1)
|
i = (next_pos > i) and next_pos or (i + 1)
|
||||||
elseif c == "/" and tok:sub(i + 1, i + 1) == "/" then
|
elseif c == "/" and tok:sub(i + 1, i + 1) == "/" then
|
||||||
-- line comment: skip to end of line
|
-- line comment: skip to end of line
|
||||||
--- @type integer|nil
|
local nl = tok:find("\n", i, true) ---@type integer|nil
|
||||||
local nl = tok:find("\n", i, true)
|
|
||||||
i = (nl and nl + 1) or (#tok + 1)
|
i = (nl and nl + 1) or (#tok + 1)
|
||||||
elseif c == "/" and tok:sub(i + 1, i + 1) == "*" then
|
elseif c == "/" and tok:sub(i + 1, i + 1) == "*" then
|
||||||
-- block comment: skip to matching */
|
-- block comment: skip to matching */
|
||||||
--- @type integer|nil
|
local close = tok:find("*/", i + 2, true) ---@type integer|nil
|
||||||
local close = tok:find("*/", i + 2, true)
|
|
||||||
i = (close and close + 2) or (#tok + 1)
|
i = (close and close + 2) or (#tok + 1)
|
||||||
elseif c == "(" then
|
elseif c == "(" then
|
||||||
depth = depth + 1
|
depth = depth + 1
|
||||||
@@ -532,11 +457,9 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
--- @param tok string
|
--- @param tok string
|
||||||
--- @return integer|nil
|
--- @return integer|nil
|
||||||
local function find_consuming_paren(tok)
|
local function find_consuming_paren(tok)
|
||||||
--- @type integer
|
local i = 1 ---@type integer
|
||||||
local i = 1
|
|
||||||
while i <= #tok do
|
while i <= #tok do
|
||||||
--- @type string
|
local c = tok:sub(i, i) ---@type string
|
||||||
local c = tok:sub(i, i)
|
|
||||||
if c == "(" then return i end
|
if c == "(" then return i end
|
||||||
if not c:match("[%w_]") and c ~= " " then return nil end
|
if not c:match("[%w_]") and c ~= " " then return nil end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -551,27 +474,22 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local function emit_embedded_markers(tok, tok_line, consuming_encoder)
|
local function emit_embedded_markers(tok, tok_line, consuming_encoder)
|
||||||
-- When called with a non-nil `consuming_encoder`, the marker is nested inside that instruction's argument list.
|
-- When called with a non-nil `consuming_encoder`, the marker is nested inside that instruction's argument list.
|
||||||
-- We compute each marker's arg position by counting top-level commas between the consuming instruction's `(` and the marker's start.
|
-- We compute each marker's arg position by counting top-level commas between the consuming instruction's `(` and the marker's start.
|
||||||
--- @type integer|nil
|
local consuming_paren = nil ---@type integer|nil
|
||||||
local consuming_paren = nil
|
|
||||||
if consuming_encoder then consuming_paren = find_consuming_paren(tok) end
|
if consuming_encoder then consuming_paren = find_consuming_paren(tok) end
|
||||||
--- @type integer
|
local pos = 1 ---@type integer
|
||||||
local pos = 1
|
|
||||||
while pos <= #tok do
|
while pos <= #tok do
|
||||||
-- Trim leading whitespace and comments before each scan.
|
-- Trim leading whitespace and comments before each scan.
|
||||||
pos = M.skip_ws_and_cmt(tok, pos)
|
pos = M.skip_ws_and_cmt(tok, pos)
|
||||||
if pos > #tok then break end
|
if pos > #tok then break end
|
||||||
--- @type string|nil, integer
|
local ident, after = M.read_ident(tok, pos) ---@type string|nil, integer
|
||||||
local ident, after = M.read_ident(tok, pos)
|
|
||||||
if not ident then
|
if not ident then
|
||||||
-- Not an ident: token is a string or comment; skip or one-step.
|
-- Not an ident: token is a string or comment; skip or one-step.
|
||||||
--- @type integer
|
local next_pos = M.skip_str_or_cmt(tok, pos) ---@type integer
|
||||||
local next_pos = M.skip_str_or_cmt(tok, pos)
|
|
||||||
pos = (next_pos > pos) and next_pos or (pos + 1)
|
pos = (next_pos > pos) and next_pos or (pos + 1)
|
||||||
goto continue_loop
|
goto continue_loop
|
||||||
end
|
end
|
||||||
if M.DELAY_MARKERS[ident] then
|
if M.DELAY_MARKERS[ident] then
|
||||||
--- @type integer|nil
|
local arg_pos = nil ---@type integer|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
|
||||||
@@ -585,10 +503,8 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
goto continue_loop
|
goto continue_loop
|
||||||
end
|
end
|
||||||
-- Marker ident: parse the (...) arguments.
|
-- Marker ident: parse the (...) arguments.
|
||||||
--- @type integer
|
local open = M.skip_ws_and_cmt(tok, after) ---@type integer
|
||||||
local open = M.skip_ws_and_cmt(tok, after)
|
local inner, after_paren = M.read_parens(tok, open) ---@type string|nil, integer
|
||||||
--- @type string|nil, integer
|
|
||||||
local inner, after_paren = M.read_parens(tok, open)
|
|
||||||
if not inner then
|
if not inner then
|
||||||
-- (...) Unreadable: fall back to non-marker behavior.
|
-- (...) Unreadable: fall back to non-marker behavior.
|
||||||
pos = after
|
pos = after
|
||||||
@@ -598,13 +514,11 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- 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.
|
||||||
-- Offset markers are emitted only when a consuming encoder is present.
|
-- Offset markers are emitted only when a consuming encoder is present.
|
||||||
--- @type integer|nil
|
local arg_pos = nil ---@type integer|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
|
||||||
--- @type string[]
|
local args = split_call_args(inner) ---@type string[]
|
||||||
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)
|
||||||
elseif consuming_encoder then 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
|
||||||
@@ -630,10 +544,8 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- The walker has already found the component body in `ctx_table.component_index[component_name]`, so the matching entry MUST exist in `ctx_table.components[component_name]`
|
-- The walker has already found the component body in `ctx_table.component_index[component_name]`, so the matching entry MUST exist in `ctx_table.components[component_name]`
|
||||||
-- (both registries are populated from the same source by the components pass).
|
-- (both registries are populated from the same source by the components pass).
|
||||||
-- A missing entry is a corpus-plumbing bug; we fail loudly here rather than silently stamp `false` and mask the regression.
|
-- A missing entry is a corpus-plumbing bug; we fail loudly here rather than silently stamp `false` and mask the regression.
|
||||||
--- @type table<string, ComponentDef>|nil
|
local components = ctx_table.components ---@type table<string, ComponentDef>|nil
|
||||||
local components = ctx_table.components
|
local component_def = components and components[component_name] or nil ---@type ComponentDef|nil
|
||||||
--- @type ComponentDef|nil
|
|
||||||
local component_def = components and components[component_name] or nil
|
|
||||||
if not component_def then
|
if not component_def then
|
||||||
error("duffle.emit_invoke_begin: component " .. string.format("%q", component_name)
|
error("duffle.emit_invoke_begin: component " .. string.format("%q", component_name)
|
||||||
.. " is present in `component_index` (the walker matched a `mac_" .. component_name .. "()` call) but absent from `components` (the canonical corpus.components registry). "
|
.. " is present in `component_index` (the walker matched a `mac_" .. component_name .. "()` call) but absent from `components` (the canonical corpus.components registry). "
|
||||||
@@ -642,10 +554,8 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
, 0
|
, 0
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
--- @type boolean
|
local debug_skip_stamp = component_def.debug_skip == true ---@type boolean
|
||||||
local debug_skip_stamp = component_def.debug_skip == true
|
local inv = { ---@type InvocationRecord
|
||||||
--- @type InvocationRecord
|
|
||||||
local inv = {
|
|
||||||
id = next_inv_id,
|
id = next_inv_id,
|
||||||
parent_id = 0, -- patched below by caller
|
parent_id = 0, -- patched below by caller
|
||||||
kind = inv_kind,
|
kind = inv_kind,
|
||||||
@@ -692,8 +602,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
word_index = word_idx,
|
word_index = word_idx,
|
||||||
invocation_ids = open_invocation_ids_snapshot(),
|
invocation_ids = open_invocation_ids_snapshot(),
|
||||||
}
|
}
|
||||||
--- @type integer
|
for i = #invocation_stack, 1, -1 do ---@type integer
|
||||||
for i = #invocation_stack, 1, -1 do
|
|
||||||
if invocation_stack[i] == inv then
|
if invocation_stack[i] == inv then
|
||||||
table.remove(invocation_stack, i)
|
table.remove(invocation_stack, i)
|
||||||
break
|
break
|
||||||
@@ -707,11 +616,9 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
--- @param tok_line integer
|
--- @param tok_line integer
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function resolve_count(ident, tok_line)
|
local function resolve_count(ident, tok_line)
|
||||||
--- @type WordCounts|nil
|
local wc = ctx_table.word_counts ---@type WordCounts|nil
|
||||||
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
|
||||||
--- @type string
|
local canon = M.gte_canon(ident) ---@type string
|
||||||
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",
|
||||||
@@ -734,60 +641,45 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function walk_body_entry(body_entry, walk_parent_inv_id,
|
local function walk_body_entry(body_entry, walk_parent_inv_id,
|
||||||
walk_root_call_text, walk_immediate_call_text)
|
walk_root_call_text, walk_immediate_call_text)
|
||||||
--- @type BodyToken[]
|
local tokens = body_entry.body_tokens or {} ---@type BodyToken[]
|
||||||
local tokens = body_entry.body_tokens or {}
|
local body_off = body_entry.body_off or 0 ---@type integer
|
||||||
--- @type integer
|
local line_of = body_entry.line_of or M.LineIndex("") ---@type LineIndexFn
|
||||||
local body_off = body_entry.body_off or 0
|
local def_source = body_entry.source or "" ---@type string
|
||||||
--- @type LineIndexFn
|
local def_line = body_entry.declaration or 0 ---@type integer
|
||||||
local line_of = body_entry.line_of or M.LineIndex("")
|
local sub_map = body_entry.sub_map ---@type table<string, string>|nil
|
||||||
--- @type string
|
|
||||||
local def_source = body_entry.source or ""
|
|
||||||
--- @type integer
|
|
||||||
local def_line = body_entry.declaration or 0
|
|
||||||
--- @type table<string, string>|nil
|
|
||||||
local sub_map = body_entry.sub_map
|
|
||||||
-- Per-token dispatch: each matched branch returns; only the fall-through
|
-- Per-token dispatch: each matched branch returns; only the fall-through
|
||||||
-- "opaque word" emit handles direct encoders + mac_X-without-component.
|
-- "opaque word" emit handles direct encoders + mac_X-without-component.
|
||||||
--- @param bt BodyToken
|
--- @param bt BodyToken
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function process_token(bt)
|
local function process_token(bt)
|
||||||
--- @type string
|
local tok = M.trim(bt.tok or "") ---@type string
|
||||||
local tok = M.trim(bt.tok or "")
|
|
||||||
-- Substituted MipsCode args can carry // comments from the call site.
|
-- Substituted MipsCode args can carry // comments from the call site.
|
||||||
while tok ~= "" do
|
while tok ~= "" do
|
||||||
if tok:sub(1, 2) == "//" then
|
if tok:sub(1, 2) == "//" then
|
||||||
--- @type integer|nil
|
local nl = tok:find("\n") ---@type integer|nil
|
||||||
local nl = tok:find("\n")
|
|
||||||
tok = M.trim(nl and tok:sub(nl + 1) or "")
|
tok = M.trim(nl and tok:sub(nl + 1) or "")
|
||||||
elseif tok:sub(1, 2) == "/*" then
|
elseif tok:sub(1, 2) == "/*" then
|
||||||
--- @type integer|nil
|
local close = tok:find("*/", 3, true) ---@type integer|nil
|
||||||
local close = tok:find("*/", 3, true)
|
|
||||||
tok = M.trim(close and tok:sub(close + 2) or "")
|
tok = M.trim(close and tok:sub(close + 2) or "")
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if tok == "" then return end
|
if tok == "" then return end
|
||||||
--- @type string|nil, integer
|
local ident, after = M.read_ident(tok, 1) ---@type string|nil, integer
|
||||||
local ident, after = M.read_ident(tok, 1)
|
|
||||||
if not ident then ident = "?" end
|
if not ident then ident = "?" end
|
||||||
--- @type string, string[]
|
local _, args = token_ident_and_args(tok) ---@type string, string[]
|
||||||
local _, args = token_ident_and_args(tok)
|
local tok_line = line_of(body_off + bt.rel) or 0 ---@type integer
|
||||||
--- @type integer
|
|
||||||
local tok_line = line_of(body_off + bt.rel) or 0
|
|
||||||
if M.DELAY_MARKERS[ident] then
|
if M.DELAY_MARKERS[ident] then
|
||||||
emit_marker("delay", ident, nil, tok_line)
|
emit_marker("delay", ident, nil, tok_line)
|
||||||
--- @type string
|
local rest = tok:sub(after or (#tok + 1)) ---@type string
|
||||||
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
|
if rest:sub(1, 2) == "//" then
|
||||||
--- @type integer|nil
|
local nl = rest:find("\n") ---@type integer|nil
|
||||||
local nl = rest:find("\n")
|
|
||||||
rest = nl and rest:sub(nl + 1) or ""
|
rest = nl and rest:sub(nl + 1) or ""
|
||||||
elseif rest:sub(1, 2) == "/*" then
|
elseif rest:sub(1, 2) == "/*" then
|
||||||
--- @type integer|nil
|
local close = rest:find("*/", 3, true) ---@type integer|nil
|
||||||
local close = rest:find("*/", 3, true)
|
|
||||||
if not close then rest = ""; break end
|
if not close then rest = ""; break end
|
||||||
rest = rest:sub(close + 2)
|
rest = rest:sub(close + 2)
|
||||||
else
|
else
|
||||||
@@ -803,8 +695,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- Pass `ident` as the consuming instruction so `emit_embedded_markers` can compute each marker's arg position + record the consuming_encoder for the offsets pass.
|
-- Pass `ident` as the consuming instruction so `emit_embedded_markers` can compute each marker's arg position + record the consuming_encoder for the offsets pass.
|
||||||
-- Canonicalize `jump_rel` to `branch_equal` (its preprocessor-expanded form) so the `consuming_encoder` metadata in marker records is canonical.
|
-- Canonicalize `jump_rel` to `branch_equal` (its preprocessor-expanded form) so the `consuming_encoder` metadata in marker records is canonical.
|
||||||
-- `jump_rel`: unconditional jump alias from `code/duffle/mips.h`.
|
-- `jump_rel`: unconditional jump alias from `code/duffle/mips.h`.
|
||||||
--- @type string
|
local consuming_encoder_for_markers = (ident == "jump_rel") and "branch_equal" or ident ---@type string
|
||||||
local consuming_encoder_for_markers = (ident == "jump_rel") and "branch_equal" or ident
|
|
||||||
if ident ~= "atom_label" and ident ~= "atom_offset" then
|
if ident ~= "atom_label" and ident ~= "atom_offset" then
|
||||||
emit_embedded_markers(tok, tok_line, consuming_encoder_for_markers)
|
emit_embedded_markers(tok, tok_line, consuming_encoder_for_markers)
|
||||||
end
|
end
|
||||||
@@ -817,29 +708,23 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- MipsCode formals (nop_slot1, …): the ident is a sub_map key.
|
-- MipsCode formals (nop_slot1, …): the ident is a sub_map key.
|
||||||
-- Re-process the replacement token so load_word(...) becomes a real encoder.
|
-- 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
|
if sub_map and type(sub_map[ident]) == "string" and sub_map[ident] ~= ident then
|
||||||
--- @type string
|
local repl = M.trim(sub_map[ident]) ---@type string
|
||||||
local repl = M.trim(sub_map[ident])
|
|
||||||
if repl ~= "" then
|
if repl ~= "" then
|
||||||
process_token({ tok = repl, rel = bt.rel })
|
process_token({ tok = repl, rel = bt.rel })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if ident:sub(1, 4) == "mac_" then
|
if ident:sub(1, 4) == "mac_" then
|
||||||
--- @type string
|
local bare = ident:sub(5) ---@type string
|
||||||
local bare = ident:sub(5)
|
local comp = ctx_table.component_index[bare] ---@type ComponentBodyEntry|nil
|
||||||
--- @type ComponentBodyEntry|nil
|
|
||||||
local comp = ctx_table.component_index[bare]
|
|
||||||
if comp then
|
if comp then
|
||||||
--- @type string
|
local invocation_root_call_text = walk_root_call_text or tok ---@type string
|
||||||
local invocation_root_call_text = walk_root_call_text or tok
|
|
||||||
if ctx_table.visiting[bare] then
|
if ctx_table.visiting[bare] then
|
||||||
-- Cycle: still allocate inv_id, emit zero-width begin/end, record the cycle error; do NOT recurse.
|
-- Cycle: still allocate inv_id, emit zero-width begin/end, record the cycle error; do NOT recurse.
|
||||||
--- @type InvocationRecord
|
local inv = emit_invoke_begin(comp.kind or "comp_bare", bare, tok, invocation_root_call_text, def_source, tok_line) ---@type InvocationRecord
|
||||||
local inv = emit_invoke_begin(comp.kind or "comp_bare", bare, tok, invocation_root_call_text, def_source, tok_line)
|
|
||||||
inv.parent_id = walk_parent_inv_id
|
inv.parent_id = walk_parent_inv_id
|
||||||
inv.call_text = tok
|
inv.call_text = tok
|
||||||
--- @type EmitError
|
local err = { ---@type EmitError
|
||||||
local err = {
|
|
||||||
kind = "cycle",
|
kind = "cycle",
|
||||||
msg = string.format("project_emission: component cycle detected: %q", bare),
|
msg = string.format("project_emission: component cycle detected: %q", bare),
|
||||||
source = def_source,
|
source = def_source,
|
||||||
@@ -852,8 +737,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
end
|
end
|
||||||
-- First visit: descend + count + count_mismatch-check below.
|
-- First visit: descend + count + count_mismatch-check below.
|
||||||
ctx_table.visiting[bare] = true
|
ctx_table.visiting[bare] = true
|
||||||
--- @type InvocationRecord
|
local inv = emit_invoke_begin(comp.kind or "comp_bare", bare, tok, invocation_root_call_text, def_source, tok_line) ---@type InvocationRecord
|
||||||
local inv = emit_invoke_begin(comp.kind or "comp_bare", bare, tok, invocation_root_call_text, def_source, tok_line)
|
|
||||||
inv.parent_id = walk_parent_inv_id
|
inv.parent_id = walk_parent_inv_id
|
||||||
inv.call_text = tok
|
inv.call_text = tok
|
||||||
inv.def_path = comp.source
|
inv.def_path = comp.source
|
||||||
@@ -861,15 +745,12 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- Propagate trackers into the recursive walk:
|
-- Propagate trackers into the recursive walk:
|
||||||
-- immediate_call_text = this call's tok (the IMMEDIATE outer call for words emitted in this body)
|
-- immediate_call_text = this call's tok (the IMMEDIATE outer call for words emitted in this body)
|
||||||
-- root_call_text = the OUTERMOST call (immutable across the recursion)
|
-- root_call_text = the OUTERMOST call (immutable across the recursion)
|
||||||
--- @type string[]|nil
|
local formal_names = ctx_table.component_index[bare] ---@type string[]|nil
|
||||||
local formal_names = ctx_table.component_index[bare]
|
|
||||||
and ctx_table.component_index[bare].arg_names
|
and ctx_table.component_index[bare].arg_names
|
||||||
--- @type table<string, string>|nil
|
local child_map = nil ---@type table<string, string>|nil
|
||||||
local child_map = nil
|
|
||||||
if formal_names then
|
if formal_names then
|
||||||
child_map = {}
|
child_map = {}
|
||||||
--- @type integer, string
|
for i, fname in ipairs(formal_names) do ---@type integer, string
|
||||||
for i, fname in ipairs(formal_names) do
|
|
||||||
child_map[fname] = apply_sub(sub_map, args[i])
|
child_map[fname] = apply_sub(sub_map, args[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -887,12 +768,9 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
ctx_table.visiting[bare] = nil
|
ctx_table.visiting[bare] = nil
|
||||||
emit_invoke_end(inv)
|
emit_invoke_end(inv)
|
||||||
-- Count `word` items inside [start_word, end_word].
|
-- Count `word` items inside [start_word, end_word].
|
||||||
--- @type integer
|
local wc_inside = 0 ---@type integer
|
||||||
local wc_inside = 0
|
for i = inv.start_word, inv.end_word do ---@type integer
|
||||||
--- @type integer
|
local it = items[i] ---@type EmissionItem|nil
|
||||||
for i = inv.start_word, inv.end_word do
|
|
||||||
--- @type EmissionItem|nil
|
|
||||||
local it = items[i]
|
|
||||||
if it and it.kind == "word" then
|
if it and it.kind == "word" then
|
||||||
wc_inside = wc_inside + 1
|
wc_inside = wc_inside + 1
|
||||||
end
|
end
|
||||||
@@ -900,11 +778,9 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
inv.word_count = wc_inside
|
inv.word_count = wc_inside
|
||||||
-- count_mismatch is a construction error: word_counts["mac_X"] is the declared count populated by the components pass;
|
-- count_mismatch is a construction error: word_counts["mac_X"] is the declared count populated by the components pass;
|
||||||
-- We compare against the measured word count.
|
-- We compare against the measured word count.
|
||||||
--- @type integer|nil
|
local declared = ctx_table.word_counts["mac_" .. bare] ---@type integer|nil
|
||||||
local declared = ctx_table.word_counts["mac_" .. bare]
|
|
||||||
if declared and wc_inside ~= declared then
|
if declared and wc_inside ~= declared then
|
||||||
--- @type EmitError
|
local err = { ---@type EmitError
|
||||||
local err = {
|
|
||||||
kind = "count_mismatch",
|
kind = "count_mismatch",
|
||||||
msg = string.format("project_emission: mac_%s declared=%d measured=%d", bare, declared, wc_inside),
|
msg = string.format("project_emission: mac_%s declared=%d measured=%d", bare, declared, wc_inside),
|
||||||
source = def_source,
|
source = def_source,
|
||||||
@@ -919,18 +795,14 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
end
|
end
|
||||||
-- Direct encoder, or mac_X-without-component: resolve count + emit n words.
|
-- Direct encoder, or mac_X-without-component: resolve count + emit n words.
|
||||||
-- Resolve_count may emit a warning if the count is unresolved.
|
-- Resolve_count may emit a warning if the count is unresolved.
|
||||||
--- @type integer
|
local n = resolve_count(ident, tok_line) ---@type integer
|
||||||
local n = resolve_count(ident, tok_line)
|
local out_ident = (ident == "nop2") and "nop" or ident ---@type string
|
||||||
--- @type string
|
for _ = 1, n do ---@type integer
|
||||||
local out_ident = (ident == "nop2") and "nop" or ident
|
|
||||||
--- @type integer
|
|
||||||
for _ = 1, n do
|
|
||||||
emit_word(out_ident, args, tok_line, tok, def_source, def_line, walk_immediate_call_text, walk_root_call_text, sub_map)
|
emit_word(out_ident, args, tok_line, tok, def_source, def_line, walk_immediate_call_text, walk_root_call_text, sub_map)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @type integer, BodyToken
|
for _, bt in ipairs(tokens) do ---@type integer, BodyToken
|
||||||
for _, bt in ipairs(tokens) do
|
|
||||||
process_token(bt)
|
process_token(bt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1025,8 +897,7 @@ function M.project_emission(body_text, component_index, word_counts, components,
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @type BodyToken[]
|
local tokens = M.tokenize_body(body_text) ---@type BodyToken[]
|
||||||
local tokens = M.tokenize_body(body_text)
|
|
||||||
return _project_emission_inner({
|
return _project_emission_inner({
|
||||||
body_tokens = tokens,
|
body_tokens = tokens,
|
||||||
body_off = 0,
|
body_off = 0,
|
||||||
@@ -1064,26 +935,21 @@ end
|
|||||||
--- @param slice_mips_code_len integer
|
--- @param slice_mips_code_len integer
|
||||||
--- @return string|nil, string|nil
|
--- @return string|nil, string|nil
|
||||||
function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
|
function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
|
||||||
--- @type integer
|
local search_pos = 1 ---@type integer
|
||||||
local search_pos = 1
|
local last_match = nil ---@type integer|nil
|
||||||
--- @type integer|nil
|
|
||||||
local last_match = nil
|
|
||||||
while true do
|
while true do
|
||||||
--- @type integer|nil
|
local found = source:find("Slice_MipsCode", search_pos, true) ---@type integer|nil
|
||||||
local found = source:find("Slice_MipsCode", search_pos, true)
|
|
||||||
if not found or found >= before_pos then break end
|
if not found or found >= before_pos then break end
|
||||||
last_match = found
|
last_match = found
|
||||||
search_pos = found + slice_mips_code_len
|
search_pos = found + slice_mips_code_len
|
||||||
end
|
end
|
||||||
if not last_match then return nil, nil end
|
if not last_match then return nil, nil end
|
||||||
|
|
||||||
--- @type integer
|
local pos = last_match + slice_mips_code_len ---@type integer
|
||||||
local pos = last_match + slice_mips_code_len
|
|
||||||
while pos < before_pos do
|
while pos < before_pos do
|
||||||
-- skip whitespace
|
-- skip whitespace
|
||||||
while pos <= #source do
|
while pos <= #source do
|
||||||
--- @type string
|
local c = source:sub(pos, pos) ---@type string
|
||||||
local c = source:sub(pos, pos)
|
|
||||||
if c == " " or c == "\t" or c == "\n" or c == "\r" then
|
if c == " " or c == "\t" or c == "\n" or c == "\r" then
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
else
|
else
|
||||||
@@ -1099,22 +965,18 @@ function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
|
|||||||
end
|
end
|
||||||
-- skip block comments
|
-- skip block comments
|
||||||
if source:sub(pos, pos + 1) == "/*" then
|
if source:sub(pos, pos + 1) == "/*" then
|
||||||
--- @type integer|nil
|
local close = source:find("*/", pos + 2, true) ---@type integer|nil
|
||||||
local close = source:find("*/", pos + 2, true)
|
|
||||||
if not close then break end
|
if not close then break end
|
||||||
pos = close + 2
|
pos = close + 2
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
-- try to read an ident
|
-- try to read an ident
|
||||||
--- @type string|nil, integer
|
local ident, ident_end = M.read_ident(source, pos) ---@type string|nil, integer
|
||||||
local ident, ident_end = M.read_ident(source, pos)
|
|
||||||
if not ident then break end
|
if not ident then break end
|
||||||
-- check if the next non-ws char after ident is "("
|
-- check if the next non-ws char after ident is "("
|
||||||
--- @type integer
|
local next_pos = M.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local next_pos = M.skip_ws_and_cmt(source, ident_end)
|
|
||||||
if source:sub(next_pos, next_pos) == "(" then
|
if source:sub(next_pos, next_pos) == "(" then
|
||||||
--- @type string|nil
|
local inner = M.read_parens(source, next_pos) ---@type string|nil
|
||||||
local inner = M.read_parens(source, next_pos)
|
|
||||||
if inner then
|
if inner then
|
||||||
return ident, inner
|
return ident, inner
|
||||||
end
|
end
|
||||||
@@ -1145,27 +1007,22 @@ end
|
|||||||
--- @param mips_atom_ptr_len integer
|
--- @param mips_atom_ptr_len integer
|
||||||
--- @return string|nil, string|nil, string|nil, integer|nil
|
--- @return string|nil, string|nil, string|nil, integer|nil
|
||||||
function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
|
function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
|
||||||
--- @type integer
|
local search_pos = 1 ---@type integer
|
||||||
local search_pos = 1
|
local last_match = nil ---@type integer|nil
|
||||||
--- @type integer|nil
|
|
||||||
local last_match = nil
|
|
||||||
while true do
|
while true do
|
||||||
-- plain=true: "*" is literal, no escaping needed
|
-- plain=true: "*" is literal, no escaping needed
|
||||||
--- @type integer|nil
|
local found = source:find("MipsAtom*", search_pos, true) ---@type integer|nil
|
||||||
local found = source:find("MipsAtom*", search_pos, true)
|
|
||||||
if not found or found >= before_pos then break end
|
if not found or found >= before_pos then break end
|
||||||
last_match = found
|
last_match = found
|
||||||
search_pos = found + mips_atom_ptr_len
|
search_pos = found + mips_atom_ptr_len
|
||||||
end
|
end
|
||||||
if not last_match then return nil, nil end
|
if not last_match then return nil, nil end
|
||||||
|
|
||||||
--- @type integer
|
local pos = last_match + mips_atom_ptr_len ---@type integer
|
||||||
local pos = last_match + mips_atom_ptr_len
|
|
||||||
while pos < before_pos do
|
while pos < before_pos do
|
||||||
-- skip whitespace
|
-- skip whitespace
|
||||||
while pos <= #source do
|
while pos <= #source do
|
||||||
--- @type string
|
local c = source:sub(pos, pos) ---@type string
|
||||||
local c = source:sub(pos, pos)
|
|
||||||
if c == " " or c == "\t" or c == "\n" or c == "\r" then
|
if c == " " or c == "\t" or c == "\n" or c == "\r" then
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
else
|
else
|
||||||
@@ -1181,22 +1038,18 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
|
|||||||
end
|
end
|
||||||
-- skip block comments
|
-- skip block comments
|
||||||
if source:sub(pos, pos + 1) == "/*" then
|
if source:sub(pos, pos + 1) == "/*" then
|
||||||
--- @type integer|nil
|
local close = source:find("*/", pos + 2, true) ---@type integer|nil
|
||||||
local close = source:find("*/", pos + 2, true)
|
|
||||||
if not close then break end
|
if not close then break end
|
||||||
pos = close + 2
|
pos = close + 2
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
-- try to read an ident
|
-- try to read an ident
|
||||||
--- @type string|nil, integer
|
local ident, ident_end = M.read_ident(source, pos) ---@type string|nil, integer
|
||||||
local ident, ident_end = M.read_ident(source, pos)
|
|
||||||
if not ident then break end
|
if not ident then break end
|
||||||
-- check if the next non-ws char after ident is "("
|
-- check if the next non-ws char after ident is "("
|
||||||
--- @type integer
|
local next_pos = M.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local next_pos = M.skip_ws_and_cmt(source, ident_end)
|
|
||||||
if source:sub(next_pos, next_pos) == "(" then
|
if source:sub(next_pos, next_pos) == "(" then
|
||||||
--- @type string|nil, integer
|
local inner, after_paren = M.read_parens(source, next_pos) ---@type string|nil, integer
|
||||||
local inner, after_paren = M.read_parens(source, next_pos)
|
|
||||||
if inner then
|
if inner then
|
||||||
return ident, inner, ident, after_paren
|
return ident, inner, ident, after_paren
|
||||||
end
|
end
|
||||||
@@ -1209,3 +1062,4 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
||||||
|
|||||||
+59
-59
@@ -78,34 +78,34 @@ local lfs = require("lfs") ---@type LfsMod
|
|||||||
-- ASCII byte constants
|
-- ASCII byte constants
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
local BYTE_SPACE = 0x20 ---@type integer -- ' '
|
local BYTE_SPACE = 0x20 ---@type integer -- ' '
|
||||||
local BYTE_TAB = 0x09 ---@type integer -- '\t'
|
local BYTE_TAB = 0x09 ---@type integer -- '\t'
|
||||||
local BYTE_NEWLINE = 0x0A ---@type integer -- '\n'
|
local BYTE_NEWLINE = 0x0A ---@type integer -- '\n'
|
||||||
local BYTE_CR = 0x0D ---@type integer -- '\r'
|
local BYTE_CR = 0x0D ---@type integer -- '\r'
|
||||||
local BYTE_VT = 0x0B ---@type integer -- '\v'
|
local BYTE_VT = 0x0B ---@type integer -- '\v'
|
||||||
local BYTE_FF = 0x0C ---@type integer -- '\f'
|
local BYTE_FF = 0x0C ---@type integer -- '\f'
|
||||||
|
|
||||||
local BYTE_UNDERSCORE = 0x5F ---@type integer -- '_'
|
local BYTE_UNDERSCORE = 0x5F ---@type integer -- '_'
|
||||||
local BYTE_DOT = 0x2E ---@type integer -- '.'
|
local BYTE_DOT = 0x2E ---@type integer -- '.'
|
||||||
local BYTE_SLASH = 0x2F ---@type integer -- '/'
|
local BYTE_SLASH = 0x2F ---@type integer -- '/'
|
||||||
local BYTE_BACKSLASH = 0x5C ---@type integer -- '\\'
|
local BYTE_BACKSLASH = 0x5C ---@type integer -- '\\'
|
||||||
local BYTE_STAR = 0x2A ---@type integer -- '*'
|
local BYTE_STAR = 0x2A ---@type integer -- '*'
|
||||||
local BYTE_DQUOTE = 0x22 ---@type integer -- '"'
|
local BYTE_DQUOTE = 0x22 ---@type integer -- '"'
|
||||||
local BYTE_SQUOTE = 0x27 ---@type integer -- '\''
|
local BYTE_SQUOTE = 0x27 ---@type integer -- '\''
|
||||||
local BYTE_COMMA = 0x2C ---@type integer -- ','
|
local BYTE_COMMA = 0x2C ---@type integer -- ','
|
||||||
local BYTE_SEMI = 0x3B ---@type integer -- ';'
|
local BYTE_SEMI = 0x3B ---@type integer -- ';'
|
||||||
|
|
||||||
local BYTE_OPEN_PAREN = 0x28 ---@type integer -- '('
|
local BYTE_OPEN_PAREN = 0x28 ---@type integer -- '('
|
||||||
local BYTE_OPEN_BRACE = 0x7B ---@type integer -- '{'
|
local BYTE_OPEN_BRACE = 0x7B ---@type integer -- '{'
|
||||||
local BYTE_OPEN_BRACK = 0x5B ---@type integer -- '['
|
local BYTE_OPEN_BRACK = 0x5B ---@type integer -- '['
|
||||||
|
|
||||||
local BYTE_LOWER_A = 0x61 ---@type integer -- 'a'
|
local BYTE_LOWER_A = 0x61 ---@type integer -- 'a'
|
||||||
local BYTE_LOWER_Z = 0x7A ---@type integer -- 'z'
|
local BYTE_LOWER_Z = 0x7A ---@type integer -- 'z'
|
||||||
local BYTE_UPPER_A = 0x41 ---@type integer -- 'A'
|
local BYTE_UPPER_A = 0x41 ---@type integer -- 'A'
|
||||||
local BYTE_UPPER_Z = 0x5A ---@type integer -- 'Z'
|
local BYTE_UPPER_Z = 0x5A ---@type integer -- 'Z'
|
||||||
|
|
||||||
local BYTE_DIGIT_0 = 0x30 ---@type integer -- '0'
|
local BYTE_DIGIT_0 = 0x30 ---@type integer -- '0'
|
||||||
local BYTE_DIGIT_9 = 0x39 ---@type integer -- '9'
|
local BYTE_DIGIT_9 = 0x39 ---@type integer -- '9'
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Section -1: Bootstrap (path-setup at module load)
|
-- Section -1: Bootstrap (path-setup at module load)
|
||||||
@@ -134,17 +134,17 @@ local digit_pat = R("09") ---@type LpegPattern
|
|||||||
local lpeg_alnum_pat = alpha_pat + digit_pat ---@type LpegPattern
|
local lpeg_alnum_pat = alpha_pat + digit_pat ---@type LpegPattern
|
||||||
|
|
||||||
-- Identifier: alpha followed by zero+ alnum. Capture as a string.
|
-- Identifier: alpha followed by zero+ alnum. Capture as a string.
|
||||||
local lpeg_alpha_pat = alpha_pat ---@type LpegPattern
|
local lpeg_alpha_pat = alpha_pat ---@type LpegPattern
|
||||||
local lpeg_ident_pat = lpeg.C(alpha_pat * lpeg_alnum_pat^0) ---@type LpegPattern
|
local lpeg_ident_pat = lpeg.C(alpha_pat * lpeg_alnum_pat^0) ---@type LpegPattern
|
||||||
|
|
||||||
local lpeg_str_pat = P('"') * (P(1) - S('"\\') + P('\\') * P(1))^0 * P('"') ---@type LpegPattern -- String literal: "..." with backslash escapes.
|
local lpeg_str_pat = P('"') * (P(1) - S('"\\') + P('\\') * P(1))^0 * P('"') ---@type LpegPattern -- String literal: "..." with backslash escapes.
|
||||||
local lpeg_chr_pat = P("'") * (P(1) - S("'\\") + P('\\') * P(1))^0 * P("'") ---@type LpegPattern -- Char literal: '...' with backslash escapes.
|
local lpeg_chr_pat = P("'") * (P(1) - S("'\\") + P('\\') * P(1))^0 * P("'") ---@type LpegPattern -- Char literal: '...' with backslash escapes.
|
||||||
local lpeg_line_cmt_pat = P("//") * (P(1) - S("\n"))^0 ---@type LpegPattern -- Line comment: // ... to end-of-line.
|
local lpeg_line_cmt_pat = P("//") * (P(1) - S("\n"))^0 ---@type LpegPattern -- Line comment: // ... to end-of-line.
|
||||||
local lpeg_block_cmt_pat = P("/*") * (P(1) - P("*/"))^0 * P("*/") ---@type LpegPattern -- Block comment: /* ... */ (no nesting per C standard).
|
local lpeg_block_cmt_pat = P("/*") * (P(1) - P("*/"))^0 * P("*/") ---@type LpegPattern -- Block comment: /* ... */ (no nesting per C standard).
|
||||||
local lpeg_str_or_cmt_pat = lpeg_str_pat + lpeg_chr_pat + lpeg_line_cmt_pat + lpeg_block_cmt_pat ---@type LpegPattern -- String or comment (any of the four forms).
|
local lpeg_str_or_cmt_pat = lpeg_str_pat + lpeg_chr_pat + lpeg_line_cmt_pat + lpeg_block_cmt_pat ---@type LpegPattern -- String or comment (any of the four forms).
|
||||||
|
|
||||||
-- Whitespace + comment skipper: zero+ (whitespace run | string | comment).
|
-- Whitespace + comment skipper: zero+ (whitespace run | string | comment).
|
||||||
local ws_pat = S(" \t\n\r\v\f") ---@type LpegPattern
|
local ws_pat = S(" \t\n\r\v\f") ---@type LpegPattern
|
||||||
local lpeg_ws_and_cmt_pat = (ws_pat + lpeg_str_or_cmt_pat)^0 ---@type LpegPattern
|
local lpeg_ws_and_cmt_pat = (ws_pat + lpeg_str_or_cmt_pat)^0 ---@type LpegPattern
|
||||||
|
|
||||||
-- Generic "skip until target, but step over balanced groups" matcher.
|
-- Generic "skip until target, but step over balanced groups" matcher.
|
||||||
@@ -239,7 +239,7 @@ end
|
|||||||
--- @param path Path
|
--- @param path Path
|
||||||
--- @return Path
|
--- @return Path
|
||||||
function M.dirname(path)
|
function M.dirname(path)
|
||||||
local last_sep = 0 ---@type integer
|
local last_sep = 0 ---@type integer
|
||||||
for pos = 1, #path do ---@type integer
|
for pos = 1, #path do ---@type integer
|
||||||
local b = path:byte(pos) ---@type integer
|
local b = path:byte(pos) ---@type integer
|
||||||
if b == BYTE_SLASH or b == BYTE_BACKSLASH then last_sep = pos end
|
if b == BYTE_SLASH or b == BYTE_BACKSLASH then last_sep = pos end
|
||||||
@@ -252,14 +252,14 @@ end
|
|||||||
--- @param path Path
|
--- @param path Path
|
||||||
--- @return string
|
--- @return string
|
||||||
function M.basename_no_ext(path)
|
function M.basename_no_ext(path)
|
||||||
local last_sep = 0 ---@type integer
|
local last_sep = 0 ---@type integer
|
||||||
for pos = 1, #path do ---@type integer
|
for pos = 1, #path do ---@type integer
|
||||||
local b = path:byte(pos) ---@type integer
|
local b = path:byte(pos) ---@type integer
|
||||||
if b == BYTE_SLASH or b == BYTE_BACKSLASH then last_sep = pos end
|
if b == BYTE_SLASH or b == BYTE_BACKSLASH then last_sep = pos end
|
||||||
end
|
end
|
||||||
local a = last_sep + 1 ---@type integer
|
local a = last_sep + 1 ---@type integer
|
||||||
local last_dot = #path + 1 ---@type integer
|
local last_dot = #path + 1 ---@type integer
|
||||||
for pos = #path, a, -1 do ---@type integer
|
for pos = #path, a, -1 do ---@type integer
|
||||||
if path:byte(pos) == BYTE_DOT then last_dot = pos; break end
|
if path:byte(pos) == BYTE_DOT then last_dot = pos; break end
|
||||||
end
|
end
|
||||||
return path:sub(a, last_dot - 1)
|
return path:sub(a, last_dot - 1)
|
||||||
@@ -281,13 +281,13 @@ local function parse_path_root(input)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if input:sub(1, 2) == "//" then
|
if input:sub(1, 2) == "//" then
|
||||||
local server_start = 3 ---@type integer
|
local server_start = 3 ---@type integer
|
||||||
local server_end = M.find_byte(input, BYTE_SLASH, server_start) ---@type integer|nil
|
local server_end = M.find_byte(input, BYTE_SLASH, server_start) ---@type integer|nil
|
||||||
if not server_end or server_end == server_start then
|
if not server_end or server_end == server_start then
|
||||||
error("UNC path requires //server/share: " .. input, 3)
|
error("UNC path requires //server/share: " .. input, 3)
|
||||||
end
|
end
|
||||||
local server = input:sub(server_start, server_end - 1) ---@type string
|
local server = input:sub(server_start, server_end - 1) ---@type string
|
||||||
local share_start = server_end + 1 ---@type integer
|
local share_start = server_end + 1 ---@type integer
|
||||||
while input:sub(share_start, share_start) == "/" do
|
while input:sub(share_start, share_start) == "/" do
|
||||||
share_start = share_start + 1
|
share_start = share_start + 1
|
||||||
end
|
end
|
||||||
@@ -296,7 +296,7 @@ local function parse_path_root(input)
|
|||||||
error("UNC path requires //server/share: " .. input, 3)
|
error("UNC path requires //server/share: " .. input, 3)
|
||||||
end
|
end
|
||||||
local share = input:sub(share_start, share_end - 1) ---@type string
|
local share = input:sub(share_start, share_end - 1) ---@type string
|
||||||
local rest = input:sub(share_end + 1) ---@type string
|
local rest = input:sub(share_end + 1) ---@type string
|
||||||
while rest:sub(1, 1) == "/" do rest = rest:sub(2) end
|
while rest:sub(1, 1) == "/" do rest = rest:sub(2) end
|
||||||
return {
|
return {
|
||||||
kind = "unc_absolute",
|
kind = "unc_absolute",
|
||||||
@@ -323,8 +323,8 @@ function M.normalize_path(path)
|
|||||||
if path == "" then return "" end
|
if path == "" then return "" end
|
||||||
|
|
||||||
local root = parse_path_root(path:gsub("\\", "/")) ---@type PathRoot
|
local root = parse_path_root(path:gsub("\\", "/")) ---@type PathRoot
|
||||||
local segments = {} ---@type string[]
|
local segments = {} ---@type string[]
|
||||||
for segment in root.rest:gmatch("[^/]+") do ---@type string
|
for segment in root.rest:gmatch("[^/]+") do ---@type string
|
||||||
if segment == "." then
|
if segment == "." then
|
||||||
-- no-op
|
-- no-op
|
||||||
elseif segment == ".." then
|
elseif segment == ".." then
|
||||||
@@ -348,7 +348,7 @@ end
|
|||||||
--- @param path Path
|
--- @param path Path
|
||||||
--- @return Path
|
--- @return Path
|
||||||
local function absolute_normalized_path(path)
|
local function absolute_normalized_path(path)
|
||||||
local normalized = M.normalize_path(path) ---@type Path
|
local normalized = M.normalize_path(path) ---@type Path
|
||||||
local root = parse_path_root(normalized) ---@type PathRoot
|
local root = parse_path_root(normalized) ---@type PathRoot
|
||||||
if root.kind == "drive_relative" then
|
if root.kind == "drive_relative" then
|
||||||
error("drive-relative path cannot be resolved without a per-drive cwd: " .. normalized, 3)
|
error("drive-relative path cannot be resolved without a per-drive cwd: " .. normalized, 3)
|
||||||
@@ -428,7 +428,7 @@ function M.to_absolute_path(path)
|
|||||||
if not cwd then _absolute_path_cache[path] = path; return path end
|
if not cwd then _absolute_path_cache[path] = path; return path end
|
||||||
cwd = cwd:gsub("/", "\\")
|
cwd = cwd:gsub("/", "\\")
|
||||||
local tail = (path:gsub("/", "\\")) ---@type string
|
local tail = (path:gsub("/", "\\")) ---@type string
|
||||||
local result = cwd .. "\\" .. tail ---@type string
|
local result = cwd .. "\\" .. tail ---@type string
|
||||||
_absolute_path_cache[path] = result
|
_absolute_path_cache[path] = result
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
@@ -452,7 +452,7 @@ end
|
|||||||
--- @param sources SourceFile[]
|
--- @param sources SourceFile[]
|
||||||
--- @return table<string, SourceFile[]>
|
--- @return table<string, SourceFile[]>
|
||||||
function M.group_sources_by_dir(sources)
|
function M.group_sources_by_dir(sources)
|
||||||
local by_dir = {} ---@type table<string, SourceFile[]>
|
local by_dir = {} ---@type table<string, SourceFile[]>
|
||||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||||
by_dir[src.dir] = by_dir[src.dir] or {}
|
by_dir[src.dir] = by_dir[src.dir] or {}
|
||||||
table.insert(by_dir[src.dir], src)
|
table.insert(by_dir[src.dir], src)
|
||||||
@@ -552,7 +552,7 @@ M.read_brackets = function(s, pos) return M.read_balanced(s, "[", "]", pos) end
|
|||||||
--- @return integer|nil
|
--- @return integer|nil
|
||||||
function M.scan_to_char(s, target, start)
|
function M.scan_to_char(s, target, start)
|
||||||
local target_byte = target:byte() ---@type integer
|
local target_byte = target:byte() ---@type integer
|
||||||
local pos = start ---@type integer
|
local pos = start ---@type integer
|
||||||
while pos <= #s do
|
while pos <= #s do
|
||||||
local c = s:byte(pos) ---@type integer
|
local c = s:byte(pos) ---@type integer
|
||||||
if c == target_byte then return pos end -- scan: ... <target found> | <skipping to target>
|
if c == target_byte then return pos end -- scan: ... <target found> | <skipping to target>
|
||||||
@@ -632,7 +632,7 @@ local function splice_c_lines(source)
|
|||||||
local line = 1 ---@type integer
|
local line = 1 ---@type integer
|
||||||
while pos <= #source do
|
while pos <= #source do
|
||||||
local byte = source:byte(pos) ---@type integer
|
local byte = source:byte(pos) ---@type integer
|
||||||
local splice_len = nil ---@type integer|nil
|
local splice_len = nil ---@type integer|nil
|
||||||
if byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_NEWLINE then
|
if byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_NEWLINE then
|
||||||
splice_len = 2
|
splice_len = 2
|
||||||
elseif byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_CR and source:byte(pos + 2) == BYTE_NEWLINE then
|
elseif byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_CR and source:byte(pos + 2) == BYTE_NEWLINE then
|
||||||
@@ -668,9 +668,9 @@ function M.parse_direct_quoted_includes(source_text)
|
|||||||
-- Each arm's effect on (pos, line_leading) is annotated at the branch site.
|
-- Each arm's effect on (pos, line_leading) is annotated at the branch site.
|
||||||
-- Arm order: newline / horiz-space / '//' / '/*' / '"' / '\'' / '#' / default.
|
-- Arm order: newline / horiz-space / '//' / '/*' / '"' / '\'' / '#' / default.
|
||||||
local logical_text, physical_pos, physical_line = splice_c_lines(source_text) ---@type string, integer[], integer[]
|
local logical_text, physical_pos, physical_line = splice_c_lines(source_text) ---@type string, integer[], integer[]
|
||||||
local includes = {} ---@type QuotedInclude[]
|
local includes = {} ---@type QuotedInclude[]
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local line_leading = true ---@type boolean
|
local line_leading = true ---@type boolean
|
||||||
while pos <= #logical_text do
|
while pos <= #logical_text do
|
||||||
local byte = logical_text:byte(pos) ---@type integer
|
local byte = logical_text:byte(pos) ---@type integer
|
||||||
if byte == BYTE_NEWLINE then
|
if byte == BYTE_NEWLINE then
|
||||||
@@ -709,7 +709,7 @@ function M.parse_direct_quoted_includes(source_text)
|
|||||||
-- Full success pushes the record and jumps to ::directive_done:: without ever entering the not-include path.
|
-- Full success pushes the record and jumps to ::directive_done:: without ever entering the not-include path.
|
||||||
-- (All locals are pre-declared at the top of this arm because Lua forbids a goto from crossing a local declaration into its scope.)
|
-- (All locals are pre-declared at the top of this arm because Lua forbids a goto from crossing a local declaration into its scope.)
|
||||||
local hash_pos, directive_line, scan, ident, after_ident, after_quote ---@type integer, integer, integer|nil, string|nil, integer, integer
|
local hash_pos, directive_line, scan, ident, after_ident, after_quote ---@type integer, integer, integer|nil, string|nil, integer, integer
|
||||||
local include_path, physical_first, physical_last ---@type string, integer, integer
|
local include_path, physical_first, physical_last ---@type string, integer, integer
|
||||||
hash_pos = pos
|
hash_pos = pos
|
||||||
directive_line = physical_line[hash_pos] or 1
|
directive_line = physical_line[hash_pos] or 1
|
||||||
scan = skip_directive_space(logical_text, pos + 1)
|
scan = skip_directive_space(logical_text, pos + 1)
|
||||||
@@ -802,9 +802,9 @@ function M.resolve_source_corpus(options)
|
|||||||
local code_root = M.normalize_path(project_root .. "/code") ---@type Path
|
local code_root = M.normalize_path(project_root .. "/code") ---@type Path
|
||||||
local code_root_key = M.canonical_path_key(code_root) ---@type string
|
local code_root_key = M.canonical_path_key(code_root) ---@type string
|
||||||
local root = load_source_record(options.unity_root) ---@type SourceFile
|
local root = load_source_record(options.unity_root) ---@type SourceFile
|
||||||
local source_order = { root } ---@type SourceFile[]
|
local source_order = { root } ---@type SourceFile[]
|
||||||
local sources_by_path = { [M.canonical_path_key(root.path)] = root, } ---@type table<Path, SourceFile>
|
local sources_by_path = { [M.canonical_path_key(root.path)] = root, } ---@type table<Path, SourceFile>
|
||||||
local resolver = { ---@type SourceResolver
|
local resolver = { ---@type SourceResolver
|
||||||
resolved = {
|
resolved = {
|
||||||
{
|
{
|
||||||
include_path = nil,
|
include_path = nil,
|
||||||
@@ -824,11 +824,11 @@ function M.resolve_source_corpus(options)
|
|||||||
for _, include in ipairs(M.parse_direct_quoted_includes(root.text)) do ---@type integer, QuotedInclude
|
for _, include in ipairs(M.parse_direct_quoted_includes(root.text)) do ---@type integer, QuotedInclude
|
||||||
local candidate_a = absolute_normalized_path(root.dir .. "/" .. include.path) ---@type Path
|
local candidate_a = absolute_normalized_path(root.dir .. "/" .. include.path) ---@type Path
|
||||||
local candidate_b = absolute_normalized_path(code_root .. "/" .. include.path) ---@type Path
|
local candidate_b = absolute_normalized_path(code_root .. "/" .. include.path) ---@type Path
|
||||||
local key_a = M.canonical_path_key(candidate_a) ---@type string
|
local key_a = M.canonical_path_key(candidate_a) ---@type string
|
||||||
local key_b = M.canonical_path_key(candidate_b) ---@type string
|
local key_b = M.canonical_path_key(candidate_b) ---@type string
|
||||||
local inside_a = canonical_key_is_within(key_a, code_root_key) ---@type boolean
|
local inside_a = canonical_key_is_within(key_a, code_root_key) ---@type boolean
|
||||||
local inside_b = canonical_key_is_within(key_b, code_root_key) ---@type boolean
|
local inside_b = canonical_key_is_within(key_b, code_root_key) ---@type boolean
|
||||||
local evidence = { ---@type ResolverEvidence
|
local evidence = { ---@type ResolverEvidence
|
||||||
include_path = include.path,
|
include_path = include.path,
|
||||||
include_text = include.include_text,
|
include_text = include.include_text,
|
||||||
root_source = root.path,
|
root_source = root.path,
|
||||||
@@ -854,9 +854,9 @@ function M.resolve_source_corpus(options)
|
|||||||
-- Boundary checks above deliberately precede every filesystem probe.
|
-- Boundary checks above deliberately precede every filesystem probe.
|
||||||
local exists_a = inside_a and lfs.attributes(candidate_a, "mode") == "file" ---@type boolean
|
local exists_a = inside_a and lfs.attributes(candidate_a, "mode") == "file" ---@type boolean
|
||||||
local exists_b = inside_b and ((key_b == key_a and exists_a) or lfs.attributes(candidate_b, "mode") == "file") ---@type boolean
|
local exists_b = inside_b and ((key_b == key_a and exists_a) or lfs.attributes(candidate_b, "mode") == "file") ---@type boolean
|
||||||
local selected = nil ---@type Path|nil
|
local selected = nil ---@type Path|nil
|
||||||
local selected_key = nil ---@type string|nil
|
local selected_key = nil ---@type string|nil
|
||||||
local disposition = nil ---@type string|nil
|
local disposition = nil ---@type string|nil
|
||||||
if exists_a then
|
if exists_a then
|
||||||
selected = candidate_a
|
selected = candidate_a
|
||||||
selected_key = key_a
|
selected_key = key_a
|
||||||
@@ -1075,7 +1075,7 @@ function M.build_body_line_index(body)
|
|||||||
local index = {} ---@type table<integer, integer> -- bag: byte offset -> 1-based line
|
local index = {} ---@type table<integer, integer> -- bag: byte offset -> 1-based line
|
||||||
local len = #body ---@type integer
|
local len = #body ---@type integer
|
||||||
local newline_count = 0 ---@type integer
|
local newline_count = 0 ---@type integer
|
||||||
for pos = 1, len do ---@type integer
|
for pos = 1, len do ---@type integer
|
||||||
if pos > 1 then
|
if pos > 1 then
|
||||||
index[pos] = newline_count + 1
|
index[pos] = newline_count + 1
|
||||||
end
|
end
|
||||||
|
|||||||
+33
-33
@@ -392,7 +392,7 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
function M.read_sleb128_at(buf, pos)
|
function M.read_sleb128_at(buf, pos)
|
||||||
local value, shift = 0, 0 ---@type integer, integer
|
local value, shift = 0, 0 ---@type integer, integer
|
||||||
local len = #buf ---@type integer
|
local len = #buf ---@type integer
|
||||||
while pos < len do
|
while pos < len do
|
||||||
local b = buf:byte(pos + 1) ---@type integer
|
local b = buf:byte(pos + 1) ---@type integer
|
||||||
value = value + (b % 0x80) * (2 ^ shift)
|
value = value + (b % 0x80) * (2 ^ shift)
|
||||||
@@ -451,7 +451,7 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function read_c_string_at(buf, off)
|
local function read_c_string_at(buf, off)
|
||||||
local len = #buf ---@type integer
|
local len = #buf ---@type integer
|
||||||
local start = off ---@type integer
|
local start = off ---@type integer
|
||||||
while off < len and buf:byte(off + 1) ~= 0 do off = off + 1 end
|
while off < len and buf:byte(off + 1) ~= 0 do off = off + 1 end
|
||||||
return buf:sub(start + 1, off)
|
return buf:sub(start + 1, off)
|
||||||
end
|
end
|
||||||
@@ -466,7 +466,7 @@ end
|
|||||||
local function parse_abbrev_table(table_bytes, table_start)
|
local function parse_abbrev_table(table_bytes, table_start)
|
||||||
local table_end = M.find_abbrev_table_end(table_bytes, table_start) ---@type integer|nil
|
local table_end = M.find_abbrev_table_end(table_bytes, table_start) ---@type integer|nil
|
||||||
if not table_end then return nil, "no terminator" end
|
if not table_end then return nil, "no terminator" end
|
||||||
local decls = {} ---@type AbbrevDecl[]
|
local decls = {} ---@type AbbrevDecl[]
|
||||||
local pos = table_start ---@type integer
|
local pos = table_start ---@type integer
|
||||||
while pos < table_end do
|
while pos < table_end do
|
||||||
local code, code_end = M.read_uleb128_at(table_bytes, pos) ---@type integer|nil, integer
|
local code, code_end = M.read_uleb128_at(table_bytes, pos) ---@type integer|nil, integer
|
||||||
@@ -667,7 +667,7 @@ function M.read_ref_sig8(buf, pos) return M.read_u32_le(buf, pos), M.read_u32_le
|
|||||||
--- @param target_sig_hi integer -- high 4 bytes (LE) of the desired signature
|
--- @param target_sig_hi integer -- high 4 bytes (LE) of the desired signature
|
||||||
--- @return integer|nil, integer|nil -- unit offset, type_offset within the unit
|
--- @return integer|nil, integer|nil -- unit offset, type_offset within the unit
|
||||||
function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi)
|
function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi)
|
||||||
local pos = 0 ---@type integer
|
local pos = 0 ---@type integer
|
||||||
local section_len = #info ---@type integer
|
local section_len = #info ---@type integer
|
||||||
while pos + 4 < section_len do
|
while pos + 4 < section_len do
|
||||||
local unit_length = M.read_u32_le(info, pos) ---@type integer
|
local unit_length = M.read_u32_le(info, pos) ---@type integer
|
||||||
@@ -675,7 +675,7 @@ function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi)
|
|||||||
return nil, nil -- DWARF64 not supported
|
return nil, nil -- DWARF64 not supported
|
||||||
end
|
end
|
||||||
-- unit_length is the body size, NOT including the 4-byte unit_length field itself.
|
-- unit_length is the body size, NOT including the 4-byte unit_length field itself.
|
||||||
local body_start = pos + 4 ---@type integer
|
local body_start = pos + 4 ---@type integer
|
||||||
local body_end = body_start + unit_length ---@type integer
|
local body_end = body_start + unit_length ---@type integer
|
||||||
if body_end > section_len then
|
if body_end > section_len then
|
||||||
return nil, nil -- malformed
|
return nil, nil -- malformed
|
||||||
@@ -754,11 +754,11 @@ end
|
|||||||
function M.read_elf_sections(elf_path, section_names)
|
function M.read_elf_sections(elf_path, section_names)
|
||||||
-- Initialize result with all requested names set to "" so callers can do `sections[X]
|
-- Initialize result with all requested names set to "" so callers can do `sections[X]
|
||||||
-- or ""` for missing sections without nil-checks.
|
-- or ""` for missing sections without nil-checks.
|
||||||
local result = {} ---@type table<string, string>
|
local result = {} ---@type table<string, string>
|
||||||
for _, name in ipairs(section_names) do result[name] = "" end ---@type integer, string
|
for _, name in ipairs(section_names) do result[name] = "" end ---@type integer, string
|
||||||
|
|
||||||
-- O(1) lookup set.
|
-- O(1) lookup set.
|
||||||
local wanted = {} ---@type table<string, boolean> -- bag: requested section name -> true
|
local wanted = {} ---@type table<string, boolean> -- bag: requested section name -> true
|
||||||
for _, name in ipairs(section_names) do wanted[name] = true end ---@type integer, string
|
for _, name in ipairs(section_names) do wanted[name] = true end ---@type integer, string
|
||||||
|
|
||||||
-- Existence check (lfs.attributes avoids an io.open-vs-fail race).
|
-- Existence check (lfs.attributes avoids an io.open-vs-fail race).
|
||||||
@@ -1006,7 +1006,7 @@ end
|
|||||||
--- @param n integer -- any integer (negative allowed)
|
--- @param n integer -- any integer (negative allowed)
|
||||||
--- @return string
|
--- @return string
|
||||||
function M.sleb128(n)
|
function M.sleb128(n)
|
||||||
local bytes = {} ---@type string[]
|
local bytes = {} ---@type string[]
|
||||||
local more = true ---@type boolean
|
local more = true ---@type boolean
|
||||||
while more do
|
while more do
|
||||||
local b = n % (LEB_DATA_MASK + 1) ---@type integer -- extract low 7 bits
|
local b = n % (LEB_DATA_MASK + 1) ---@type integer -- extract low 7 bits
|
||||||
@@ -1045,8 +1045,8 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
function M.sleb128_size(n)
|
function M.sleb128_size(n)
|
||||||
local more = true ---@type boolean
|
local more = true ---@type boolean
|
||||||
local bytes = 0 ---@type integer
|
local bytes = 0 ---@type integer
|
||||||
local v = n ---@type integer
|
local v = n ---@type integer
|
||||||
while more do
|
while more do
|
||||||
local b = v % (LEB_DATA_MASK + 1) ---@type integer -- extract low 7 bits
|
local b = v % (LEB_DATA_MASK + 1) ---@type integer -- extract low 7 bits
|
||||||
v = (v - b) / (LEB_DATA_MASK + 1) -- arithmetic shift right by 7
|
v = (v - b) / (LEB_DATA_MASK + 1) -- arithmetic shift right by 7
|
||||||
@@ -1091,8 +1091,8 @@ end
|
|||||||
--- @return table<integer, string>|nil
|
--- @return table<integer, string>|nil
|
||||||
function M.read_line_unit_file_table(elf_path)
|
function M.read_line_unit_file_table(elf_path)
|
||||||
local sections = M.read_elf_sections(elf_path, { ".debug_line", ".debug_line_str" }) ---@type table<string, string>
|
local sections = M.read_elf_sections(elf_path, { ".debug_line", ".debug_line_str" }) ---@type table<string, string>
|
||||||
local line = sections[".debug_line"] ---@type string
|
local line = sections[".debug_line"] ---@type string
|
||||||
local lstr = sections[".debug_line_str"] or "" ---@type string
|
local lstr = sections[".debug_line_str"] or "" ---@type string
|
||||||
if not line or line == "" then
|
if not line or line == "" then
|
||||||
io.stderr:write("[elf_dwarf.read_line_unit_file_table] no .debug_line section in: " .. tostring(elf_path) .. "\n")
|
io.stderr:write("[elf_dwarf.read_line_unit_file_table] no .debug_line section in: " .. tostring(elf_path) .. "\n")
|
||||||
return nil
|
return nil
|
||||||
@@ -1115,7 +1115,7 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
local function read_form(buf, lstr_buf, p, form)
|
local function read_form(buf, lstr_buf, p, form)
|
||||||
if form == M.DWARF5_DEBUG_LINE.form_line_strp then
|
if form == M.DWARF5_DEBUG_LINE.form_line_strp then
|
||||||
local strp = M.read_u32_le(buf, p) ---@type integer
|
local strp = M.read_u32_le(buf, p) ---@type integer
|
||||||
local end_pos = lstr_buf:find("\0", strp + 1, true) or (#lstr_buf + 1) ---@type integer
|
local end_pos = lstr_buf:find("\0", strp + 1, true) or (#lstr_buf + 1) ---@type integer
|
||||||
return lstr_buf:sub(strp + 1, end_pos - 1), p + M.DWARF5_DEBUG_LINE.form_strp_bytes
|
return lstr_buf:sub(strp + 1, end_pos - 1), p + M.DWARF5_DEBUG_LINE.form_strp_bytes
|
||||||
elseif form == M.DWARF5_DEBUG_LINE.form_string then
|
elseif form == M.DWARF5_DEBUG_LINE.form_string then
|
||||||
@@ -1159,7 +1159,7 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
up = nul
|
up = nul
|
||||||
end
|
end
|
||||||
local unit_basenames = {} ---@type table<integer, string> -- bag: 1-based unit file index -> basename
|
local unit_basenames = {} ---@type table<integer, string> -- bag: 1-based unit file index -> basename
|
||||||
local unit_paths = {} ---@type table<integer, string> -- bag: 1-based unit file index -> full path
|
local unit_paths = {} ---@type table<integer, string> -- bag: 1-based unit file index -> full path
|
||||||
while up < body_end do
|
while up < body_end do
|
||||||
local nul = buf:find("\0", up + 1, true) or (body_end + 1) ---@type integer
|
local nul = buf:find("\0", up + 1, true) or (body_end + 1) ---@type integer
|
||||||
if nul > body_end or nul == up + 1 then up = nul break end
|
if nul > body_end or nul == up + 1 then up = nul break end
|
||||||
@@ -1168,8 +1168,8 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
local didx, up_next = M.read_uleb128_at(buf, up); up = up_next ---@type integer|nil, integer
|
local didx, up_next = M.read_uleb128_at(buf, up); up = up_next ---@type integer|nil, integer
|
||||||
local _time, up_next2 = M.read_uleb128_at(buf, up); up = up_next2 ---@type integer|nil, integer
|
local _time, up_next2 = M.read_uleb128_at(buf, up); up = up_next2 ---@type integer|nil, integer
|
||||||
local _size, up_next3 = M.read_uleb128_at(buf, up); up = up_next3 ---@type integer|nil, integer
|
local _size, up_next3 = M.read_uleb128_at(buf, up); up = up_next3 ---@type integer|nil, integer
|
||||||
local idx = #unit_basenames + 1 ---@type integer
|
local idx = #unit_basenames + 1 ---@type integer
|
||||||
local bs = path:match("[^/\\]+$") or path ---@type string
|
local bs = path:match("[^/\\]+$") or path ---@type string
|
||||||
unit_paths[idx] = path
|
unit_paths[idx] = path
|
||||||
unit_basenames[idx] = bs
|
unit_basenames[idx] = bs
|
||||||
dirs[1] = dirs[1] or "" -- safety: gcc emits "" sentinel dir at 0
|
dirs[1] = dirs[1] or "" -- safety: gcc emits "" sentinel dir at 0
|
||||||
@@ -1196,15 +1196,15 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
up = up + (opcode_base - 1) -- std_opcode_lengths
|
up = up + (opcode_base - 1) -- std_opcode_lengths
|
||||||
-- directories
|
-- directories
|
||||||
local dir_format_count, after = M.read_uleb128_at(buf, up); up = after ---@type integer|nil, integer
|
local dir_format_count, after = M.read_uleb128_at(buf, up); up = after ---@type integer|nil, integer
|
||||||
local dir_formats = {} ---@type integer[]
|
local dir_formats = {} ---@type integer[]
|
||||||
for i = 1, dir_format_count do ---@type integer
|
for i = 1, dir_format_count do ---@type integer
|
||||||
local f, a2 = M.read_uleb128_at(buf, up); up = a2 ---@type integer|nil, integer
|
local f, a2 = M.read_uleb128_at(buf, up); up = a2 ---@type integer|nil, integer
|
||||||
dir_formats[i] = f
|
dir_formats[i] = f
|
||||||
end
|
end
|
||||||
local dir_count, a3 = M.read_uleb128_at(buf, up); up = a3 ---@type integer|nil, integer
|
local dir_count, a3 = M.read_uleb128_at(buf, up); up = a3 ---@type integer|nil, integer
|
||||||
local dirs = {} ---@type string[]
|
local dirs = {} ---@type string[]
|
||||||
for i = 1, dir_count do ---@type integer
|
for i = 1, dir_count do ---@type integer
|
||||||
local combined = "" ---@type string
|
local combined = "" ---@type string
|
||||||
for j = 1, dir_format_count do ---@type integer
|
for j = 1, dir_format_count do ---@type integer
|
||||||
local v, a4 = read_form(buf, lstr_buf, up, dir_formats[j]) ---@type string|integer|nil, integer
|
local v, a4 = read_form(buf, lstr_buf, up, dir_formats[j]) ---@type string|integer|nil, integer
|
||||||
up = a4
|
up = a4
|
||||||
@@ -1214,24 +1214,24 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
end
|
end
|
||||||
-- file names
|
-- file names
|
||||||
local file_format_count, after2 = M.read_uleb128_at(buf, up); up = after2 ---@type integer|nil, integer
|
local file_format_count, after2 = M.read_uleb128_at(buf, up); up = after2 ---@type integer|nil, integer
|
||||||
local file_formats = {} ---@type integer[]
|
local file_formats = {} ---@type integer[]
|
||||||
for i = 1, file_format_count do ---@type integer
|
for i = 1, file_format_count do ---@type integer
|
||||||
local f, a2 = M.read_uleb128_at(buf, up); up = a2 ---@type integer|nil, integer
|
local f, a2 = M.read_uleb128_at(buf, up); up = a2 ---@type integer|nil, integer
|
||||||
file_formats[i] = f
|
file_formats[i] = f
|
||||||
end
|
end
|
||||||
local file_count, a3 = M.read_uleb128_at(buf, up); up = a3 ---@type integer|nil, integer
|
local file_count, a3 = M.read_uleb128_at(buf, up); up = a3 ---@type integer|nil, integer
|
||||||
local unit_basenames = {} ---@type table<integer, string> -- bag: 1-based unit file index -> basename
|
local unit_basenames = {} ---@type table<integer, string> -- bag: 1-based unit file index -> basename
|
||||||
local unit_paths = {} ---@type table<integer, string> -- bag: 1-based unit file index -> full path
|
local unit_paths = {} ---@type table<integer, string> -- bag: 1-based unit file index -> full path
|
||||||
for i = 1, file_count do ---@type integer
|
for i = 1, file_count do ---@type integer
|
||||||
local combined = "" ---@type string
|
local combined = "" ---@type string
|
||||||
local didx = 0 ---@type integer
|
local didx = 0 ---@type integer
|
||||||
for j = 1, file_format_count do ---@type integer
|
for j = 1, file_format_count do ---@type integer
|
||||||
local v, a4 = read_form(buf, lstr_buf, up, file_formats[j]) ---@type string|integer|nil, integer
|
local v, a4 = read_form(buf, lstr_buf, up, file_formats[j]) ---@type string|integer|nil, integer
|
||||||
up = a4
|
up = a4
|
||||||
if j == 1 and type(v) == "string" then combined = v end
|
if j == 1 and type(v) == "string" then combined = v end
|
||||||
if j == 2 and type(v) == "number" then didx = v end
|
if j == 2 and type(v) == "number" then didx = v end
|
||||||
end
|
end
|
||||||
local idx = #unit_basenames + 1 ---@type integer
|
local idx = #unit_basenames + 1 ---@type integer
|
||||||
local bs = combined:match("[^/\\]+$") or combined ---@type string
|
local bs = combined:match("[^/\\]+$") or combined ---@type string
|
||||||
unit_paths[idx] = combined
|
unit_paths[idx] = combined
|
||||||
unit_basenames[idx] = bs
|
unit_basenames[idx] = bs
|
||||||
@@ -1243,7 +1243,7 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Walk every line-program unit in the section.
|
--- Walk every line-program unit in the section.
|
||||||
local p = 0 ---@type integer
|
local p = 0 ---@type integer
|
||||||
local section_end = #line ---@type integer
|
local section_end = #line ---@type integer
|
||||||
while p + 4 <= section_end do
|
while p + 4 <= section_end do
|
||||||
local unit_length = M.read_u32_le(line, p) ---@type integer
|
local unit_length = M.read_u32_le(line, p) ---@type integer
|
||||||
@@ -1255,11 +1255,11 @@ function M.read_line_unit_file_table(elf_path)
|
|||||||
local body_end = p + 4 + unit_length ---@type integer
|
local body_end = p + 4 + unit_length ---@type integer
|
||||||
if body_end > section_end then break end
|
if body_end > section_end then break end
|
||||||
local version = M.read_u16_le(line, body_start) ---@type integer
|
local version = M.read_u16_le(line, body_start) ---@type integer
|
||||||
local unit_basenames, unit_paths ---@type table<integer, string>|nil, table<integer, string>|nil
|
local unit_basenames, unit_paths ---@type table<integer, string>|nil, table<integer, string>|nil
|
||||||
if version >= 5 then
|
if version >= 5 then
|
||||||
-- DWARF5 header: version(2) + addr_size(1) + seg_size(1) + header_length(4) + content
|
-- DWARF5 header: version(2) + addr_size(1) + seg_size(1) + header_length(4) + content
|
||||||
local header_length_offset = body_start + 6 ---@type integer -- past version(2) + addr_size(1) + seg_size(1) - wait that's wrong; past hdr len is at +6
|
local header_length_offset = body_start + 6 ---@type integer -- past version(2) + addr_size(1) + seg_size(1) - wait that's wrong; past hdr len is at +6
|
||||||
local content_start = body_start + 8 ---@type integer -- past version(2) + addr_size(1) + seg_size(1) + header_length(4)
|
local content_start = body_start + 8 ---@type integer -- past version(2) + addr_size(1) + seg_size(1) + header_length(4)
|
||||||
unit_basenames, unit_paths = parse_dwarf5_unit(line, lstr, content_start, body_end)
|
unit_basenames, unit_paths = parse_dwarf5_unit(line, lstr, content_start, body_end)
|
||||||
elseif version >= 2 then
|
elseif version >= 2 then
|
||||||
-- DWARF2/3/4 header: version(2) + header_length(4) + content
|
-- DWARF2/3/4 header: version(2) + header_length(4) + content
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
-- Bootstrap follows the entry scripts; `scripts/duffle_paths.lua` sets package.path and package.cpath. See `ps1_meta.lua` for the rationale.
|
-- Bootstrap follows the entry scripts; `scripts/duffle_paths.lua` sets package.path and package.cpath. See `ps1_meta.lua` for the rationale.
|
||||||
-- `debug.getinfo(1, "S").source` locates this file for standalone and orchestrated runs, then `duffle_paths.lua` returns the loaded `duffle` module.
|
-- `debug.getinfo(1, "S").source` locates this file for standalone and orchestrated runs, then `duffle_paths.lua` returns the loaded `duffle` module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- The annotation pass reads the source-derived registries from scan_source:
|
-- The annotation pass reads the source-derived registries from scan_source:
|
||||||
-- * pipe_ctx.register_alias_registry — for atom_dbg_reg_default(R_X, ...) and atom_reg_types(R_X, ...) member-identity checks
|
-- * pipe_ctx.register_alias_registry — for atom_dbg_reg_default(R_X, ...) and atom_reg_types(R_X, ...) member-identity checks
|
||||||
@@ -140,7 +140,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function check_macro_word_drift(m, pipe_ctx, findings)
|
local function check_macro_word_drift(m, pipe_ctx, findings)
|
||||||
local wc = (pipe_ctx and pipe_ctx.word_counts) or {} ---@type WordCounts
|
local wc = (pipe_ctx and pipe_ctx.word_counts) or {} ---@type WordCounts
|
||||||
local declared = wc[m.name] ---@type integer|nil
|
local declared = wc[m.name] ---@type integer|nil
|
||||||
if not declared then
|
if not declared then
|
||||||
findings.errors[#findings.errors + 1] = {
|
findings.errors[#findings.errors + 1] = {
|
||||||
line = m.line,
|
line = m.line,
|
||||||
@@ -169,7 +169,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function check_semantic_reg_defaults(_src, pipe_ctx, findings)
|
local function check_semantic_reg_defaults(_src, pipe_ctx, findings)
|
||||||
-- Detect duplicate defaults using the ordered occurrence list (the out.types hash only retains the last declaration).
|
-- Detect duplicate defaults using the ordered occurrence list (the out.types hash only retains the last declaration).
|
||||||
local seen_first_line = {} ---@type table<string, integer> -- bag: register ident -> first source line
|
local seen_first_line = {} ---@type table<string, integer> -- bag: register ident -> first source line
|
||||||
for _, occ in ipairs(pipe_ctx.type_occurrences or {}) do ---@type integer, RegTypeOccurrence
|
for _, occ in ipairs(pipe_ctx.type_occurrences or {}) do ---@type integer, RegTypeOccurrence
|
||||||
if seen_first_line[occ.reg] == nil then
|
if seen_first_line[occ.reg] == nil then
|
||||||
seen_first_line[occ.reg] = occ.source_line
|
seen_first_line[occ.reg] = occ.source_line
|
||||||
@@ -184,7 +184,7 @@ local function check_semantic_reg_defaults(_src, pipe_ctx, findings)
|
|||||||
end
|
end
|
||||||
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
||||||
local type_registry = pipe_ctx.type_name_registry or {} ---@type table<string, TypeNameEntry>
|
local type_registry = pipe_ctx.type_name_registry or {} ---@type table<string, TypeNameEntry>
|
||||||
for reg, def in pairs(pipe_ctx.types or {}) do ---@type string, RegTypeDefault
|
for reg, def in pairs(pipe_ctx.types or {}) do ---@type string, RegTypeDefault
|
||||||
if not reg_registry[reg] then
|
if not reg_registry[reg] then
|
||||||
findings.errors[#findings.errors + 1] = {
|
findings.errors[#findings.errors + 1] = {
|
||||||
line = def.source_line,
|
line = def.source_line,
|
||||||
@@ -221,7 +221,7 @@ end
|
|||||||
local function check_atom_reg_types(_src, pipe_ctx, findings)
|
local function check_atom_reg_types(_src, pipe_ctx, findings)
|
||||||
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
||||||
local type_registry = pipe_ctx.type_name_registry or {} ---@type table<string, TypeNameEntry>
|
local type_registry = pipe_ctx.type_name_registry or {} ---@type table<string, TypeNameEntry>
|
||||||
for _, ai in ipairs(pipe_ctx.atom_infos_list or {}) do ---@type integer, AtomInfoEntry
|
for _, ai in ipairs(pipe_ctx.atom_infos_list or {}) do ---@type integer, AtomInfoEntry
|
||||||
if ai.reg_type_overrides then
|
if ai.reg_type_overrides then
|
||||||
for reg, ov in pairs(ai.reg_type_overrides) do ---@type string, RegTypeOverride
|
for reg, ov in pairs(ai.reg_type_overrides) do ---@type string, RegTypeOverride
|
||||||
if not reg_registry[reg] then
|
if not reg_registry[reg] then
|
||||||
@@ -282,7 +282,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function check_binds_no_duplicate_fields(_src, pipe_ctx, findings)
|
local function check_binds_no_duplicate_fields(_src, pipe_ctx, findings)
|
||||||
for _, bs in ipairs(pipe_ctx.binds_list or {}) do ---@type integer, BindsEntry
|
for _, bs in ipairs(pipe_ctx.binds_list or {}) do ---@type integer, BindsEntry
|
||||||
local seen = {} ---@type table<string, integer> -- bag: field name -> occurrence count
|
local seen = {} ---@type table<string, integer> -- bag: field name -> occurrence count
|
||||||
for _, f in ipairs(bs.fields or {}) do ---@type integer, TypeField
|
for _, f in ipairs(bs.fields or {}) do ---@type integer, TypeField
|
||||||
seen[f.name] = (seen[f.name] or 0) + 1
|
seen[f.name] = (seen[f.name] or 0) + 1
|
||||||
end
|
end
|
||||||
@@ -375,7 +375,7 @@ local function check_wave_context_migration(_src, pipe_ctx, findings)
|
|||||||
if not (pipe_ctx.types and next(pipe_ctx.types)) then return end
|
if not (pipe_ctx.types and next(pipe_ctx.types)) then return end
|
||||||
if not (pipe_ctx.atom_infos_list) then return end
|
if not (pipe_ctx.atom_infos_list) then return end
|
||||||
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
local reg_registry = pipe_ctx.register_alias_registry or {} ---@type table<string, AliasEntry>
|
||||||
for _, ai in ipairs(pipe_ctx.atom_infos_list) do ---@type integer, AtomInfoEntry
|
for _, ai in ipairs(pipe_ctx.atom_infos_list) do ---@type integer, AtomInfoEntry
|
||||||
if ai.reg_type_overrides then
|
if ai.reg_type_overrides then
|
||||||
for reg, _ in pairs(ai.reg_type_overrides) do ---@type string, RegTypeOverride
|
for reg, _ in pairs(ai.reg_type_overrides) do ---@type string, RegTypeOverride
|
||||||
if not reg_registry[reg] then
|
if not reg_registry[reg] then
|
||||||
@@ -428,8 +428,8 @@ local CHECK_RULES = { ---@type CheckRule[]
|
|||||||
--- @return PipeCtx
|
--- @return PipeCtx
|
||||||
local function build_corpus_pipe_ctx(ctx)
|
local function build_corpus_pipe_ctx(ctx)
|
||||||
local view = duffle.corpus_view(ctx) ---@type PipeCtx
|
local view = duffle.corpus_view(ctx) ---@type PipeCtx
|
||||||
local annot_counts = {} ---@type table<string, integer> -- bag: atom name -> annotation count
|
local annot_counts = {} ---@type table<string, integer> -- bag: atom name -> annotation count
|
||||||
for _, info in ipairs(view.atom_infos) do ---@type integer, AtomInfoEntry
|
for _, info in ipairs(view.atom_infos) do ---@type integer, AtomInfoEntry
|
||||||
if info and info.atom_name then
|
if info and info.atom_name then
|
||||||
annot_counts[info.atom_name] = (annot_counts[info.atom_name] or 0) + 1
|
annot_counts[info.atom_name] = (annot_counts[info.atom_name] or 0) + 1
|
||||||
end
|
end
|
||||||
@@ -467,7 +467,7 @@ 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,
|
||||||
}
|
}
|
||||||
local atoms = {} ---@type AtomEntry[]
|
local atoms = {} ---@type AtomEntry[]
|
||||||
for _, a in ipairs(scan.atoms) do ---@type integer, AtomEntry
|
for _, a in ipairs(scan.atoms) do ---@type integer, AtomEntry
|
||||||
if a.kind == "atom" or a.kind == "atom_proc" then
|
if a.kind == "atom" or a.kind == "atom_proc" then
|
||||||
atoms[#atoms + 1] = a
|
atoms[#atoms + 1] = a
|
||||||
@@ -503,7 +503,7 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
-- scan_source records each marker in scan.debug_skip_markers; this loop validates each record independently and emits at most one error per marker.
|
-- scan_source records each marker in scan.debug_skip_markers; this loop validates each record independently and emits at most one error per marker.
|
||||||
-- Valid markers stamp `debug_skip = true` on the following atom or component declaration, which downstream consumers read directly.
|
-- Valid markers stamp `debug_skip = true` on the following atom or component declaration, which downstream consumers read directly.
|
||||||
local skip_markers = scan.debug_skip_markers or {} ---@type DebugSkipMarker[]
|
local skip_markers = scan.debug_skip_markers or {} ---@type DebugSkipMarker[]
|
||||||
for _, marker in ipairs(skip_markers) do ---@type integer, DebugSkipMarker
|
for _, marker in ipairs(skip_markers) do ---@type integer, DebugSkipMarker
|
||||||
duffle.run_check_rules(CHECK_RULES, "per_skip_marker", marker, pipe_ctx, findings)
|
duffle.run_check_rules(CHECK_RULES, "per_skip_marker", marker, pipe_ctx, findings)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -554,17 +554,17 @@ 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) ---@type PipeCtx
|
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx) ---@type PipeCtx
|
||||||
local corpus = ctx.shared.corpus ---@type Corpus
|
local corpus = ctx.shared.corpus ---@type Corpus
|
||||||
|
|
||||||
-- Group `corpus.sources_by_dir` by module, validate every source in each bucket, and emit one errors.h per directory.
|
-- 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 {} ---@type table<string, SourceFile[]>
|
local by_dir = (corpus and corpus.sources_by_dir) or {} ---@type table<string, SourceFile[]>
|
||||||
|
|
||||||
for dir, dir_sources in pairs(by_dir) do ---@type string, SourceFile[]
|
for dir, dir_sources in pairs(by_dir) do ---@type string, SourceFile[]
|
||||||
local dir_basename = dir:match("([^/\\]+)$") or dir ---@type string
|
local dir_basename = dir:match("([^/\\]+)$") or dir ---@type string
|
||||||
local dir_atoms = 0 ---@type integer
|
local dir_atoms = 0 ---@type integer
|
||||||
local dir_errors = {} ---@type PassFinding[]
|
local dir_errors = {} ---@type PassFinding[]
|
||||||
local dir_warnings = {} ---@type PassFinding[]
|
local dir_warnings = {} ---@type PassFinding[]
|
||||||
for _, src in ipairs(dir_sources) do ---@type integer, SourceFile
|
for _, src in ipairs(dir_sources) do ---@type integer, SourceFile
|
||||||
local result = validate(ctx, src, corpus_pipe_ctx) ---@type AnnotatedResult
|
local result = validate(ctx, src, corpus_pipe_ctx) ---@type AnnotatedResult
|
||||||
result.source = src.path -- tag for downstream rendering
|
result.source = src.path -- tag for downstream rendering
|
||||||
dir_atoms = dir_atoms + #result.atoms
|
dir_atoms = dir_atoms + #result.atoms
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
-- (works both standalone + when require'd). `duffle_paths.lua` sets package.path then returns `require("duffle")`
|
-- (works both standalone + when require'd). `duffle_paths.lua` sets package.path then returns `require("duffle")`
|
||||||
-- at the bottom, so the dofile value IS the duffle module.
|
-- at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
local elf_dwarf = require("elf_dwarf") ---@type ElfDwarfMod
|
local elf_dwarf = require("elf_dwarf") ---@type ElfDwarfMod
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Constants
|
-- Constants
|
||||||
@@ -103,14 +103,14 @@ local FORMAT_VERSION = 1 ---@type integer
|
|||||||
--- @return WordMapEntry[]
|
--- @return WordMapEntry[]
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function canonical_word_entries(atom)
|
local function canonical_word_entries(atom)
|
||||||
local paths = atom.paths or {} ---@type AtomPaths
|
local paths = atom.paths or {} ---@type AtomPaths
|
||||||
local events = paths.word_events or {} ---@type WordEvent[]
|
local events = paths.word_events or {} ---@type WordEvent[]
|
||||||
local word_items = {} ---@type EmissionItem[]
|
local word_items = {} ---@type EmissionItem[]
|
||||||
for _, item in ipairs(paths.items or {}) do ---@type integer, EmissionItem
|
for _, item in ipairs(paths.items or {}) do ---@type integer, EmissionItem
|
||||||
if item.kind == "word" then word_items[#word_items + 1] = item end
|
if item.kind == "word" then word_items[#word_items + 1] = item end
|
||||||
end
|
end
|
||||||
|
|
||||||
local entries = {} ---@type WordMapEntry[]
|
local entries = {} ---@type WordMapEntry[]
|
||||||
for index, event in ipairs(events) do ---@type integer, WordEvent
|
for index, event in ipairs(events) do ---@type integer, WordEvent
|
||||||
local item = word_items[index] or {} ---@type EmissionItem
|
local item = word_items[index] or {} ---@type EmissionItem
|
||||||
entries[#entries + 1] = {
|
entries[#entries + 1] = {
|
||||||
@@ -138,13 +138,13 @@ end
|
|||||||
--- @return string[]
|
--- @return string[]
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function emit_provenance_stanza(src, atom, wc)
|
local function emit_provenance_stanza(src, atom, wc)
|
||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
||||||
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
||||||
lines[#lines + 1] = string.format('ATOM %s "%s" 0', atom.raw_name or atom.name, rel_path)
|
lines[#lines + 1] = string.format('ATOM %s "%s" 0', atom.raw_name or atom.name, rel_path)
|
||||||
|
|
||||||
for _, entry in ipairs(entries) do ---@type integer, WordMapEntry
|
for _, entry in ipairs(entries) do ---@type integer, WordMapEntry
|
||||||
local inv = entry.invocation ---@type InvocationRecord|nil
|
local inv = entry.invocation ---@type InvocationRecord|nil
|
||||||
local macro_count = inv and wc["mac_" .. inv.component_name] ---@type integer|nil
|
local macro_count = inv and wc["mac_" .. inv.component_name] ---@type integer|nil
|
||||||
if inv and macro_count ~= nil then
|
if inv and macro_count ~= nil then
|
||||||
lines[#lines + 1] = string.format('WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d'
|
lines[#lines + 1] = string.format('WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d'
|
||||||
@@ -177,7 +177,7 @@ local function render_provenance(src, wc)
|
|||||||
--- @param atom AtomEntry
|
--- @param atom AtomEntry
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function append(atom)
|
local function append(atom)
|
||||||
local stanza = emit_provenance_stanza(src, atom, wc) ---@type string[]
|
local stanza = emit_provenance_stanza(src, atom, wc) ---@type string[]
|
||||||
for _, line in ipairs(stanza) do lines[#lines + 1] = line end ---@type integer, string
|
for _, line in ipairs(stanza) do lines[#lines + 1] = line end ---@type integer, string
|
||||||
end
|
end
|
||||||
for _, atom in ipairs(src.scan.atoms or {}) do ---@type integer, AtomEntry
|
for _, atom in ipairs(src.scan.atoms or {}) do ---@type integer, AtomEntry
|
||||||
@@ -197,8 +197,8 @@ end
|
|||||||
--- @return string[]
|
--- @return string[]
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function emit_atom_stanza(src, atom)
|
local function emit_atom_stanza(src, atom)
|
||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
||||||
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
||||||
|
|
||||||
lines[#lines + 1] = string.format('ATOM %s "%s" 0', atom.raw_name or atom.name, rel_path)
|
lines[#lines + 1] = string.format('ATOM %s "%s" 0', atom.raw_name or atom.name, rel_path)
|
||||||
@@ -223,7 +223,7 @@ local function render_source_map(src)
|
|||||||
--- @param atom AtomEntry
|
--- @param atom AtomEntry
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function append(atom)
|
local function append(atom)
|
||||||
local stanza = emit_atom_stanza(src, atom) ---@type string[]
|
local stanza = emit_atom_stanza(src, atom) ---@type string[]
|
||||||
for _, line in ipairs(stanza) do lines[#lines + 1] = line end ---@type integer, string
|
for _, line in ipairs(stanza) do lines[#lines + 1] = line end ---@type integer, string
|
||||||
end
|
end
|
||||||
for _, atom in ipairs(src.scan.atoms or {}) do ---@type integer, AtomEntry
|
for _, atom in ipairs(src.scan.atoms or {}) do ---@type integer, AtomEntry
|
||||||
@@ -253,8 +253,8 @@ end
|
|||||||
--- @return GdbAtomRecord[]
|
--- @return GdbAtomRecord[]
|
||||||
local function build_atom_table(ctx)
|
local function build_atom_table(ctx)
|
||||||
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path) ---@type table<string, NmAddr>
|
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path) ---@type table<string, NmAddr>
|
||||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||||
local matched = {} ---@type GdbAtomRecord[]
|
local matched = {} ---@type GdbAtomRecord[]
|
||||||
|
|
||||||
for _, src in ipairs(corpus.source_order or {}) do ---@type integer, SourceFile
|
for _, src in ipairs(corpus.source_order or {}) do ---@type integer, SourceFile
|
||||||
local file_base = src.path:match("([^/\\\\]+)$") or src.path ---@type string
|
local file_base = src.path:match("([^/\\\\]+)$") or src.path ---@type string
|
||||||
@@ -263,7 +263,7 @@ local function build_atom_table(ctx)
|
|||||||
local function append(atom)
|
local function append(atom)
|
||||||
if not atom.paths then return end
|
if not atom.paths then return end
|
||||||
local name = atom.raw_name or atom.name ---@type string
|
local name = atom.raw_name or atom.name ---@type string
|
||||||
local info = addrs[name] ---@type NmAddr|nil
|
local info = addrs[name] ---@type NmAddr|nil
|
||||||
if not info then return end
|
if not info then return end
|
||||||
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
||||||
matched[#matched + 1] = {
|
matched[#matched + 1] = {
|
||||||
@@ -276,7 +276,7 @@ local function build_atom_table(ctx)
|
|||||||
entries = entries,
|
entries = entries,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
for _, atom in ipairs((src.scan or {}).atoms or {}) do append(atom) end ---@type integer, AtomEntry
|
for _, atom in ipairs((src.scan or {}).atoms or {}) do append(atom) end ---@type integer, AtomEntry
|
||||||
for _, atom in ipairs((src.scan or {}).raw_atoms or {}) do append(atom) end ---@type integer, AtomEntry
|
for _, atom in ipairs((src.scan or {}).raw_atoms or {}) do append(atom) end ---@type integer, AtomEntry
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -537,12 +537,12 @@ function M.render_atom_source_map(atom)
|
|||||||
assert(type(atom) == "table", "render_atom_source_map: atom must be a table")
|
assert(type(atom) == "table", "render_atom_source_map: atom must be a table")
|
||||||
assert(type(atom.paths) == "table", "render_atom_source_map: atom.paths must be a table")
|
assert(type(atom.paths) == "table", "render_atom_source_map: atom.paths must be a table")
|
||||||
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
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 ---@type integer, WordMapEntry
|
for _, entry in ipairs(entries) do ---@type integer, WordMapEntry
|
||||||
local word_line = string.format("WORD %d LINE %d TEXT %s", ---@type string
|
local word_line = string.format("WORD %d LINE %d TEXT %s", ---@type string
|
||||||
entry.pos, entry.line, entry.text)
|
entry.pos, entry.line, entry.text)
|
||||||
local keys = {} ---@type string[]
|
local keys = {} ---@type string[]
|
||||||
for pos = 1, 16 do ---@type integer
|
for pos = 1, 16 do ---@type integer
|
||||||
local k = entry.gpr_keys and entry.gpr_keys[pos] ---@type string|nil
|
local k = entry.gpr_keys and entry.gpr_keys[pos] ---@type string|nil
|
||||||
if type(k) == "string" and k:sub(1, 7) == "reguse:" then
|
if type(k) == "string" and k:sub(1, 7) == "reguse:" then
|
||||||
@@ -571,10 +571,10 @@ function M.render_atom_provenance(atom, wc, rel_path)
|
|||||||
assert(type(atom.paths) == "table", "render_atom_provenance: atom.paths must be a table")
|
assert(type(atom.paths) == "table", "render_atom_provenance: atom.paths must be a table")
|
||||||
assert(type(rel_path) == "string", "render_atom_provenance: rel_path must be a string")
|
assert(type(rel_path) == "string", "render_atom_provenance: rel_path must be a string")
|
||||||
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
local entries, total = canonical_word_entries(atom) ---@type WordMapEntry[], integer
|
||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
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 ---@type integer, WordMapEntry
|
for _, entry in ipairs(entries) do ---@type integer, WordMapEntry
|
||||||
local inv = entry.invocation ---@type InvocationRecord|nil
|
local inv = entry.invocation ---@type InvocationRecord|nil
|
||||||
local macro_count = inv and wc and wc["mac_" .. inv.component_name] ---@type integer|nil
|
local macro_count = inv and wc and wc["mac_" .. inv.component_name] ---@type integer|nil
|
||||||
if inv and macro_count ~= nil then
|
if inv and macro_count ~= nil then
|
||||||
lines[#lines + 1] = string.format('WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d'
|
lines[#lines + 1] = string.format('WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d'
|
||||||
|
|||||||
+21
-21
@@ -36,7 +36,7 @@
|
|||||||
--- @field POOL GprIdent[]
|
--- @field POOL GprIdent[]
|
||||||
|
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
--- ════════════════════════════════════════════════════════════════════════════
|
--- ════════════════════════════════════════════════════════════════════════════
|
||||||
--- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY
|
--- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY
|
||||||
@@ -83,7 +83,7 @@ local INT_CODE_TO_POOL_GPR = { ---@type table<integer, GprIdent> -- bag: MIPS G
|
|||||||
--- @param tbl table<string, string> -- bag: key set only; values unused
|
--- @param tbl table<string, string> -- bag: key set only; values unused
|
||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function stable_sort_keys(tbl)
|
local function stable_sort_keys(tbl)
|
||||||
local keys = {} ---@type string[]
|
local keys = {} ---@type string[]
|
||||||
for k in pairs(tbl) do keys[#keys + 1] = k end ---@type string
|
for k in pairs(tbl) do keys[#keys + 1] = k end ---@type string
|
||||||
table.sort(keys)
|
table.sort(keys)
|
||||||
return keys
|
return keys
|
||||||
@@ -99,10 +99,10 @@ local function allocate_phase(phase_label, decls)
|
|||||||
-- Deep-copy POOL into a fresh sequence table. The original `table.unpack and table.unpack(POOL) or { unpack(POOL) }`
|
-- Deep-copy POOL into a fresh sequence table. The original `table.unpack and table.unpack(POOL) or { unpack(POOL) }`
|
||||||
-- idiom wraps the unpacked values in a single inner table under LuaJIT 5.1 (`table.unpack` is nil; the `or` returns one value),
|
-- idiom wraps the unpacked values in a single inner table under LuaJIT 5.1 (`table.unpack` is nil; the `or` returns one value),
|
||||||
-- which corrupts the pool into `{ {R_T0, R_T1, ...} }` — making `table.remove(pool, 1)` return the inner table on iteration.
|
-- which corrupts the pool into `{ {R_T0, R_T1, ...} }` — making `table.remove(pool, 1)` return the inner table on iteration.
|
||||||
local pool = {} ---@type GprIdent[]
|
local pool = {} ---@type GprIdent[]
|
||||||
for i = 1, #POOL do pool[i] = POOL[i] end ---@type integer
|
for i = 1, #POOL do pool[i] = POOL[i] end ---@type integer
|
||||||
local result = {} ---@type GprAllocMap
|
local result = {} ---@type GprAllocMap
|
||||||
local errors = {} ---@type PassFinding[]
|
local errors = {} ---@type PassFinding[]
|
||||||
for _, sym in ipairs(stable_sort_keys(decls)) do ---@type integer, string
|
for _, sym in ipairs(stable_sort_keys(decls)) do ---@type integer, string
|
||||||
local next_gpr = table.remove(pool, 1) ---@type GprIdent|nil
|
local next_gpr = table.remove(pool, 1) ---@type GprIdent|nil
|
||||||
if not next_gpr then
|
if not next_gpr then
|
||||||
@@ -166,13 +166,13 @@ local function find_used_gprs(body_text, alias_to_gpr)
|
|||||||
-- (b) Alias references (R_<Alias>) resolved to physical GPRs via the registry.
|
-- (b) Alias references (R_<Alias>) resolved to physical GPRs via the registry.
|
||||||
-- Sorted by name so the regex is byte-stable across runs.
|
-- Sorted by name so the regex is byte-stable across runs.
|
||||||
if alias_to_gpr and next(alias_to_gpr) then
|
if alias_to_gpr and next(alias_to_gpr) then
|
||||||
local aliases = {} ---@type string[]
|
local aliases = {} ---@type string[]
|
||||||
for alias_name in pairs(alias_to_gpr) do ---@type string
|
for alias_name in pairs(alias_to_gpr) do ---@type string
|
||||||
aliases[#aliases + 1] = alias_name
|
aliases[#aliases + 1] = alias_name
|
||||||
end
|
end
|
||||||
table.sort(aliases)
|
table.sort(aliases)
|
||||||
local pattern = "(" .. table.concat(aliases, "|") .. ")" ---@type string
|
local pattern = "(" .. table.concat(aliases, "|") .. ")" ---@type string
|
||||||
for alias_name in body_text:gmatch(pattern) do ---@type string
|
for alias_name in body_text:gmatch(pattern) do ---@type string
|
||||||
local gpr = alias_to_gpr[alias_name] ---@type GprIdent|nil
|
local gpr = alias_to_gpr[alias_name] ---@type GprIdent|nil
|
||||||
if gpr and not found[gpr] then
|
if gpr and not found[gpr] then
|
||||||
found[gpr] = 1
|
found[gpr] = 1
|
||||||
@@ -206,7 +206,7 @@ local function emit_auto_reg_h(out_dir, dir, sources, mappings)
|
|||||||
lines[#lines + 1] = "// R_<Sym>_Code = <chosen GPR's _Code constant> for every marker in this directory."
|
lines[#lines + 1] = "// R_<Sym>_Code = <chosen GPR's _Code constant> for every marker in this directory."
|
||||||
lines[#lines + 1] = ""
|
lines[#lines + 1] = ""
|
||||||
for _, sym in ipairs(stable_sort_keys(mappings)) do ---@type integer, string
|
for _, sym in ipairs(stable_sort_keys(mappings)) do ---@type integer, string
|
||||||
local gpr = mappings[sym] ---@type GprIdent
|
local gpr = mappings[sym] ---@type GprIdent
|
||||||
local gpr_code = gpr .. "_Code" ---@type string
|
local gpr_code = gpr .. "_Code" ---@type string
|
||||||
lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code
|
lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code
|
||||||
end
|
end
|
||||||
@@ -242,10 +242,10 @@ function M.run(ctx)
|
|||||||
local user_pinned, alias_to_gpr = build_user_pins(corpus) ---@type table<GprIdent, boolean>, table<string, GprIdent>
|
local user_pinned, alias_to_gpr = build_user_pins(corpus) ---@type table<GprIdent, boolean>, table<string, GprIdent>
|
||||||
|
|
||||||
-- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations).
|
-- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations).
|
||||||
local phase_allocations = {} ---@type table<string, GprAllocMap> -- bag: phase_label -> alloc map
|
local phase_allocations = {} ---@type table<string, GprAllocMap> -- bag: phase_label -> alloc map
|
||||||
for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do ---@type string, table<string, string>
|
for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do ---@type string, table<string, string>
|
||||||
local mapping, errs = allocate_phase(phase_label, decls) ---@type GprAllocMap, PassFinding[]
|
local mapping, errs = allocate_phase(phase_label, decls) ---@type GprAllocMap, PassFinding[]
|
||||||
for sym, gpr in pairs(mapping) do ---@type string, GprIdent
|
for sym, gpr in pairs(mapping) do ---@type string, GprIdent
|
||||||
phase_allocations[phase_label] = phase_allocations[phase_label] or {}
|
phase_allocations[phase_label] = phase_allocations[phase_label] or {}
|
||||||
phase_allocations[phase_label][sym] = gpr
|
phase_allocations[phase_label][sym] = gpr
|
||||||
end
|
end
|
||||||
@@ -258,14 +258,14 @@ function M.run(ctx)
|
|||||||
-- Otherwise, allocate a private pool for the atom.
|
-- Otherwise, allocate a private pool for the atom.
|
||||||
-- The phase membership is in `corpus.atom_phases[phase_label].atoms` (an array of atom names declared via `atom_phase(<phase>)`
|
-- The phase membership is in `corpus.atom_phases[phase_label].atoms` (an array of atom names declared via `atom_phase(<phase>)`
|
||||||
-- in the atom's `atom_info` line). Build a reverse map `atom_name -> phase_label` so the lookup is O(1) per atom scope.
|
-- in the atom's `atom_info` line). Build a reverse map `atom_name -> phase_label` so the lookup is O(1) per atom scope.
|
||||||
local atom_name_to_phase = {} ---@type table<AtomName, string> -- bag: atom name -> phase label
|
local atom_name_to_phase = {} ---@type table<AtomName, string> -- bag: atom name -> phase label
|
||||||
for phase_label, entry in pairs(corpus.atom_phases or {}) do ---@type string, AtomPhaseGroup
|
for phase_label, entry in pairs(corpus.atom_phases or {}) do ---@type string, AtomPhaseGroup
|
||||||
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, AtomName
|
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, AtomName
|
||||||
atom_name_to_phase[atom_name] = phase_label
|
atom_name_to_phase[atom_name] = phase_label
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local atom_allocations = {} ---@type table<AtomName, GprAllocMap> -- bag: atom scope -> alloc map
|
local atom_allocations = {} ---@type table<AtomName, GprAllocMap> -- bag: atom scope -> alloc map
|
||||||
for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do ---@type AtomName, table<string, string>
|
for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do ---@type AtomName, table<string, string>
|
||||||
local phase_label = atom_name_to_phase[atom_scope] ---@type string|nil
|
local phase_label = atom_name_to_phase[atom_scope] ---@type string|nil
|
||||||
-- Build the atom's source pool: start with the full POOL, subtract:
|
-- Build the atom's source pool: start with the full POOL, subtract:
|
||||||
@@ -277,7 +277,7 @@ function M.run(ctx)
|
|||||||
-- the original `source_pool = phase_allocations[phase_label]` form used the phase
|
-- the original `source_pool = phase_allocations[phase_label]` form used the phase
|
||||||
-- allocation MAP as a pool, but that map has no array part, so `table.remove(source_pool, 1)`
|
-- allocation MAP as a pool, but that map has no array part, so `table.remove(source_pool, 1)`
|
||||||
-- returned nil and every atom-with-phase marker errored with `phase_register_pool_exhausted`.
|
-- returned nil and every atom-with-phase marker errored with `phase_register_pool_exhausted`.
|
||||||
local used = {} ---@type table<GprIdent, boolean> -- bag: committed or body-referenced GPR -> true
|
local used = {} ---@type table<GprIdent, boolean> -- bag: committed or body-referenced GPR -> true
|
||||||
for _, m in pairs(phase_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end ---@type integer, GprAllocMap
|
for _, m in pairs(phase_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end ---@type integer, GprAllocMap
|
||||||
for _, m in pairs(atom_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end ---@type integer, GprAllocMap
|
for _, m in pairs(atom_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end ---@type integer, GprAllocMap
|
||||||
-- (c) Body references — scan the atom body for hardcoded + alias-resolved GPRs.
|
-- (c) Body references — scan the atom body for hardcoded + alias-resolved GPRs.
|
||||||
@@ -285,16 +285,16 @@ function M.run(ctx)
|
|||||||
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] ---@type AtomEntry|nil
|
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] ---@type AtomEntry|nil
|
||||||
if atom and atom.body then
|
if atom and atom.body then
|
||||||
local body_used = find_used_gprs(atom.body, alias_to_gpr) ---@type table<GprIdent, integer>
|
local body_used = find_used_gprs(atom.body, alias_to_gpr) ---@type table<GprIdent, integer>
|
||||||
for gpr in pairs(body_used) do used[gpr] = true end ---@type GprIdent
|
for gpr in pairs(body_used) do used[gpr] = true end ---@type GprIdent
|
||||||
end
|
end
|
||||||
local source_pool = {} ---@type GprIdent[]
|
local source_pool = {} ---@type GprIdent[]
|
||||||
for _, gpr in ipairs(POOL) do ---@type integer, GprIdent
|
for _, gpr in ipairs(POOL) do ---@type integer, GprIdent
|
||||||
-- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers declared via atom_reg + _Code defs, preserved across atoms globally).
|
-- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers 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
|
||||||
end
|
end
|
||||||
local result = {} ---@type GprAllocMap
|
local result = {} ---@type GprAllocMap
|
||||||
for _, sym in ipairs(stable_sort_keys(decls)) do ---@type integer, string
|
for _, sym in ipairs(stable_sort_keys(decls)) do ---@type integer, string
|
||||||
local next_gpr = table.remove(source_pool, 1) ---@type GprIdent|nil
|
local next_gpr = table.remove(source_pool, 1) ---@type GprIdent|nil
|
||||||
if not next_gpr then
|
if not next_gpr then
|
||||||
@@ -322,7 +322,7 @@ function M.run(ctx)
|
|||||||
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] ---@type AtomEntry|nil
|
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] ---@type AtomEntry|nil
|
||||||
if atom and atom.body then
|
if atom and atom.body then
|
||||||
local used_in_body = find_used_gprs(atom.body, alias_to_gpr) ---@type table<GprIdent, integer>
|
local used_in_body = find_used_gprs(atom.body, alias_to_gpr) ---@type table<GprIdent, integer>
|
||||||
for sym, allocated_gpr in pairs(decls) do ---@type string, GprIdent
|
for sym, allocated_gpr in pairs(decls) do ---@type string, GprIdent
|
||||||
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
|
||||||
warnings[#warnings + 1] = {
|
warnings[#warnings + 1] = {
|
||||||
line = atom.line or 0,
|
line = atom.line or 0,
|
||||||
@@ -338,8 +338,8 @@ function M.run(ctx)
|
|||||||
-- 4. Emit per-directory gen/auto_reg.h.
|
-- 4. Emit per-directory gen/auto_reg.h.
|
||||||
-- For each source directory that has atom_auto_regs or phase_auto_regs entries, emit one header.
|
-- For each source directory that has atom_auto_regs or phase_auto_regs entries, emit one header.
|
||||||
local sources_by_dir = corpus.sources_by_dir or {} ---@type table<string, SourceFile[]>
|
local sources_by_dir = corpus.sources_by_dir or {} ---@type table<string, SourceFile[]>
|
||||||
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
||||||
local per_dir_mappings = {} ---@type GprAllocMap
|
local per_dir_mappings = {} ---@type GprAllocMap
|
||||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||||
-- Collect every (sym -> gpr) entry that originated from a source in this directory.
|
-- Collect every (sym -> gpr) entry that originated from a source in this directory.
|
||||||
-- `src.scan.atom_auto_regs` is keyed by ATOM SCOPE NAME; `pairs(t)` iterates KEYS so `scope_name` here is the scope ident (e.g. "cube_g4_face").
|
-- `src.scan.atom_auto_regs` is keyed by ATOM SCOPE NAME; `pairs(t)` iterates KEYS so `scope_name` here is the scope ident (e.g. "cube_g4_face").
|
||||||
@@ -356,7 +356,7 @@ function M.run(ctx)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local out_dir = dir .. "/gen" ---@type string
|
local out_dir = dir .. "/gen" ---@type string
|
||||||
local out_path = emit_auto_reg_h(out_dir, dir, sources, per_dir_mappings) ---@type string|nil
|
local out_path = emit_auto_reg_h(out_dir, dir, sources, per_dir_mappings) ---@type string|nil
|
||||||
if out_path then outputs[#outputs + 1] = { auto_reg_h = out_path } end
|
if out_path then outputs[#outputs + 1] = { auto_reg_h = out_path } end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Constants
|
-- Constants
|
||||||
@@ -31,20 +31,20 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type
|
|||||||
|
|
||||||
-- Atom component declaration identifiers.
|
-- Atom component declaration identifiers.
|
||||||
local ATOM_COMP_PROC = "MipsAtomComp_Proc_" ---@type string
|
local ATOM_COMP_PROC = "MipsAtomComp_Proc_" ---@type string
|
||||||
local MIPS_ATOM = "Slice_MipsCode" ---@type string -- prefix on the function declaration that wraps an AtomComp_Proc_
|
local MIPS_ATOM = "Slice_MipsCode" ---@type string -- prefix on the function declaration that wraps an AtomComp_Proc_
|
||||||
|
|
||||||
-- Component-name prefixes.
|
-- Component-name prefixes.
|
||||||
local AC_PREFIX = "ac_" ---@type string -- arg to MipsAtomComp_(ac_X); the X is the atom name
|
local AC_PREFIX = "ac_" ---@type string -- arg to MipsAtomComp_(ac_X); the X is the atom name
|
||||||
local AC_PREFIX_LEN = 3 ---@type integer
|
local AC_PREFIX_LEN = 3 ---@type integer
|
||||||
local MAC_PREFIX = "mac_" ---@type string -- prefix on generated macros; the rest is the atom name
|
local MAC_PREFIX = "mac_" ---@type string -- prefix on generated macros; the rest is the atom name
|
||||||
local MAC_PREFIX_LEN = 4 ---@type integer
|
local MAC_PREFIX_LEN = 4 ---@type integer
|
||||||
|
|
||||||
-- ASCII byte values used in tokenization.
|
-- ASCII byte values used in tokenization.
|
||||||
local BYTE_NEWLINE = 10 ---@type integer
|
local BYTE_NEWLINE = 10 ---@type integer
|
||||||
local BYTE_SLASH = 47 ---@type integer
|
local BYTE_SLASH = 47 ---@type integer
|
||||||
|
|
||||||
-- Output gen subdirectory + filename (per-directory aggregation; the directory name is the namespace).
|
-- Output gen subdirectory + filename (per-directory aggregation; the directory name is the namespace).
|
||||||
local GEN_SUBDIR = "gen" ---@type string
|
local GEN_SUBDIR = "gen" ---@type string
|
||||||
local MACS_FILENAME = "macs.h" ---@type string
|
local MACS_FILENAME = "macs.h" ---@type string
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -126,9 +126,9 @@ end
|
|||||||
--- @return string[]|nil
|
--- @return string[]|nil
|
||||||
local function extract_arg_names(args_str)
|
local function extract_arg_names(args_str)
|
||||||
if not args_str or args_str == "" then return nil end
|
if not args_str or args_str == "" then return nil end
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
local tokens = duffle.split_top_level_commas(args_str) ---@type string[]
|
local tokens = duffle.split_top_level_commas(args_str) ---@type string[]
|
||||||
for _, tok in ipairs(tokens) do ---@type integer, string
|
for _, tok in ipairs(tokens) do ---@type integer, string
|
||||||
local trimmed = duffle.trim(tok) ---@type string
|
local trimmed = duffle.trim(tok) ---@type string
|
||||||
if trimmed ~= "" then
|
if trimmed ~= "" then
|
||||||
-- Strip trailing block comment (/* ... */) from the token, if present.
|
-- Strip trailing block comment (/* ... */) from the token, if present.
|
||||||
@@ -151,7 +151,7 @@ local function extract_arg_names(args_str)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- Now scan back from close_pos for the `/*` opener (slashes are at close_pos-1 and close_pos-2).
|
-- Now scan back from close_pos for the `/*` opener (slashes are at close_pos-1 and close_pos-2).
|
||||||
local opener_pos = nil ---@type integer|nil
|
local opener_pos = nil ---@type integer|nil
|
||||||
local scan = close_pos - 3 ---@type integer
|
local scan = close_pos - 3 ---@type integer
|
||||||
while scan >= 1 do
|
while scan >= 1 do
|
||||||
if trimmed:sub(scan, scan + 1) == "/*" then
|
if trimmed:sub(scan, scan + 1) == "/*" then
|
||||||
@@ -240,7 +240,7 @@ end
|
|||||||
--- @param scan SourceScan
|
--- @param scan SourceScan
|
||||||
--- @return Component[]
|
--- @return Component[]
|
||||||
local function project_components(source, scan)
|
local function project_components(source, scan)
|
||||||
local out = {} ---@type Component[]
|
local out = {} ---@type Component[]
|
||||||
for _, a in ipairs(scan.atoms) do ---@type integer, AtomEntry
|
for _, a in ipairs(scan.atoms) do ---@type integer, AtomEntry
|
||||||
-- Only `MipsAtomComp_(ac_X)` (kind="comp_bare") and `MipsAtomComp_Proc_(ac_X, ...)` (kind="comp_proc")
|
-- Only `MipsAtomComp_(ac_X)` (kind="comp_bare") and `MipsAtomComp_Proc_(ac_X, ...)` (kind="comp_proc")
|
||||||
-- are COMPONENTS — they get inlined via `mac_<name>` aliases inside atom bodies.
|
-- are COMPONENTS — they get inlined via `mac_<name>` aliases inside atom bodies.
|
||||||
@@ -286,8 +286,8 @@ end
|
|||||||
--- @param s string
|
--- @param s string
|
||||||
--- @return string
|
--- @return string
|
||||||
local function convert_line_comments_to_block(s)
|
local function convert_line_comments_to_block(s)
|
||||||
local result = s ---@type string
|
local result = s ---@type string
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local len = #result ---@type integer
|
local len = #result ---@type integer
|
||||||
while pos <= len do
|
while pos <= len do
|
||||||
local is_double_slash = result:byte(pos) == BYTE_SLASH ---@type boolean
|
local is_double_slash = result:byte(pos) == BYTE_SLASH ---@type boolean
|
||||||
@@ -300,9 +300,9 @@ local function convert_line_comments_to_block(s)
|
|||||||
while eol <= len and result:byte(eol) ~= BYTE_NEWLINE do
|
while eol <= len and result:byte(eol) ~= BYTE_NEWLINE do
|
||||||
eol = eol + 1
|
eol = eol + 1
|
||||||
end
|
end
|
||||||
local before = result:sub(1, pos - 1) ---@type string
|
local before = result:sub(1, pos - 1) ---@type string
|
||||||
local comment = result:sub(pos + 2, eol - 1) ---@type string -- skip the `//`
|
local comment = result:sub(pos + 2, eol - 1) ---@type string -- skip the `//`
|
||||||
local after ---@type string
|
local after ---@type string
|
||||||
if eol <= len and result:byte(eol) == BYTE_NEWLINE then
|
if eol <= len and result:byte(eol) == BYTE_NEWLINE then
|
||||||
after = " */" .. result:sub(eol) -- keep the newline
|
after = " */" .. result:sub(eol) -- keep the newline
|
||||||
else
|
else
|
||||||
@@ -361,7 +361,7 @@ local function word_count_rec(name, comp_by_name, wc, cache)
|
|||||||
if cache[name] ~= nil then return cache[name] end
|
if cache[name] ~= nil then return cache[name] end
|
||||||
cache[name] = -1 -- mark in-progress (cycle detection)
|
cache[name] = -1 -- mark in-progress (cycle detection)
|
||||||
local cc = comp_by_name[name] ---@type Component|nil
|
local cc = comp_by_name[name] ---@type Component|nil
|
||||||
local n ---@type integer
|
local n ---@type integer
|
||||||
if cc then
|
if cc then
|
||||||
n = 0
|
n = 0
|
||||||
local tokens = cc.body_tokens ---@type BodyToken[]
|
local tokens = cc.body_tokens ---@type BodyToken[]
|
||||||
@@ -412,11 +412,11 @@ end
|
|||||||
--- @param wc WordCounts
|
--- @param wc WordCounts
|
||||||
--- @return table<string, integer> -- bag: bare component name -> word count
|
--- @return table<string, integer> -- bag: bare component name -> word count
|
||||||
local function count_all_components(components, wc)
|
local function count_all_components(components, wc)
|
||||||
local comp_by_name = {} ---@type table<string, Component>
|
local comp_by_name = {} ---@type table<string, Component>
|
||||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
||||||
local cache = {} ---@type table<string, integer> -- bag: memo; -1 in-progress sentinel
|
local cache = {} ---@type table<string, integer> -- bag: memo; -1 in-progress sentinel
|
||||||
local counts = {} ---@type table<string, integer> -- bag: bare name -> word count
|
local counts = {} ---@type table<string, integer> -- bag: bare name -> word count
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
counts[c.name] = word_count_rec(c.name, comp_by_name, wc, cache)
|
counts[c.name] = word_count_rec(c.name, comp_by_name, wc, cache)
|
||||||
end
|
end
|
||||||
return counts
|
return counts
|
||||||
@@ -446,10 +446,10 @@ 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] = { cycle_cost = -1, gp0_contrib = -1 }
|
cache[name] = { cycle_cost = -1, gp0_contrib = -1 }
|
||||||
local cc = comp_by_name[name] ---@type Component|nil
|
local cc = comp_by_name[name] ---@type Component|nil
|
||||||
local cycle_cost ---@type integer
|
local cycle_cost ---@type integer
|
||||||
local gp0_contrib ---@type integer
|
local gp0_contrib ---@type integer
|
||||||
if cc then
|
if cc then
|
||||||
local skip_cycle = (name == "yield") ---@type boolean
|
local skip_cycle = (name == "yield") ---@type boolean
|
||||||
local skip_gp0 = name:match("^insert_ot_tag") ~= nil ---@type boolean
|
local skip_gp0 = name:match("^insert_ot_tag") ~= nil ---@type boolean
|
||||||
cycle_cost = 0
|
cycle_cost = 0
|
||||||
gp0_contrib = 0
|
gp0_contrib = 0
|
||||||
@@ -460,7 +460,7 @@ local function component_meta_rec(name, comp_by_name, latency, cache)
|
|||||||
if trimmed ~= "" then
|
if trimmed ~= "" then
|
||||||
local ident = duffle.read_ident(trimmed, 1) ---@type string|nil
|
local ident = duffle.read_ident(trimmed, 1) ---@type string|nil
|
||||||
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
||||||
local nested = ident:sub(MAC_PREFIX_LEN + 1) ---@type string
|
local nested = ident:sub(MAC_PREFIX_LEN + 1) ---@type string
|
||||||
local nested_meta = component_meta_rec(nested, comp_by_name, latency, cache) ---@type ComponentMeta
|
local nested_meta = component_meta_rec(nested, comp_by_name, latency, cache) ---@type ComponentMeta
|
||||||
if not skip_cycle then
|
if not skip_cycle then
|
||||||
cycle_cost = cycle_cost + nested_meta.cycle_cost
|
cycle_cost = cycle_cost + nested_meta.cycle_cost
|
||||||
@@ -471,7 +471,7 @@ local function component_meta_rec(name, comp_by_name, latency, cache)
|
|||||||
else
|
else
|
||||||
if not skip_cycle then
|
if not skip_cycle then
|
||||||
local isa = duffle.instr(ident) ---@type InstructionRow|nil
|
local isa = duffle.instr(ident) ---@type InstructionRow|nil
|
||||||
local gte = duffle.gte(ident) ---@type GteCommandRow|nil
|
local gte = duffle.gte(ident) ---@type GteCommandRow|nil
|
||||||
cycle_cost = cycle_cost + ((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
|
end
|
||||||
if not skip_gp0 then
|
if not skip_gp0 then
|
||||||
@@ -506,11 +506,11 @@ end
|
|||||||
--- @param latency table<string, integer> -- bag: ident -> cycle cost
|
--- @param latency table<string, integer> -- bag: ident -> cycle cost
|
||||||
--- @return ComponentMetaMap
|
--- @return ComponentMetaMap
|
||||||
local function compute_components_metadata(components, latency)
|
local function compute_components_metadata(components, latency)
|
||||||
local comp_by_name = {} ---@type table<string, Component>
|
local comp_by_name = {} ---@type table<string, Component>
|
||||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
||||||
local cache = {} ---@type ComponentMetaMap
|
local cache = {} ---@type ComponentMetaMap
|
||||||
local out = {} ---@type ComponentMetaMap
|
local out = {} ---@type ComponentMetaMap
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
out[c.name] = component_meta_rec(c.name, comp_by_name, latency, cache)
|
out[c.name] = component_meta_rec(c.name, comp_by_name, latency, cache)
|
||||||
end
|
end
|
||||||
return out
|
return out
|
||||||
@@ -526,7 +526,7 @@ end
|
|||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function split_comment_lines(s)
|
local function split_comment_lines(s)
|
||||||
local out = {} ---@type string[]
|
local out = {} ---@type string[]
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local s_len = #s ---@type integer
|
local s_len = #s ---@type integer
|
||||||
while pos <= s_len do
|
while pos <= s_len do
|
||||||
local nl = s:find("\n", pos, true) ---@type integer|nil
|
local nl = s:find("\n", pos, true) ---@type integer|nil
|
||||||
@@ -690,9 +690,9 @@ local function build_component_lines(c, counts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local tokens = duffle.split_top_level_commas(c.body) ---@type string[]
|
local tokens = duffle.split_top_level_commas(c.body) ---@type string[]
|
||||||
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end ---@type integer
|
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end ---@type integer
|
||||||
local sig = signature_from_args(c.args) ---@type string
|
local sig = signature_from_args(c.args) ---@type string
|
||||||
-- Direct lookup against the per-source precomputed `counts` table (built once by count_all_components).
|
-- Direct lookup against the per-source precomputed `counts` table (built once by count_all_components).
|
||||||
local n = counts[c.name] ---@type integer
|
local n = counts[c.name] ---@type integer
|
||||||
|
|
||||||
@@ -718,7 +718,7 @@ end
|
|||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function header_boilerplate(dir, sources)
|
local function header_boilerplate(dir, sources)
|
||||||
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" } ---@type string[]
|
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" } ---@type string[]
|
||||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||||
source_lines[#source_lines + 1] = "// source: " .. duffle.to_absolute_path(src.path)
|
source_lines[#source_lines + 1] = "// source: " .. duffle.to_absolute_path(src.path)
|
||||||
end
|
end
|
||||||
local source_blob = table.concat(source_lines, "\n") ---@type string
|
local source_blob = table.concat(source_lines, "\n") ---@type string
|
||||||
@@ -749,7 +749,7 @@ end
|
|||||||
--- @return string -- Output directory
|
--- @return string -- Output directory
|
||||||
--- @return string -- Full output path
|
--- @return string -- Full output path
|
||||||
local function compute_macs_h_path(dir)
|
local function compute_macs_h_path(dir)
|
||||||
local out_dir = dir .. "/" .. GEN_SUBDIR ---@type string
|
local out_dir = dir .. "/" .. GEN_SUBDIR ---@type string
|
||||||
local out_path = out_dir .. "/" .. MACS_FILENAME ---@type string
|
local out_path = out_dir .. "/" .. MACS_FILENAME ---@type string
|
||||||
return out_dir, out_path
|
return out_dir, out_path
|
||||||
end
|
end
|
||||||
@@ -764,7 +764,7 @@ end
|
|||||||
--- @return string|nil -- Path to the written file (nil if no components)
|
--- @return string|nil -- Path to the written file (nil if no components)
|
||||||
local function emit_component_macros_h(ctx, dir, sources, components, counts)
|
local function emit_component_macros_h(ctx, dir, sources, components, counts)
|
||||||
if #components == 0 then return nil end
|
if #components == 0 then return nil end
|
||||||
local out_dir, out_path = compute_macs_h_path(dir) ---@type string, string
|
local out_dir, out_path = compute_macs_h_path(dir) ---@type string, string
|
||||||
local lines = header_boilerplate(dir, sources) ---@type string[]
|
local lines = header_boilerplate(dir, sources) ---@type string[]
|
||||||
|
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
@@ -791,7 +791,7 @@ end
|
|||||||
--- @param counts table<string, integer> -- bag: bare component name -> word count
|
--- @param counts table<string, integer> -- bag: bare component name -> word count
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function update_canonical_word_counts(corpus, components, counts)
|
local function update_canonical_word_counts(corpus, components, counts)
|
||||||
local wc = corpus.word_counts ---@type WordCounts
|
local wc = corpus.word_counts ---@type WordCounts
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
local key = "mac_" .. c.name ---@type string
|
local key = "mac_" .. c.name ---@type string
|
||||||
if wc[key] == nil then
|
if wc[key] == nil then
|
||||||
@@ -821,7 +821,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function update_canonical_components(corpus, src, components, metadata)
|
local function update_canonical_components(corpus, src, components, metadata)
|
||||||
local rel_path = src.path:gsub("\\", "/") ---@type string
|
local rel_path = src.path:gsub("\\", "/") ---@type string
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
-- Keyed by bare name (e.g. `yield`, `load_tri_indices`).
|
-- Keyed by bare name (e.g. `yield`, `load_tri_indices`).
|
||||||
-- The atoms_source_map pass looks up components by bare name from the corpus;
|
-- The atoms_source_map pass looks up components by bare name from the corpus;
|
||||||
-- `mac_` prefix lives at the call-site identifier and is stripped before lookup.
|
-- `mac_` prefix lives at the call-site identifier and is stripped before lookup.
|
||||||
@@ -841,7 +841,7 @@ local function update_canonical_components(corpus, src, components, metadata)
|
|||||||
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
||||||
local existing = corpus.components[c.name] ---@type ComponentDef
|
local existing = corpus.components[c.name] ---@type ComponentDef
|
||||||
if existing.path ~= rel_path or existing.line ~= c.line then
|
if existing.path ~= rel_path or existing.line ~= c.line then
|
||||||
local kind = c.kind or "comp_bare" ---@type string
|
local kind = c.kind or "comp_bare" ---@type string
|
||||||
local first_kind = existing.kind or "comp_bare" ---@type string
|
local first_kind = existing.kind or "comp_bare" ---@type string
|
||||||
corpus.collisions[#corpus.collisions + 1] = {
|
corpus.collisions[#corpus.collisions + 1] = {
|
||||||
kind = "component",
|
kind = "component",
|
||||||
@@ -866,7 +866,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function update_canonical_component_body_index(corpus, src, components, scan)
|
local function update_canonical_component_body_index(corpus, src, components, scan)
|
||||||
local line_of = scan and scan.line_of ---@type (fun(pos: integer): integer)|nil
|
local line_of = scan and scan.line_of ---@type (fun(pos: integer): integer)|nil
|
||||||
for _, c in ipairs(components) do ---@type integer, Component
|
for _, c in ipairs(components) do ---@type integer, Component
|
||||||
if corpus.component_body_index[c.name] == nil then
|
if corpus.component_body_index[c.name] == nil then
|
||||||
corpus.component_body_index[c.name] = {
|
corpus.component_body_index[c.name] = {
|
||||||
body_tokens = c.body_tokens,
|
body_tokens = c.body_tokens,
|
||||||
@@ -911,14 +911,14 @@ function M.run(ctx)
|
|||||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/macs.h`.
|
-- Per-directory aggregation: every source in the same directory contributes to one `gen/macs.h`.
|
||||||
-- The directory itself is the namespace. `corpus.sources_by_dir` preserves source-order within each bucket (matches `corpus.source_order`).
|
-- The directory itself is the namespace. `corpus.sources_by_dir` preserves source-order within each bucket (matches `corpus.source_order`).
|
||||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order) ---@type table<string, SourceFile[]>
|
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order) ---@type table<string, SourceFile[]>
|
||||||
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
||||||
-- Aggregate components from every source in this directory.
|
-- Aggregate components from every source in this directory.
|
||||||
-- `project_components` returns nil for sources with no `MipsAtomComp_` declarations; we skip those.
|
-- `project_components` returns nil for sources with no `MipsAtomComp_` declarations; we skip those.
|
||||||
local aggregated_components = {} ---@type Component[]
|
local aggregated_components = {} ---@type Component[]
|
||||||
local metadata_per_source = {} ---@type table<SourceFile, ComponentMetaMap>
|
local metadata_per_source = {} ---@type table<SourceFile, ComponentMetaMap>
|
||||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||||
local per_source = project_components(src.text, src.scan) or {} ---@type Component[]
|
local per_source = project_components(src.text, src.scan) or {} ---@type Component[]
|
||||||
for _, c in ipairs(per_source) do ---@type integer, Component
|
for _, c in ipairs(per_source) do ---@type integer, Component
|
||||||
aggregated_components[#aggregated_components + 1] = c
|
aggregated_components[#aggregated_components + 1] = c
|
||||||
end
|
end
|
||||||
if #per_source > 0 then
|
if #per_source > 0 then
|
||||||
@@ -928,7 +928,7 @@ function M.run(ctx)
|
|||||||
if #aggregated_components > 0 then
|
if #aggregated_components > 0 then
|
||||||
-- Compute word counts across the aggregated set. `corpus.word_counts` carries the
|
-- Compute word counts across the aggregated set. `corpus.word_counts` carries the
|
||||||
-- same-source + prior-directory entries so the recursive lookup sees both.
|
-- same-source + prior-directory entries so the recursive lookup sees both.
|
||||||
local counts = count_all_components(aggregated_components, corpus.word_counts) ---@type table<string, integer> -- bag: bare name -> word count
|
local counts = count_all_components(aggregated_components, corpus.word_counts) ---@type table<string, integer> -- bag: bare name -> word count
|
||||||
local macs_path = emit_component_macros_h(ctx, dir, sources, aggregated_components, counts) ---@type string|nil
|
local macs_path = emit_component_macros_h(ctx, dir, sources, aggregated_components, counts) ---@type string|nil
|
||||||
if macs_path then
|
if macs_path then
|
||||||
outputs[#outputs + 1] = { macs_h = macs_path }
|
outputs[#outputs + 1] = { macs_h = macs_path }
|
||||||
|
|||||||
+167
-167
@@ -34,7 +34,7 @@
|
|||||||
-- Load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
-- Load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||||
-- Sets package.path + package.cpath then returns duffle.
|
-- Sets package.path + package.cpath then returns duffle.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- ELF32 / DWARF / atoms-source-map utilities (post-link debug-info injection).
|
-- ELF32 / DWARF / atoms-source-map utilities (post-link debug-info injection).
|
||||||
-- Sister module to duffle.lua — contains the format-constant tables (ELF32 byte offsets, DWARF opcodes, etc.) and the I/O helpers
|
-- Sister module to duffle.lua — contains the format-constant tables (ELF32 byte offsets, DWARF opcodes, etc.) and the I/O helpers
|
||||||
@@ -55,27 +55,27 @@ local sleb128 = elf_dwarf.sleb128 ---@type fun(n: integer): string
|
|||||||
-- All values lifted from `elf_dwarf.DWARF_LINE_OPS` + `elf_dwarf.DWARF5_RNGLISTS`.
|
-- All values lifted from `elf_dwarf.DWARF_LINE_OPS` + `elf_dwarf.DWARF5_RNGLISTS`.
|
||||||
-- Local aliases preserve the code's readability
|
-- Local aliases preserve the code's readability
|
||||||
-- (e.g. `DW_LNS_copy` reads better than `elf_dwarf.DWARF_LINE_OPS.DW_LNS_copy` in an emitter body).
|
-- (e.g. `DW_LNS_copy` reads better than `elf_dwarf.DWARF_LINE_OPS.DW_LNS_copy` in an emitter body).
|
||||||
local DWARF_LINE_OPS = elf_dwarf.DWARF_LINE_OPS ---@type DwarfLineOps
|
local DWARF_LINE_OPS = elf_dwarf.DWARF_LINE_OPS ---@type DwarfLineOps
|
||||||
local DWARF5_RNGLISTS = elf_dwarf.DWARF5_RNGLISTS ---@type Dwarf5Rnglists
|
local DWARF5_RNGLISTS = elf_dwarf.DWARF5_RNGLISTS ---@type Dwarf5Rnglists
|
||||||
local MIPS_BYTES_PER_WORD = elf_dwarf.MIPS_BYTES_PER_WORD ---@type integer
|
local MIPS_BYTES_PER_WORD = elf_dwarf.MIPS_BYTES_PER_WORD ---@type integer
|
||||||
|
|
||||||
local DW_LNS_copy = DWARF_LINE_OPS.DW_LNS_copy ---@type integer
|
local DW_LNS_copy = DWARF_LINE_OPS.DW_LNS_copy ---@type integer
|
||||||
local DW_LNS_advance_pc = DWARF_LINE_OPS.DW_LNS_advance_pc ---@type integer
|
local DW_LNS_advance_pc = DWARF_LINE_OPS.DW_LNS_advance_pc ---@type integer
|
||||||
local DW_LNS_advance_line = DWARF_LINE_OPS.DW_LNS_advance_line ---@type integer
|
local DW_LNS_advance_line = DWARF_LINE_OPS.DW_LNS_advance_line ---@type integer
|
||||||
local DW_LNS_set_file = DWARF_LINE_OPS.DW_LNS_set_file ---@type integer
|
local DW_LNS_set_file = DWARF_LINE_OPS.DW_LNS_set_file ---@type integer
|
||||||
local DW_LNS_negate_stmt = DWARF_LINE_OPS.DW_LNS_negate_stmt ---@type integer
|
local DW_LNS_negate_stmt = DWARF_LINE_OPS.DW_LNS_negate_stmt ---@type integer
|
||||||
local DW_LNS_extended = DWARF_LINE_OPS.DW_LNS_extended ---@type integer
|
local DW_LNS_extended = DWARF_LINE_OPS.DW_LNS_extended ---@type integer
|
||||||
local DW_LNE_end_sequence = DWARF_LINE_OPS.DW_LNE_end_sequence ---@type integer
|
local DW_LNE_end_sequence = DWARF_LINE_OPS.DW_LNE_end_sequence ---@type integer
|
||||||
local DW_LNE_set_address = DWARF_LINE_OPS.DW_LNE_set_address ---@type integer
|
local DW_LNE_set_address = DWARF_LINE_OPS.DW_LNE_set_address ---@type integer
|
||||||
|
|
||||||
local DW_RLE_end_of_list = DWARF5_RNGLISTS.end_of_list ---@type integer
|
local DW_RLE_end_of_list = DWARF5_RNGLISTS.end_of_list ---@type integer
|
||||||
local DW_RLE_start_length = DWARF5_RNGLISTS.start_length ---@type integer
|
local DW_RLE_start_length = DWARF5_RNGLISTS.start_length ---@type integer
|
||||||
|
|
||||||
-- File-index lookup for the existing main line unit (Unit 2).
|
-- File-index lookup for the existing main line unit (Unit 2).
|
||||||
-- Populated at pass start by `init_file_index_lookup(elf_path)` from the runtime ELF (see `elf_dwarf.read_line_unit_file_table`).
|
-- Populated at pass start by `init_file_index_lookup(elf_path)` from the runtime ELF (see `elf_dwarf.read_line_unit_file_table`).
|
||||||
local _file_index_by_basename = nil ---@type table<string, integer>|nil -- bag -- [basename] = 1-based line-table file index
|
local _file_index_by_basename = nil ---@type table<string, integer>|nil -- bag -- [basename] = 1-based line-table file index
|
||||||
local _file_path_by_index = nil ---@type table<integer, string>|nil -- bag -- [1-based index] = full source path (diagnostics / future consumers)
|
local _file_path_by_index = nil ---@type table<integer, string>|nil -- bag -- [1-based index] = full source path (diagnostics / future consumers)
|
||||||
local _default_atom_source_index = nil ---@type integer -- any valid index used in opaque-row fallbacks
|
local _default_atom_source_index = nil ---@type integer -- any valid index used in opaque-row fallbacks
|
||||||
|
|
||||||
-- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31
|
-- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31
|
||||||
-- (see collect_per_source_registries + by_alias in build_inserted_children).
|
-- (see collect_per_source_registries + by_alias in build_inserted_children).
|
||||||
@@ -86,16 +86,16 @@ local _default_atom_source_index = nil ---@type integer -- any valid index used
|
|||||||
-- DW_OP_bregN would describe a memory location addressed from a register; the breg form would make gdb dereference the atom register value rather than display it.
|
-- DW_OP_bregN would describe a memory location addressed from a register; the breg form would make gdb dereference the atom register value rather than display it.
|
||||||
|
|
||||||
-- New abbreviation codes (100+ to avoid collision with gcc's existing 1-60+ codes).
|
-- New abbreviation codes (100+ to avoid collision with gcc's existing 1-60+ codes).
|
||||||
local ABBREV_CU = 0x64 ---@type integer -- 100: DW_TAG_compile_unit
|
local ABBREV_CU = 0x64 ---@type integer -- 100: DW_TAG_compile_unit
|
||||||
local ABBREV_SUBPROGRAM = 0x65 ---@type integer -- 101: DW_TAG_subprogram
|
local ABBREV_SUBPROGRAM = 0x65 ---@type integer -- 101: DW_TAG_subprogram
|
||||||
local ABBREV_VARIABLE = 0x66 ---@type integer -- 102: DW_TAG_variable with DW_AT_type = ref4 to U4
|
local ABBREV_VARIABLE = 0x66 ---@type integer -- 102: DW_TAG_variable with DW_AT_type = ref4 to U4
|
||||||
local ABBREV_STRUCT_TYPE = 0x67 ---@type integer -- 103: DW_TAG_structure_type with children (Binds_X mirror)
|
local ABBREV_STRUCT_TYPE = 0x67 ---@type integer -- 103: DW_TAG_structure_type with children (Binds_X mirror)
|
||||||
local ABBREV_MEMBER = 0x68 ---@type integer -- 104: DW_TAG_member no children (DW_AT_type = ref4 to U4 base)
|
local ABBREV_MEMBER = 0x68 ---@type integer -- 104: DW_TAG_member no children (DW_AT_type = ref4 to U4 base)
|
||||||
local ABBREV_BIND_VAR = 0x69 ---@type integer -- 105: DW_TAG_variable no children + DW_AT_type = ref4 (the bind_args variable)
|
local ABBREV_BIND_VAR = 0x69 ---@type integer -- 105: DW_TAG_variable no children + DW_AT_type = ref4 (the bind_args variable)
|
||||||
local ABBREV_BASE_TYPE = 0x6A ---@type integer -- 106: DW_TAG_base_type no children (U4)
|
local ABBREV_BASE_TYPE = 0x6A ---@type integer -- 106: DW_TAG_base_type no children (U4)
|
||||||
-- Component step-into (DW_TAG_inlined_subroutine + abstract DW_TAG_subprogram).
|
-- Component step-into (DW_TAG_inlined_subroutine + abstract DW_TAG_subprogram).
|
||||||
local ABBREV_ABSTRACT_SUBPROGRAM = 0x6B ---@type integer -- 107: DW_TAG_subprogram (abstract — no low_pc/high_pc); for each unique mac_X component
|
local ABBREV_ABSTRACT_SUBPROGRAM = 0x6B ---@type integer -- 107: DW_TAG_subprogram (abstract — no low_pc/high_pc); for each unique mac_X component
|
||||||
local ABBREV_INLINED_SUBROUTINE = 0x6C ---@type integer -- 108: DW_TAG_inlined_subroutine with children (per-component invocation range)
|
local ABBREV_INLINED_SUBROUTINE = 0x6C ---@type integer -- 108: DW_TAG_inlined_subroutine with children (per-component invocation range)
|
||||||
-- Bind_args uses DW_FORM_sec_offset → .debug_loclists for PC-ranged liveness
|
-- Bind_args uses DW_FORM_sec_offset → .debug_loclists for PC-ranged liveness
|
||||||
-- (each field transitions from tape memory to GPR at load_pc + 8 = MIPS I load-delay slot boundary).
|
-- (each field transitions from tape memory to GPR at load_pc + 8 = MIPS I load-delay slot boundary).
|
||||||
local ABBREV_BIND_VAR_LOCLIST = 0x6D ---@type integer -- 109: DW_TAG_variable no children + DW_AT_type = ref4 + DW_AT_location = sec_offset
|
local ABBREV_BIND_VAR_LOCLIST = 0x6D ---@type integer -- 109: DW_TAG_variable no children + DW_AT_type = ref4 + DW_AT_location = sec_offset
|
||||||
@@ -213,26 +213,26 @@ local DW_TAG_pointer_type = 0x0F ---@type integer
|
|||||||
-- Component step-into.
|
-- Component step-into.
|
||||||
local DW_TAG_inlined_subroutine = 0x1D ---@type integer
|
local DW_TAG_inlined_subroutine = 0x1D ---@type integer
|
||||||
|
|
||||||
local DW_AT_name = 0x03 ---@type integer
|
local DW_AT_name = 0x03 ---@type integer
|
||||||
local DW_AT_low_pc = 0x11 ---@type integer
|
local DW_AT_low_pc = 0x11 ---@type integer
|
||||||
local DW_AT_high_pc = 0x12 ---@type integer
|
local DW_AT_high_pc = 0x12 ---@type integer
|
||||||
local DW_AT_language = 0x13 ---@type integer
|
local DW_AT_language = 0x13 ---@type integer
|
||||||
local DW_AT_location = 0x02 ---@type integer
|
local DW_AT_location = 0x02 ---@type integer
|
||||||
local DW_AT_comp_dir = 0x1B ---@type integer
|
local DW_AT_comp_dir = 0x1B ---@type integer
|
||||||
local DW_AT_byte_size = 0x0B ---@type integer
|
local DW_AT_byte_size = 0x0B ---@type integer
|
||||||
local DW_AT_encoding = 0x3E ---@type integer -- DWARF5 §7.7.1: DW_AT_encoding for the DW_ATE_unsigned base type
|
local DW_AT_encoding = 0x3E ---@type integer -- DWARF5 §7.7.1: DW_AT_encoding for the DW_ATE_unsigned base type
|
||||||
local DW_AT_data_member_location = 0x38 ---@type integer
|
local DW_AT_data_member_location = 0x38 ---@type integer
|
||||||
local DW_AT_type = 0x49 ---@type integer
|
local DW_AT_type = 0x49 ---@type integer
|
||||||
local DW_AT_linkage_name = 0x6E ---@type integer -- DWARF5 §7.7.1: DW_AT_linkage_name with DW_FORM_string
|
local DW_AT_linkage_name = 0x6E ---@type integer -- DWARF5 §7.7.1: DW_AT_linkage_name with DW_FORM_string
|
||||||
local DW_AT_external = 0x3F ---@type integer -- marks a variable/function as externally visible
|
local DW_AT_external = 0x3F ---@type integer -- marks a variable/function as externally visible
|
||||||
-- Inlined_subroutine + abstract_origin attributes.
|
-- Inlined_subroutine + abstract_origin attributes.
|
||||||
local DW_AT_abstract_origin = 0x31 ---@type integer
|
local DW_AT_abstract_origin = 0x31 ---@type integer
|
||||||
local DW_AT_call_file = 0x58 ---@type integer
|
local DW_AT_call_file = 0x58 ---@type integer
|
||||||
local DW_AT_call_line = 0x59 ---@type integer
|
local DW_AT_call_line = 0x59 ---@type integer
|
||||||
local DW_AT_inline = 0x20 ---@type integer -- DWARF5 §7.7.1: DW_AT_inline (used by abstract subprogram for the mac_X() components)
|
local DW_AT_inline = 0x20 ---@type integer -- DWARF5 §7.7.1: DW_AT_inline (used by abstract subprogram for the mac_X() components)
|
||||||
-- decl_file + decl_line on the abstract subprogram so consumers can resolve an abstract origin back to its definition site even when no inlined_subroutine instance currently maps to it.
|
-- decl_file + decl_line on the abstract subprogram so consumers can resolve an abstract origin back to its definition site even when no inlined_subroutine instance currently maps to it.
|
||||||
local DW_AT_decl_file = 0x3A ---@type integer -- DWARF5 §7.7.1: DW_AT_decl_file (1-based file index into the CU's file table)
|
local DW_AT_decl_file = 0x3A ---@type integer -- DWARF5 §7.7.1: DW_AT_decl_file (1-based file index into the CU's file table)
|
||||||
local DW_AT_decl_line = 0x3B ---@type integer -- DWARF5 §7.7.1: DW_AT_decl_line
|
local DW_AT_decl_line = 0x3B ---@type integer -- DWARF5 §7.7.1: DW_AT_decl_line
|
||||||
|
|
||||||
-- Replaced the hardcoded `ATOM_SOURCE_FILE_INDEX = 11` and the `PROVENANCE_BASENAME_TO_FILE_INDEX` table below with a runtime lookup
|
-- Replaced the hardcoded `ATOM_SOURCE_FILE_INDEX = 11` and the `PROVENANCE_BASENAME_TO_FILE_INDEX` table below with a runtime lookup
|
||||||
-- (`init_file_index_lookup` + `resolve_provenance_file_index`) that reads the actual `.debug_line` file table from the post-link ELF.
|
-- (`init_file_index_lookup` + `resolve_provenance_file_index`) that reads the actual `.debug_line` file table from the post-link ELF.
|
||||||
@@ -284,7 +284,7 @@ local function resolve_provenance_file_index(path)
|
|||||||
local normalized = path:gsub("\\", "/") ---@type string
|
local normalized = path:gsub("\\", "/") ---@type string
|
||||||
-- Take the last path component (the basename).
|
-- Take the last path component (the basename).
|
||||||
local basename = normalized:match("([^/]+)$") or normalized ---@type string
|
local basename = normalized:match("([^/]+)$") or normalized ---@type string
|
||||||
local idx = _file_index_by_basename[basename] ---@type integer|nil
|
local idx = _file_index_by_basename[basename] ---@type integer|nil
|
||||||
if idx ~= nil then return idx end
|
if idx ~= nil then return idx end
|
||||||
-- Last-resort exact-path match (handles paths that don't reduce to a known basename).
|
-- Last-resort exact-path match (handles paths that don't reduce to a known basename).
|
||||||
for i, p in pairs(_file_path_by_index) do ---@type integer, string
|
for i, p in pairs(_file_path_by_index) do ---@type integer, string
|
||||||
@@ -299,13 +299,13 @@ end
|
|||||||
|
|
||||||
local DW_FORM_addr = 0x01 ---@type integer
|
local DW_FORM_addr = 0x01 ---@type integer
|
||||||
local DW_FORM_data1 = 0x0B ---@type integer
|
local DW_FORM_data1 = 0x0B ---@type integer
|
||||||
local DW_FORM_string = 0x08 ---@type integer -- inline null-terminated
|
local DW_FORM_string = 0x08 ---@type integer -- inline null-terminated
|
||||||
local DW_FORM_strp = 0x0E ---@type integer -- 4-byte offset into .debug_str
|
local DW_FORM_strp = 0x0E ---@type integer -- 4-byte offset into .debug_str
|
||||||
local DW_FORM_exprloc = 0x18 ---@type integer -- length-prefixed (ULEB128) DW_OP bytes
|
local DW_FORM_exprloc = 0x18 ---@type integer -- length-prefixed (ULEB128) DW_OP bytes
|
||||||
local DW_FORM_ref4 = 0x13 ---@type integer -- 4-byte offset within the same .debug_info CU
|
local DW_FORM_ref4 = 0x13 ---@type integer -- 4-byte offset within the same .debug_info CU
|
||||||
local DW_FORM_udata = 0x0F ---@type integer -- ULEB128 (DW_AT_byte_size for struct_type, DW_AT_data_member_location for member)
|
local DW_FORM_udata = 0x0F ---@type integer -- ULEB128 (DW_AT_byte_size for struct_type, DW_AT_data_member_location for member)
|
||||||
local DW_FORM_implicit_const = 0x21 ---@type integer -- DWARF5 §7.5.6: abbrev declaration carries a SLEB constant (used by the abbrev-table walker)
|
local DW_FORM_implicit_const = 0x21 ---@type integer -- DWARF5 §7.5.6: abbrev declaration carries a SLEB constant (used by the abbrev-table walker)
|
||||||
local DW_FORM_sec_offset = 0x17 ---@type integer -- 4-byte section-relative offset (into .debug_loclists / .debug_rnglists)
|
local DW_FORM_sec_offset = 0x17 ---@type integer -- 4-byte section-relative offset (into .debug_loclists / .debug_rnglists)
|
||||||
|
|
||||||
-- DW_OP_reg0 + DW_OP_piece are declared above (lines 114-116) alongside the other DWARF5 §7.7.3 loclist opcodes.
|
-- DW_OP_reg0 + DW_OP_piece are declared above (lines 114-116) alongside the other DWARF5 §7.7.3 loclist opcodes.
|
||||||
|
|
||||||
@@ -337,26 +337,26 @@ local function build_debug_loclists_section(atom_table, registries)
|
|||||||
-- When absent we emit just the section terminator (a single DW_LLE_end_of_list byte); the .debug_loclists section stays non-empty so the linker accepts it,
|
-- When absent we emit just the section terminator (a single DW_LLE_end_of_list byte); the .debug_loclists section stays non-empty so the linker accepts it,
|
||||||
-- and `bind_args` will be emitted with no loclist PC range (readelf will display it as having no .debug_loclists entries).
|
-- and `bind_args` will be emitted with no loclist PC range (readelf will display it as having no .debug_loclists entries).
|
||||||
local tape_alias_entry = registries.register_alias_registry and registries.register_alias_registry["R_TapePtr"] ---@type AliasEntry|nil
|
local tape_alias_entry = registries.register_alias_registry and registries.register_alias_registry["R_TapePtr"] ---@type AliasEntry|nil
|
||||||
local tape_reg = tape_alias_entry and tape_alias_entry.code ---@type integer
|
local tape_reg = tape_alias_entry and tape_alias_entry.code ---@type integer
|
||||||
local parts = {} ---@type string[]
|
local parts = {} ---@type string[]
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
if atom.rbind and tape_reg then
|
if atom.rbind and tape_reg then
|
||||||
local fields = atom.rbind.fields or {} ---@type TypeField[]
|
local fields = atom.rbind.fields or {} ---@type TypeField[]
|
||||||
local regs = atom.rbind.regs or {} ---@type DwarfLoadPair[]
|
local regs = atom.rbind.regs or {} ---@type DwarfLoadPair[]
|
||||||
local n_fields = #fields ---@type integer
|
local n_fields = #fields ---@type integer
|
||||||
local last_load_pc = atom.addr + (n_fields - 1) * MIPS_BYTES_PER_WORD ---@type integer
|
local last_load_pc = atom.addr + (n_fields - 1) * MIPS_BYTES_PER_WORD ---@type integer
|
||||||
local transition_pc = last_load_pc + MIPS_LOAD_DELAY_BYTES ---@type integer
|
local transition_pc = last_load_pc + MIPS_LOAD_DELAY_BYTES ---@type integer
|
||||||
local tape_pieces = {} ---@type string[]
|
local tape_pieces = {} ---@type string[]
|
||||||
for _, f in ipairs(fields) do ---@type integer, TypeField
|
for _, f in ipairs(fields) do ---@type integer, TypeField
|
||||||
local offset = f.offset or 0 ---@type integer
|
local offset = f.offset or 0 ---@type integer
|
||||||
local offset_sleb = elf_dwarf.sleb128(offset) ---@type string
|
local offset_sleb = elf_dwarf.sleb128(offset) ---@type string
|
||||||
-- (DW_OP_bregN, SLEB128(offset), DW_OP_piece, ULEB128(U4_BYTE_SIZE))
|
-- (DW_OP_bregN, SLEB128(offset), DW_OP_piece, ULEB128(U4_BYTE_SIZE))
|
||||||
-- 4 = U4_BYTE_SIZE: each piece is sizeof(uint32_t) on MIPS32.
|
-- 4 = U4_BYTE_SIZE: each piece is sizeof(uint32_t) on MIPS32.
|
||||||
table.insert(tape_pieces, string.char(DW_OP_breg0 + tape_reg) .. offset_sleb .. string.char(DW_OP_piece) .. uleb128(U4_BYTE_SIZE))
|
table.insert(tape_pieces, string.char(DW_OP_breg0 + tape_reg) .. offset_sleb .. string.char(DW_OP_piece) .. uleb128(U4_BYTE_SIZE))
|
||||||
end
|
end
|
||||||
local tape_expr = table.concat(tape_pieces) ---@type string
|
local tape_expr = table.concat(tape_pieces) ---@type string
|
||||||
local gpr_pieces = {} ---@type string[]
|
local gpr_pieces = {} ---@type string[]
|
||||||
for _, pair in ipairs(regs) do ---@type integer, DwarfLoadPair
|
for _, pair in ipairs(regs) do ---@type integer, DwarfLoadPair
|
||||||
-- (DW_OP_regN, DW_OP_piece, ULEB128(4)) — one piece per GPR-resident field.
|
-- (DW_OP_regN, DW_OP_piece, ULEB128(4)) — one piece per GPR-resident field.
|
||||||
-- The 4 = U4_BYTE_SIZE: each piece is sizeof(uint32_t) on MIPS32.
|
-- The 4 = U4_BYTE_SIZE: each piece is sizeof(uint32_t) on MIPS32.
|
||||||
table.insert(gpr_pieces, string.char(DW_OP_reg0 + pair.reg) .. string.char(DW_OP_piece) .. uleb128(U4_BYTE_SIZE))
|
table.insert(gpr_pieces, string.char(DW_OP_reg0 + pair.reg) .. string.char(DW_OP_piece) .. uleb128(U4_BYTE_SIZE))
|
||||||
@@ -376,9 +376,9 @@ local function build_debug_loclists_section(atom_table, registries)
|
|||||||
-- Loclist unit header (DWARF5 §7.7.2):
|
-- Loclist unit header (DWARF5 §7.7.2):
|
||||||
-- unit_length(4) + version(2) + address_size(1) + segment_size(1) + offset_entry_count(4) = 12 bytes header.
|
-- unit_length(4) + version(2) + address_size(1) + segment_size(1) + offset_entry_count(4) = 12 bytes header.
|
||||||
-- version = 5 (DWARF5); address_size = 4 (MIPS32); segment_size = 0; offset_entry_count = 0 (we use DW_LLE_start_length, not offsets).
|
-- version = 5 (DWARF5); address_size = 4 (MIPS32); segment_size = 0; offset_entry_count = 0 (we use DW_LLE_start_length, not offsets).
|
||||||
local LOCLIST_HEADER_SIZE = 12 ---@type integer
|
local LOCLIST_HEADER_SIZE = 12 ---@type integer
|
||||||
local body = table.concat(parts) ---@type string
|
local body = table.concat(parts) ---@type string
|
||||||
local unit_length = LOCLIST_HEADER_SIZE - 4 + #body ---@type integer -- -4 because unit_length excludes itself
|
local unit_length = LOCLIST_HEADER_SIZE - 4 + #body ---@type integer -- -4 because unit_length excludes itself
|
||||||
local header = elf_dwarf.write_u32_le(unit_length) ---@type string
|
local header = elf_dwarf.write_u32_le(unit_length) ---@type string
|
||||||
.. elf_dwarf.write_u16_le(5) -- DWARF5
|
.. elf_dwarf.write_u16_le(5) -- DWARF5
|
||||||
.. string.char(U4_BYTE_SIZE) -- address_size
|
.. string.char(U4_BYTE_SIZE) -- address_size
|
||||||
@@ -401,11 +401,11 @@ end
|
|||||||
-- @param atom_table DwarfAtom[] -- list of atoms with .rbind set
|
-- @param atom_table DwarfAtom[] -- list of atoms with .rbind set
|
||||||
-- @return table<string, integer> -- bag: atom name -> offset_in_section
|
-- @return table<string, integer> -- bag: atom name -> offset_in_section
|
||||||
local function compute_loclists_offsets(atom_table)
|
local function compute_loclists_offsets(atom_table)
|
||||||
local LOCLIST_ENTRY_HEADER_SIZE = 1 + 4 + 1 ---@type integer -- DW_LLE_start_length(1) + addr(4) + uleb_length(1)
|
local LOCLIST_ENTRY_HEADER_SIZE = 1 + 4 + 1 ---@type integer -- DW_LLE_start_length(1) + addr(4) + uleb_length(1)
|
||||||
local offsets = {} ---@type table<string, integer> -- bag
|
local offsets = {} ---@type table<string, integer> -- bag
|
||||||
-- Loclist unit header (DWARF5 §7.7.2): unit_length(4) + version(2) + address_size(1) + segment_size(1) + offset_entry_count(4) = 12 bytes.
|
-- Loclist unit header (DWARF5 §7.7.2): unit_length(4) + version(2) + address_size(1) + segment_size(1) + offset_entry_count(4) = 12 bytes.
|
||||||
-- The unit_length itself is not counted in the unit_length value, so the body starts at byte 12.
|
-- The unit_length itself is not counted in the unit_length value, so the body starts at byte 12.
|
||||||
local cursor = 4 + 2 + 1 + 1 + 4 ---@type integer -- = 12
|
local cursor = 4 + 2 + 1 + 1 + 4 ---@type integer -- = 12
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
if atom.rbind then
|
if atom.rbind then
|
||||||
offsets[atom.name] = cursor
|
offsets[atom.name] = cursor
|
||||||
@@ -414,14 +414,14 @@ local function compute_loclists_offsets(atom_table)
|
|||||||
-- 1 (DW_LLE_start_length) + 4 (PC) + 1 (uleb length prefix) + sum(tape_piece_size(field.offset))
|
-- 1 (DW_LLE_start_length) + 4 (PC) + 1 (uleb length prefix) + sum(tape_piece_size(field.offset))
|
||||||
-- + 1 (DW_LLE_start_length) + 4 (transition_pc) + 1 (uleb length prefix) + n_fields * 3 (gpr pieces)
|
-- + 1 (DW_LLE_start_length) + 4 (transition_pc) + 1 (uleb length prefix) + n_fields * 3 (gpr pieces)
|
||||||
-- + 1 (DW_LLE_end_of_list)
|
-- + 1 (DW_LLE_end_of_list)
|
||||||
local tape_pieces_size = 0 ---@type integer
|
local tape_pieces_size = 0 ---@type integer
|
||||||
for _, f in ipairs(atom.rbind.fields or {}) do ---@type integer, TypeField
|
for _, f in ipairs(atom.rbind.fields or {}) do ---@type integer, TypeField
|
||||||
tape_pieces_size = tape_pieces_size + tape_piece_size(f.offset or 0)
|
tape_pieces_size = tape_pieces_size + tape_piece_size(f.offset or 0)
|
||||||
end
|
end
|
||||||
local gpr_pieces_size = n_fields * 3 ---@type integer -- each gpr piece: DW_OP_regN(1) + DW_OP_piece(1) + uleb(4)(1) = 3 bytes
|
local gpr_pieces_size = n_fields * 3 ---@type integer -- each gpr piece: DW_OP_regN(1) + DW_OP_piece(1) + uleb(4)(1) = 3 bytes
|
||||||
local tape_entry = LOCLIST_ENTRY_HEADER_SIZE + tape_pieces_size ---@type integer
|
local tape_entry = LOCLIST_ENTRY_HEADER_SIZE + tape_pieces_size ---@type integer
|
||||||
local gpr_entry = LOCLIST_ENTRY_HEADER_SIZE + gpr_pieces_size ---@type integer
|
local gpr_entry = LOCLIST_ENTRY_HEADER_SIZE + gpr_pieces_size ---@type integer
|
||||||
local body_len = tape_entry + gpr_entry + 1 ---@type integer -- +1 for DW_LLE_end_of_list
|
local body_len = tape_entry + gpr_entry + 1 ---@type integer -- +1 for DW_LLE_end_of_list
|
||||||
cursor = cursor + body_len
|
cursor = cursor + body_len
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -430,7 +430,7 @@ end
|
|||||||
|
|
||||||
-- Default name for the synthetic CU (so VSCode lists it as a known source).
|
-- Default name for the synthetic CU (so VSCode lists it as a known source).
|
||||||
local DEFAULT_CU_NAME = "tape_atom_locals" ---@type string
|
local DEFAULT_CU_NAME = "tape_atom_locals" ---@type string
|
||||||
local DEFAULT_CU_COMP_DIR = "." ---@type string
|
local DEFAULT_CU_COMP_DIR = "." ---@type string
|
||||||
|
|
||||||
-- SECTION_WRITERS owns the .bin output path templates.
|
-- SECTION_WRITERS owns the .bin output path templates.
|
||||||
|
|
||||||
@@ -558,11 +558,11 @@ local DEFAULT_BASENAME = "hello_gte" ---@type string
|
|||||||
--- @field data string
|
--- @field data string
|
||||||
|
|
||||||
--- @class DwarfInjectionPass
|
--- @class DwarfInjectionPass
|
||||||
--- @field run fun(ctx: PassCtx): PassResult
|
--- @field run fun(ctx: PassCtx): PassResult
|
||||||
--- @field compute_loclists_offsets_for_test fun(atom_table: DwarfAtom[]): table<string, integer>
|
--- @field compute_loclists_offsets_for_test fun(atom_table: DwarfAtom[]): table<string, integer>
|
||||||
--- @field build_debug_loclists_section_for_test fun(atom_table: DwarfAtom[], registries: DwarfRegistries): string
|
--- @field build_debug_loclists_section_for_test fun(atom_table: DwarfAtom[], registries: DwarfRegistries): string
|
||||||
--- @field tape_piece_size_for_test fun(offset: integer): integer
|
--- @field tape_piece_size_for_test fun(offset: integer): integer
|
||||||
--- @field build_atom_table_for_test fun(corpus: Corpus, addrs: table<string, NmAddr>): DwarfAtom[]
|
--- @field build_atom_table_for_test fun(corpus: Corpus, addrs: table<string, NmAddr>): DwarfAtom[]
|
||||||
|
|
||||||
|
|
||||||
--- Project the corpus registries into the shape the section builders expect.
|
--- Project the corpus registries into the shape the section builders expect.
|
||||||
@@ -585,7 +585,7 @@ local function collect_per_source_registries(corpus)
|
|||||||
-- `passes.scan_source.lua` has already folded every per-source scan into the corpus tables, so no per-source iteration is needed here.
|
-- `passes.scan_source.lua` has already folded every per-source scan into the corpus tables, so no per-source iteration is needed here.
|
||||||
-- `atom_infos` is preserved byte-for-byte with no filtering; consumers consult `corpus.atoms_by_name`
|
-- `atom_infos` is preserved byte-for-byte with no filtering; consumers consult `corpus.atoms_by_name`
|
||||||
-- themselves when they need to know whether a particular atom_info corresponds to an actual atom record.
|
-- themselves when they need to know whether a particular atom_info corresponds to an actual atom record.
|
||||||
local atom_infos_list = {} ---@type AtomInfoEntry[]
|
local atom_infos_list = {} ---@type AtomInfoEntry[]
|
||||||
for _, ai in ipairs((corpus and corpus.atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
for _, ai in ipairs((corpus and corpus.atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
||||||
atom_infos_list[#atom_infos_list + 1] = ai
|
atom_infos_list[#atom_infos_list + 1] = ai
|
||||||
end
|
end
|
||||||
@@ -660,7 +660,7 @@ local function build_atom_sequence(atom)
|
|||||||
local function set_address(addr)
|
local function set_address(addr)
|
||||||
-- Per DWARF5 §6.2.5.3: marker(0) + size(ULEB128, includes sub_opcode byte) + sub_opcode + payload
|
-- Per DWARF5 §6.2.5.3: marker(0) + size(ULEB128, includes sub_opcode byte) + sub_opcode + payload
|
||||||
-- For set_address: size = 1 (sub_opcode) + 4 (addr) = 5
|
-- For set_address: size = 1 (sub_opcode) + 4 (addr) = 5
|
||||||
local addr_bytes = elf_dwarf.write_u32_le(addr) ---@type string
|
local addr_bytes = elf_dwarf.write_u32_le(addr) ---@type string
|
||||||
local sub_size = string.char(DW_LNE_set_address) .. addr_bytes ---@type string
|
local sub_size = string.char(DW_LNE_set_address) .. addr_bytes ---@type string
|
||||||
return string.char(DW_LNS_extended) .. uleb128(#sub_size) .. sub_size
|
return string.char(DW_LNS_extended) .. uleb128(#sub_size) .. sub_size
|
||||||
end
|
end
|
||||||
@@ -714,12 +714,12 @@ local function build_atom_sequence(atom)
|
|||||||
-- `start_pos` / `end_pos` are 0-based emitted-word positions stamped at construction/close time by `duffle.emit_invoke_begin` / `duffle.emit_invoke_end`;
|
-- `start_pos` / `end_pos` are 0-based emitted-word positions stamped at construction/close time by `duffle.emit_invoke_begin` / `duffle.emit_invoke_end`;
|
||||||
-- Missing values are a corpus-plumbing bug, so we let the index expression fail loud with arithmetic-on-nil rather than silently producing `0+1=1` for a missing start_pos.
|
-- Missing values are a corpus-plumbing bug, so we let the index expression fail loud with arithmetic-on-nil rather than silently producing `0+1=1` for a missing start_pos.
|
||||||
local invs = atom.invocations or {} ---@type InvocationRecord[]
|
local invs = atom.invocations or {} ---@type InvocationRecord[]
|
||||||
local innermost_idx = {} ---@type table<integer, InvocationRecord|nil> -- bag
|
local innermost_idx = {} ---@type table<integer, InvocationRecord|nil> -- bag
|
||||||
local ancestry_idx = {} ---@type table<integer, InvocationRecord[]> -- bag
|
local ancestry_idx = {} ---@type table<integer, InvocationRecord[]> -- bag
|
||||||
for idx = 1, #atom.entries do ---@type integer
|
for idx = 1, #atom.entries do ---@type integer
|
||||||
innermost_idx[idx] = nil
|
innermost_idx[idx] = nil
|
||||||
ancestry_idx[idx] = {}
|
ancestry_idx[idx] = {}
|
||||||
local active = {} ---@type InvocationRecord[]
|
local active = {} ---@type InvocationRecord[]
|
||||||
for _, inv in ipairs(invs) do ---@type integer, InvocationRecord
|
for _, inv in ipairs(invs) do ---@type integer, InvocationRecord
|
||||||
if idx >= inv.start_pos + 1 and idx <= inv.end_pos + 1 then
|
if idx >= inv.start_pos + 1 and idx <= inv.end_pos + 1 then
|
||||||
active[#active + 1] = inv
|
active[#active + 1] = inv
|
||||||
@@ -746,11 +746,11 @@ local function build_atom_sequence(atom)
|
|||||||
-- This is the value the multi-row PC's body_lines[1] row must reference for source-order display: `anc.body_lines[1]` is the line of the FIRST WORD
|
-- This is the value the multi-row PC's body_lines[1] row must reference for source-order display: `anc.body_lines[1]` is the line of the FIRST WORD
|
||||||
-- (which for an outer whose body starts with a nested expansion is inside the inner's expansion = wrong for display purposes);
|
-- (which for an outer whose body starts with a nested expansion is inside the inner's expansion = wrong for display purposes);
|
||||||
-- `anc.body_first_line` is the body's first content line in the parent's source (= correct for display).
|
-- `anc.body_first_line` is the body's first content line in the parent's source (= correct for display).
|
||||||
local body_first_line_of = {} ---@type table<integer, integer> -- bag
|
local body_first_line_of = {} ---@type table<integer, integer> -- bag
|
||||||
for _, top_inv in ipairs(invs) do ---@type integer, InvocationRecord
|
for _, top_inv in ipairs(invs) do ---@type integer, InvocationRecord
|
||||||
local earliest_nested_call_line = nil ---@type integer
|
local earliest_nested_call_line = nil ---@type integer
|
||||||
local earliest_nested_start_pos = nil ---@type integer
|
local earliest_nested_start_pos = nil ---@type integer
|
||||||
for _, cand in ipairs(invs) do ---@type integer, InvocationRecord
|
for _, cand in ipairs(invs) do ---@type integer, InvocationRecord
|
||||||
if cand.parent_id == top_inv.id and cand.call_line ~= nil then
|
if cand.parent_id == top_inv.id and cand.call_line ~= nil then
|
||||||
if earliest_nested_start_pos == nil or cand.start_pos < earliest_nested_start_pos then
|
if earliest_nested_start_pos == nil or cand.start_pos < earliest_nested_start_pos then
|
||||||
earliest_nested_start_pos = cand.start_pos
|
earliest_nested_start_pos = cand.start_pos
|
||||||
@@ -808,7 +808,7 @@ local function build_atom_sequence(atom)
|
|||||||
local call_file_idx = resolve_provenance_file_index(atom.src_path) ---@type integer
|
local call_file_idx = resolve_provenance_file_index(atom.src_path) ---@type integer
|
||||||
|
|
||||||
-- --- Atom entry (idx 1) -------------------------------------------------
|
-- --- Atom entry (idx 1) -------------------------------------------------
|
||||||
local entry_1 = atom.entries[1] ---@type DwarfAtomWord
|
local entry_1 = atom.entries[1] ---@type DwarfAtomWord
|
||||||
local entry_1_ancestry = ancestry_idx[1] ---@type InvocationRecord[]
|
local entry_1_ancestry = ancestry_idx[1] ---@type InvocationRecord[]
|
||||||
|
|
||||||
-- If atom entry 1 starts inside an invocation, walk the ancestry and emit a call-site row + (when applicable)
|
-- If atom entry 1 starts inside an invocation, walk the ancestry and emit a call-site row + (when applicable)
|
||||||
@@ -842,7 +842,7 @@ local function build_atom_sequence(atom)
|
|||||||
|
|
||||||
-- --- Subsequent entries (idx 2..N) --------------------------------------
|
-- --- Subsequent entries (idx 2..N) --------------------------------------
|
||||||
for idx = 2, #atom.entries do ---@type integer|nil
|
for idx = 2, #atom.entries do ---@type integer|nil
|
||||||
local entry = atom.entries[idx] ---@type DwarfAtomWord
|
local entry = atom.entries[idx] ---@type DwarfAtomWord
|
||||||
local inv = innermost_idx[idx] ---@type InvocationRecord
|
local inv = innermost_idx[idx] ---@type InvocationRecord
|
||||||
|
|
||||||
-- Advance PC by 1 .word (4 bytes on MIPS).
|
-- Advance PC by 1 .word (4 bytes on MIPS).
|
||||||
@@ -946,7 +946,7 @@ local function build_atom_table(corpus, addrs)
|
|||||||
-- Build the dense entries list from `word_events`.
|
-- Build the dense entries list from `word_events`.
|
||||||
-- `word_events[i].i` = the 0-based `.word` position
|
-- `word_events[i].i` = the 0-based `.word` position
|
||||||
-- `call_line` = the root atom's physical source line for that word (stamped by emission_model)
|
-- `call_line` = the root atom's physical source line for that word (stamped by emission_model)
|
||||||
local entries = {} ---@type DwarfAtomWord[]
|
local entries = {} ---@type DwarfAtomWord[]
|
||||||
for idx, ev in ipairs(word_events) do ---@type integer, WordEvent
|
for idx, ev in ipairs(word_events) do ---@type integer, WordEvent
|
||||||
entries[#entries + 1] = {
|
entries[#entries + 1] = {
|
||||||
pos = ev.i or (idx - 1),
|
pos = ev.i or (idx - 1),
|
||||||
@@ -990,7 +990,7 @@ local function build_atom_table(corpus, addrs)
|
|||||||
-- Cross-ref with the nm symbol table; atoms absent from `addrs` are skipped
|
-- Cross-ref with the nm symbol table; atoms absent from `addrs` are skipped
|
||||||
-- (an atom declared in source but not emitted as a symbol is a metaprogram or atom-info bug, not a source-correlation bug — emit_no_emit would catch it upstream).
|
-- (an atom declared in source but not emitted as a symbol is a metaprogram or atom-info bug, not a source-correlation bug — emit_no_emit would catch it upstream).
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
||||||
local src_path = src.path or "" ---@type string
|
local src_path = src.path or "" ---@type string
|
||||||
for _, atom_rec in ipairs(((src.scan or {}).atoms) or {}) do ---@type integer, AtomEntry
|
for _, atom_rec in ipairs(((src.scan or {}).atoms) or {}) do ---@type integer, AtomEntry
|
||||||
local info = addrs[atom_rec.name or atom_rec.raw_name] ---@type NmAddr|nil
|
local info = addrs[atom_rec.name or atom_rec.raw_name] ---@type NmAddr|nil
|
||||||
if info then
|
if info then
|
||||||
@@ -1018,7 +1018,7 @@ end
|
|||||||
--- @param atom_table DwarfAtom[]
|
--- @param atom_table DwarfAtom[]
|
||||||
--- @return table<string, DwarfComponentSite>
|
--- @return table<string, DwarfComponentSite>
|
||||||
local function collect_component_defs(atom_table)
|
local function collect_component_defs(atom_table)
|
||||||
local out = {} ---@type table<string, DwarfComponentSite> -- bag
|
local out = {} ---@type table<string, DwarfComponentSite> -- bag
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
for _, inv in ipairs(atom.invocations or {}) do ---@type integer, InvocationRecord
|
for _, inv in ipairs(atom.invocations or {}) do ---@type integer, InvocationRecord
|
||||||
if not out[inv.component_name] then
|
if not out[inv.component_name] then
|
||||||
@@ -1065,14 +1065,14 @@ end
|
|||||||
--- @param registries DwarfRegistries -- Merged registries from collect_per_source_registries
|
--- @param registries DwarfRegistries -- Merged registries from collect_per_source_registries
|
||||||
--- @return DwarfLoadPair[] -- List of {reg = <MIPS index>, field = <field name>}
|
--- @return DwarfLoadPair[] -- List of {reg = <MIPS index>, field = <field name>}
|
||||||
local function parse_body_load_pairs(body_tokens, binds_name, registries)
|
local function parse_body_load_pairs(body_tokens, binds_name, registries)
|
||||||
local pairs = {} ---@type DwarfLoadPair[]
|
local pairs = {} ---@type DwarfLoadPair[]
|
||||||
local reg_index_by_name = (registries and registries.register_alias_registry) or {} ---@type table<string, AliasEntry> -- bag
|
local reg_index_by_name = (registries and registries.register_alias_registry) or {} ---@type table<string, AliasEntry> -- bag
|
||||||
-- One regex that matches any of: load_word, load_half, load_half_u, load_byte, load_byte_u, gte_lw, gte_lwc2.
|
-- One regex that matches any of: load_word, load_half, load_half_u, load_byte, load_byte_u, gte_lw, gte_lwc2.
|
||||||
-- The captured ident is `kind`; `inner` holds the parens body for arg parsing.
|
-- The captured ident is `kind`; `inner` holds the parens body for arg parsing.
|
||||||
local load_pattern = "^(load_word|load_half|load_half_u|load_byte|load_byte_u|gte_lw|gte_lwc2)%s*%((.*)%)$" ---@type string
|
local load_pattern = "^(load_word|load_half|load_half_u|load_byte|load_byte_u|gte_lw|gte_lwc2)%s*%((.*)%)$" ---@type string
|
||||||
for _, t in ipairs(body_tokens or {}) do ---@type integer, BodyToken
|
for _, t in ipairs(body_tokens or {}) do ---@type integer, BodyToken
|
||||||
local tok = duffle.trim(t.tok or "") ---@type string
|
local tok = duffle.trim(t.tok or "") ---@type string
|
||||||
local kind, inner = tok:match(load_pattern) ---@type string|nil, string|nil
|
local kind, inner = tok:match(load_pattern) ---@type string|nil, string|nil
|
||||||
if kind then
|
if kind then
|
||||||
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
||||||
-- Expected shape for an rbind piece-chain load: (R_<reg>, R_TapePtr, O_(Binds_<X>, FieldName))
|
-- Expected shape for an rbind piece-chain load: (R_<reg>, R_TapePtr, O_(Binds_<X>, FieldName))
|
||||||
@@ -1083,7 +1083,7 @@ local function parse_body_load_pairs(body_tokens, binds_name, registries)
|
|||||||
local third_arg = duffle.trim(args[3]) ---@type string
|
local third_arg = duffle.trim(args[3]) ---@type string
|
||||||
-- Match O_(Binds_<X>, FieldName)
|
-- Match O_(Binds_<X>, FieldName)
|
||||||
local b, f = third_arg:match("^O_%((Binds_[%w_]+)%s*,%s*(.-)%s*%)$") ---@type string|nil, string|nil
|
local b, f = third_arg:match("^O_%((Binds_[%w_]+)%s*,%s*(.-)%s*%)$") ---@type string|nil, string|nil
|
||||||
local alias_entry = reg_index_by_name[reg_name] ---@type AliasEntry|nil
|
local alias_entry = reg_index_by_name[reg_name] ---@type AliasEntry|nil
|
||||||
if b and b == binds_name and alias_entry and alias_entry.code then
|
if b and b == binds_name and alias_entry and alias_entry.code then
|
||||||
pairs[#pairs + 1] = {
|
pairs[#pairs + 1] = {
|
||||||
reg = alias_entry.code,
|
reg = alias_entry.code,
|
||||||
@@ -1118,7 +1118,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
-- Index binds by struct name; consume `scan.binds[i].fields` directly.
|
-- Index binds by struct name; consume `scan.binds[i].fields` directly.
|
||||||
-- The scan-source pass emits each Binds_X's fields as {[type_name, pointer_depth, offset, byte_size, ...]},
|
-- The scan-source pass emits each Binds_X's fields as {[type_name, pointer_depth, offset, byte_size, ...]},
|
||||||
-- so this pass builds the rbind_structs entry without re-parsing.
|
-- so this pass builds the rbind_structs entry without re-parsing.
|
||||||
local binds_by_name = {} ---@type table<string, BindsEntry> -- bag
|
local binds_by_name = {} ---@type table<string, BindsEntry> -- bag
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
||||||
local scan = src.scan ---@type SourceScan|nil
|
local scan = src.scan ---@type SourceScan|nil
|
||||||
if scan then
|
if scan then
|
||||||
@@ -1139,7 +1139,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Walk every atom_info; if `binds` is set, find the atom body_tokens + parse load_word pairs.
|
-- Walk every atom_info; if `binds` is set, find the atom body_tokens + parse load_word pairs.
|
||||||
local body_tokens_by_atom = {} ---@type table<string, BodyToken[]> -- bag
|
local body_tokens_by_atom = {} ---@type table<string, BodyToken[]> -- bag
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
||||||
local scan = src.scan ---@type SourceScan|nil
|
local scan = src.scan ---@type SourceScan|nil
|
||||||
if scan then
|
if scan then
|
||||||
@@ -1149,7 +1149,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local ai_by_atom = {} ---@type table<string, AtomInfoEntry> -- bag
|
local ai_by_atom = {} ---@type table<string, AtomInfoEntry> -- bag
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do ---@type integer, SourceFile
|
||||||
local scan = src.scan ---@type SourceScan|nil
|
local scan = src.scan ---@type SourceScan|nil
|
||||||
if scan then
|
if scan then
|
||||||
@@ -1161,7 +1161,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
|
|
||||||
for atom_name, ai in pairs(ai_by_atom) do ---@type string, AtomInfoEntry
|
for atom_name, ai in pairs(ai_by_atom) do ---@type string, AtomInfoEntry
|
||||||
if ai.binds then
|
if ai.binds then
|
||||||
local struct = rbind_structs[ai.binds] ---@type DwarfRbindStruct|nil
|
local struct = rbind_structs[ai.binds] ---@type DwarfRbindStruct|nil
|
||||||
local body_toks = body_tokens_by_atom[atom_name] ---@type BodyToken[]|nil
|
local body_toks = body_tokens_by_atom[atom_name] ---@type BodyToken[]|nil
|
||||||
if struct and body_toks then
|
if struct and body_toks then
|
||||||
local pairs = parse_body_load_pairs(body_toks, ai.binds, registries) ---@type DwarfLoadPair[]
|
local pairs = parse_body_load_pairs(body_toks, ai.binds, registries) ---@type DwarfLoadPair[]
|
||||||
@@ -1208,9 +1208,9 @@ local function build_dwarf_line_section(existing, atom_table)
|
|||||||
if #atom_table == 0 then return existing end
|
if #atom_table == 0 then return existing end
|
||||||
|
|
||||||
-- Build the sequences.
|
-- Build the sequences.
|
||||||
local sequences = {} ---@type string[]
|
local sequences = {} ---@type string[]
|
||||||
for _, atom in ipairs(atom_table) do sequences[#sequences + 1] = build_atom_sequence(atom) end ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do sequences[#sequences + 1] = build_atom_sequence(atom) end ---@type integer, DwarfAtom
|
||||||
local appended = table.concat(sequences) ---@type string
|
local appended = table.concat(sequences) ---@type string
|
||||||
|
|
||||||
-- Walk DWARF32 line units and retain the final unit's bounds.
|
-- Walk DWARF32 line units and retain the final unit's bounds.
|
||||||
-- The main C CU points at this final unit (DW_AT_stmt_list = 0x5b in today's ELF).
|
-- The main C CU points at this final unit (DW_AT_stmt_list = 0x5b in today's ELF).
|
||||||
@@ -1226,7 +1226,7 @@ local function build_dwarf_line_section(existing, atom_table)
|
|||||||
end
|
end
|
||||||
if unit_pos ~= #existing or not last_pos then return existing end
|
if unit_pos ~= #existing or not last_pos then return existing end
|
||||||
|
|
||||||
local new_length = last_length + #appended ---@type integer
|
local new_length = last_length + #appended ---@type integer
|
||||||
local new_length_bytes = elf_dwarf.write_u32_le(new_length) ---@type string
|
local new_length_bytes = elf_dwarf.write_u32_le(new_length) ---@type string
|
||||||
|
|
||||||
return existing:sub(1, last_pos)
|
return existing:sub(1, last_pos)
|
||||||
@@ -1272,8 +1272,8 @@ local function build_dwarf_aranges_section(existing, atom_table)
|
|||||||
|
|
||||||
-- Walk all units and emit each one (preserving existing structure).
|
-- Walk all units and emit each one (preserving existing structure).
|
||||||
-- For the LAST unit, replace the terminator with my entries + new term.
|
-- For the LAST unit, replace the terminator with my entries + new term.
|
||||||
local result = {} ---@type string[]
|
local result = {} ---@type string[]
|
||||||
local i = 0 ---@type integer -- zero-based wire offset
|
local i = 0 ---@type integer -- zero-based wire offset
|
||||||
local is_last_unit = false ---@type boolean
|
local is_last_unit = false ---@type boolean
|
||||||
|
|
||||||
while i < #existing do
|
while i < #existing do
|
||||||
@@ -1285,21 +1285,21 @@ local function build_dwarf_aranges_section(existing, atom_table)
|
|||||||
return existing
|
return existing
|
||||||
end
|
end
|
||||||
|
|
||||||
local unit_start = i ---@type integer
|
local unit_start = i ---@type integer
|
||||||
local unit_end_excl = i + 4 + ul ---@type integer
|
local unit_end_excl = i + 4 + ul ---@type integer
|
||||||
is_last_unit = (unit_end_excl == #existing)
|
is_last_unit = (unit_end_excl == #existing)
|
||||||
|
|
||||||
if is_last_unit then
|
if is_last_unit then
|
||||||
-- The old terminator is replaced by entries + a new terminator, so net section growth (and unit_length growth) is entries only.
|
-- The old terminator is replaced by entries + a new terminator, so net section growth (and unit_length growth) is entries only.
|
||||||
local added_bytes = #atom_table * elf_dwarf.DWARF4_ARANGES.entry_size ---@type integer
|
local added_bytes = #atom_table * elf_dwarf.DWARF4_ARANGES.entry_size ---@type integer
|
||||||
local new_ul = ul + added_bytes ---@type integer
|
local new_ul = ul + added_bytes ---@type integer
|
||||||
local new_ul_bytes = elf_dwarf.write_u32_le(new_ul) ---@type string
|
local new_ul_bytes = elf_dwarf.write_u32_le(new_ul) ---@type string
|
||||||
-- Emit everything EXCEPT the last 8 bytes (terminator).
|
-- Emit everything EXCEPT the last 8 bytes (terminator).
|
||||||
result[#result + 1] = new_ul_bytes
|
result[#result + 1] = new_ul_bytes
|
||||||
.. existing:sub(i + 5, unit_end_excl - elf_dwarf.DWARF4_ARANGES.terminator_size)
|
.. existing:sub(i + 5, unit_end_excl - elf_dwarf.DWARF4_ARANGES.terminator_size)
|
||||||
-- Append my atom entries.
|
-- Append my atom entries.
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
local a = atom.addr ---@type integer
|
local a = atom.addr ---@type integer
|
||||||
local size = atom.size_bytes ---@type integer
|
local size = atom.size_bytes ---@type integer
|
||||||
result[#result + 1] = elf_dwarf.write_u32_le(a) .. elf_dwarf.write_u32_le(size)
|
result[#result + 1] = elf_dwarf.write_u32_le(a) .. elf_dwarf.write_u32_le(size)
|
||||||
end
|
end
|
||||||
@@ -1337,10 +1337,10 @@ end
|
|||||||
local function build_dwarf_rnglists_section(existing, atom_table)
|
local function build_dwarf_rnglists_section(existing, atom_table)
|
||||||
if #existing <= elf_dwarf.DWARF5_RNGLISTS.first_entry_offset or #atom_table == 0 then return existing end
|
if #existing <= elf_dwarf.DWARF5_RNGLISTS.first_entry_offset or #atom_table == 0 then return existing end
|
||||||
|
|
||||||
local unit_length = elf_dwarf.read_u32_le(existing, elf_dwarf.DWARF5_RNGLISTS.unit_length_offset) ---@type integer
|
local unit_length = elf_dwarf.read_u32_le(existing, elf_dwarf.DWARF5_RNGLISTS.unit_length_offset) ---@type integer
|
||||||
local version = elf_dwarf.read_u16_le(existing, elf_dwarf.DWARF5_RNGLISTS.version_offset) ---@type integer
|
local version = elf_dwarf.read_u16_le(existing, elf_dwarf.DWARF5_RNGLISTS.version_offset) ---@type integer
|
||||||
local address_size = existing:byte(elf_dwarf.DWARF5_RNGLISTS.addr_size_offset + 1) ---@type integer
|
local address_size = existing:byte(elf_dwarf.DWARF5_RNGLISTS.addr_size_offset + 1) ---@type integer
|
||||||
local segment_size = existing:byte(elf_dwarf.DWARF5_RNGLISTS.seg_size_offset + 1) ---@type integer
|
local segment_size = existing:byte(elf_dwarf.DWARF5_RNGLISTS.seg_size_offset + 1) ---@type integer
|
||||||
local offset_entry_count = elf_dwarf.read_u32_le(existing, elf_dwarf.DWARF5_RNGLISTS.offset_count_offset) ---@type integer
|
local offset_entry_count = elf_dwarf.read_u32_le(existing, elf_dwarf.DWARF5_RNGLISTS.offset_count_offset) ---@type integer
|
||||||
|
|
||||||
if unit_length + 4 ~= #existing
|
if unit_length + 4 ~= #existing
|
||||||
@@ -1352,14 +1352,14 @@ local function build_dwarf_rnglists_section(existing, atom_table)
|
|||||||
return existing
|
return existing
|
||||||
end
|
end
|
||||||
|
|
||||||
local entries = {} ---@type string[]
|
local entries = {} ---@type string[]
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
entries[#entries + 1] = string.char(DW_RLE_start_length)
|
entries[#entries + 1] = string.char(DW_RLE_start_length)
|
||||||
.. elf_dwarf.write_u32_le(atom.addr)
|
.. elf_dwarf.write_u32_le(atom.addr)
|
||||||
.. uleb128(atom.size_bytes)
|
.. uleb128(atom.size_bytes)
|
||||||
end
|
end
|
||||||
local appended = table.concat(entries) ---@type string
|
local appended = table.concat(entries) ---@type string
|
||||||
local new_length = unit_length + #appended ---@type integer
|
local new_length = unit_length + #appended ---@type integer
|
||||||
local new_length_bytes = elf_dwarf.write_u32_le(new_length) ---@type string
|
local new_length_bytes = elf_dwarf.write_u32_le(new_length) ---@type string
|
||||||
|
|
||||||
return new_length_bytes
|
return new_length_bytes
|
||||||
@@ -1390,16 +1390,16 @@ end
|
|||||||
--- @param rbind DwarfRbind -- {regs = {{reg, field}, ...}, fields = {{name, offset}, ...}, bytes = N}
|
--- @param rbind DwarfRbind -- {regs = {{reg, field}, ...}, fields = {{name, offset}, ...}, bytes = N}
|
||||||
--- @return string -- the exprloc byte sequence (length-prefixed)
|
--- @return string -- the exprloc byte sequence (length-prefixed)
|
||||||
local function piece_chain_exprloc(rbind)
|
local function piece_chain_exprloc(rbind)
|
||||||
local op_bytes = {} ---@type string[]
|
local op_bytes = {} ---@type string[]
|
||||||
local field_offset_by_name = {} ---@type table<string, integer> -- bag
|
local field_offset_by_name = {} ---@type table<string, integer> -- bag
|
||||||
for _, f in ipairs(rbind.fields) do ---@type integer, TypeField
|
for _, f in ipairs(rbind.fields) do ---@type integer, TypeField
|
||||||
field_offset_by_name[f.name] = f.offset
|
field_offset_by_name[f.name] = f.offset
|
||||||
end
|
end
|
||||||
local next_offset = rbind.bytes ---@type integer
|
local next_offset = rbind.bytes ---@type integer
|
||||||
for i = #rbind.regs, 1, -1 do ---@type integer -- walk backwards to know each piece's size
|
for i = #rbind.regs, 1, -1 do ---@type integer -- walk backwards to know each piece's size
|
||||||
local pair = rbind.regs[i] ---@type DwarfLoadPair
|
local pair = rbind.regs[i] ---@type DwarfLoadPair
|
||||||
local off = field_offset_by_name[pair.field] or 0 ---@type integer
|
local off = field_offset_by_name[pair.field] or 0 ---@type integer
|
||||||
local size ---@type integer
|
local size ---@type integer
|
||||||
if i == #rbind.regs then
|
if i == #rbind.regs then
|
||||||
size = next_offset - off
|
size = next_offset - off
|
||||||
else
|
else
|
||||||
@@ -1416,9 +1416,9 @@ local function piece_chain_exprloc(rbind)
|
|||||||
next_offset = off
|
next_offset = off
|
||||||
end
|
end
|
||||||
-- We built it back-to-front; reverse it.
|
-- We built it back-to-front; reverse it.
|
||||||
local rev = {} ---@type string[]
|
local rev = {} ---@type string[]
|
||||||
for i = #op_bytes, 1, -1 do rev[#rev + 1] = op_bytes[i] end ---@type integer
|
for i = #op_bytes, 1, -1 do rev[#rev + 1] = op_bytes[i] end ---@type integer
|
||||||
local op = table.concat(rev) ---@type string
|
local op = table.concat(rev) ---@type string
|
||||||
return uleb128(#op) .. op
|
return uleb128(#op) .. op
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1440,10 +1440,10 @@ end
|
|||||||
-- "math.floor(/16) instead of bit.rshift for LuaJIT 2.1 compat" comment at line 352).
|
-- "math.floor(/16) instead of bit.rshift for LuaJIT 2.1 compat" comment at line 352).
|
||||||
|
|
||||||
-- DWARF5 compile-unit header constants.
|
-- DWARF5 compile-unit header constants.
|
||||||
local DW_VERSION_5 = 5 ---@type integer
|
local DW_VERSION_5 = 5 ---@type integer
|
||||||
local DW_UT_compile = 0x01 ---@type integer
|
local DW_UT_compile = 0x01 ---@type integer
|
||||||
local DWARF32_TERMINATOR = 0xFFFFFFFF ---@type integer -- sentinel for DWARF64 marker
|
local DWARF32_TERMINATOR = 0xFFFFFFFF ---@type integer -- sentinel for DWARF64 marker
|
||||||
local CU_HEADER_SIZE = 12 ---@type integer -- 4 + 2 + 1 + 1 + 4
|
local CU_HEADER_SIZE = 12 ---@type integer -- 4 + 2 + 1 + 1 + 4
|
||||||
|
|
||||||
--- Walk .debug_info to find the FINAL compilation unit, validate it as a DWARF5 32-bit compile-unit, and extract its bounds + abbrev-table offset.
|
--- Walk .debug_info to find the FINAL compilation unit, validate it as a DWARF5 32-bit compile-unit, and extract its bounds + abbrev-table offset.
|
||||||
--- Returns nil on any layout mismatch. Callers fall back to existing sections.
|
--- Returns nil on any layout mismatch. Callers fall back to existing sections.
|
||||||
@@ -1467,7 +1467,7 @@ local function find_main_cu_layout(existing)
|
|||||||
local buf_len = #existing ---@type integer
|
local buf_len = #existing ---@type integer
|
||||||
if buf_len < CU_HEADER_SIZE then return nil end
|
if buf_len < CU_HEADER_SIZE then return nil end
|
||||||
|
|
||||||
local pos = 0 ---@type integer
|
local pos = 0 ---@type integer
|
||||||
local main_cu_start = nil ---@type integer
|
local main_cu_start = nil ---@type integer
|
||||||
local main_cu_end_excl = nil ---@type integer
|
local main_cu_end_excl = nil ---@type integer
|
||||||
while pos + 4 <= buf_len do
|
while pos + 4 <= buf_len do
|
||||||
@@ -1489,10 +1489,10 @@ local function find_main_cu_layout(existing)
|
|||||||
-- [6] unit_type
|
-- [6] unit_type
|
||||||
-- [7] address_size
|
-- [7] address_size
|
||||||
-- [8..11] debug_abbrev_offset
|
-- [8..11] debug_abbrev_offset
|
||||||
local hdr = main_cu_start + 4 ---@type integer
|
local hdr = main_cu_start + 4 ---@type integer
|
||||||
local version = elf_dwarf.read_u16_le(existing, hdr) ---@type integer
|
local version = elf_dwarf.read_u16_le(existing, hdr) ---@type integer
|
||||||
local unit_type = existing:byte(hdr + 2 + 1) ---@type integer
|
local unit_type = existing:byte(hdr + 2 + 1) ---@type integer
|
||||||
local address_size = existing:byte(hdr + 3 + 1) ---@type integer
|
local address_size = existing:byte(hdr + 3 + 1) ---@type integer
|
||||||
local abbrev_off = elf_dwarf.read_u32_le(existing, hdr + 4) ---@type integer
|
local abbrev_off = elf_dwarf.read_u32_le(existing, hdr + 4) ---@type integer
|
||||||
if version ~= DW_VERSION_5 or unit_type ~= DW_UT_compile or address_size ~= 4 then
|
if version ~= DW_VERSION_5 or unit_type ~= DW_UT_compile or address_size ~= 4 then
|
||||||
return nil
|
return nil
|
||||||
@@ -1673,7 +1673,7 @@ local function build_new_strings(atom_table, registries)
|
|||||||
-- The CU name + comp_dir are the first two strings (offsets 0 and N1).
|
-- The CU name + comp_dir are the first two strings (offsets 0 and N1).
|
||||||
-- Then each unique atom name + each register name follows.
|
-- Then each unique atom name + each register name follows.
|
||||||
local strings = {} ---@type string[]
|
local strings = {} ---@type string[]
|
||||||
local map = {} ---@type table<string, integer> -- bag
|
local map = {} ---@type table<string, integer> -- bag
|
||||||
|
|
||||||
-- CU name at offset 0 in the new blob
|
-- CU name at offset 0 in the new blob
|
||||||
strings[#strings + 1] = DEFAULT_CU_NAME .. "\0"
|
strings[#strings + 1] = DEFAULT_CU_NAME .. "\0"
|
||||||
@@ -1698,7 +1698,7 @@ local function build_new_strings(atom_table, registries)
|
|||||||
-- filtered to MIPS GPR 0..31 — the same filter that build_inserted_children applies
|
-- filtered to MIPS GPR 0..31 — the same filter that build_inserted_children applies
|
||||||
-- for the RR_<name> locals, so .debug_str entries stay in sync with .debug_info).
|
-- for the RR_<name> locals, so .debug_str entries stay in sync with .debug_info).
|
||||||
-- Lua's pairs() is non-deterministic; sort the alias names first so the emitted .debug_str bytes are byte-identical across runs.
|
-- Lua's pairs() is non-deterministic; sort the alias names first so the emitted .debug_str bytes are byte-identical across runs.
|
||||||
local sorted_alias_names = {} ---@type string[]
|
local sorted_alias_names = {} ---@type string[]
|
||||||
for r_name, alias in pairs(registries.register_alias_registry or {}) do ---@type string, AliasEntry|nil
|
for r_name, alias in pairs(registries.register_alias_registry or {}) do ---@type string, AliasEntry|nil
|
||||||
if alias.code and alias.code >= 0 and alias.code <= 31 then
|
if alias.code and alias.code >= 0 and alias.code <= 31 then
|
||||||
sorted_alias_names[#sorted_alias_names + 1] = r_name
|
sorted_alias_names[#sorted_alias_names + 1] = r_name
|
||||||
@@ -1775,13 +1775,13 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- by_alias_order: sorted list of by_alias keys, for deterministic iteration order.
|
-- by_alias_order: sorted list of by_alias keys, for deterministic iteration order.
|
||||||
-- Lua's pairs() order is implementation-defined and varies between runs; without sorting, the per-atom variable emission order
|
-- Lua's pairs() order is implementation-defined and varies between runs; without sorting, the per-atom variable emission order
|
||||||
-- would be non-deterministic and the .debug_info bytes would differ across builds.
|
-- would be non-deterministic and the .debug_info bytes would differ across builds.
|
||||||
local by_alias = {} ---@type table<string, AliasEntry> -- bag
|
local by_alias = {} ---@type table<string, AliasEntry> -- bag
|
||||||
for r_name, alias in pairs(registries.register_alias_registry or {}) do ---@type string, AliasEntry|nil
|
for r_name, alias in pairs(registries.register_alias_registry or {}) do ---@type string, AliasEntry|nil
|
||||||
if alias.code and alias.code >= 0 and alias.code <= 31 then
|
if alias.code and alias.code >= 0 and alias.code <= 31 then
|
||||||
by_alias[r_name] = alias
|
by_alias[r_name] = alias
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local by_alias_order = {} ---@type string[]
|
local by_alias_order = {} ---@type string[]
|
||||||
for r_name in pairs(by_alias) do by_alias_order[#by_alias_order + 1] = r_name end ---@type string
|
for r_name in pairs(by_alias) do by_alias_order[#by_alias_order + 1] = r_name end ---@type string
|
||||||
table.sort(by_alias_order)
|
table.sort(by_alias_order)
|
||||||
|
|
||||||
@@ -1790,7 +1790,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
--- @param atoms DwarfAtom[]
|
--- @param atoms DwarfAtom[]
|
||||||
--- @return table<string, DwarfAtom>
|
--- @return table<string, DwarfAtom>
|
||||||
local function build_atom_name_index(atoms)
|
local function build_atom_name_index(atoms)
|
||||||
local m = {} ---@type table<string, DwarfAtom> -- bag
|
local m = {} ---@type table<string, DwarfAtom> -- bag
|
||||||
for _, a in ipairs(atoms or {}) do ---@type integer, AliasEntry|nil
|
for _, a in ipairs(atoms or {}) do ---@type integer, AliasEntry|nil
|
||||||
if a and a.name then m[a.name] = a end
|
if a and a.name then m[a.name] = a end
|
||||||
end
|
end
|
||||||
@@ -1861,7 +1861,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
local row = DIE_SCHEMA[schema_name] ---@type DieSchema
|
local row = DIE_SCHEMA[schema_name] ---@type DieSchema
|
||||||
emit(uleb128(row.abbrev))
|
emit(uleb128(row.abbrev))
|
||||||
for _, attr in ipairs(row.attrs) do ---@type integer, DieSchemaAttr
|
for _, attr in ipairs(row.attrs) do ---@type integer, DieSchemaAttr
|
||||||
local v = values[attr.key] ---@type string|integer
|
local v = values[attr.key] ---@type string|integer
|
||||||
local w = FORM_WRITERS[attr.form] ---@type DieFormWriter
|
local w = FORM_WRITERS[attr.form] ---@type DieFormWriter
|
||||||
if not w then error("emit_die: unknown form " .. tostring(attr.form)) end
|
if not w then error("emit_die: unknown form " .. tostring(attr.form)) end
|
||||||
w(emit, v)
|
w(emit, v)
|
||||||
@@ -1898,7 +1898,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- Always include the base_type "unsigned int" as the U4 target.
|
-- Always include the base_type "unsigned int" as the U4 target.
|
||||||
type_offsets["U4"] = base_type_section_offset
|
type_offsets["U4"] = base_type_section_offset
|
||||||
-- Collect every unique (type_name, max_pointer_depth) used by any rbind field.
|
-- Collect every unique (type_name, max_pointer_depth) used by any rbind field.
|
||||||
local used_typed_views = {} ---@type table<string, integer> -- bag -- { [type_name] = max_depth }
|
local used_typed_views = {} ---@type table<string, integer> -- bag -- { [type_name] = max_depth }
|
||||||
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
for _, atom in ipairs(atom_table) do ---@type integer, DwarfAtom
|
||||||
if atom.rbind and atom.rbind.fields then
|
if atom.rbind and atom.rbind.fields then
|
||||||
for _, f in ipairs(atom.rbind.fields) do ---@type integer, TypeField
|
for _, f in ipairs(atom.rbind.fields) do ---@type integer, TypeField
|
||||||
@@ -1912,7 +1912,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- Sort for deterministic emission.
|
-- Sort for deterministic emission.
|
||||||
local sorted_typed_types = {} ---@type string[]
|
local sorted_typed_types = {} ---@type string[]
|
||||||
for tn in pairs(used_typed_views) do sorted_typed_types[#sorted_typed_types + 1] = tn end ---@type string
|
for tn in pairs(used_typed_views) do sorted_typed_types[#sorted_typed_types + 1] = tn end ---@type string
|
||||||
table.sort(sorted_typed_types)
|
table.sort(sorted_typed_types)
|
||||||
-- For each non-U4 type, emit a typedef (DW_TAG_typedef) named after the type and referencing the base_type "unsigned int" (4 bytes).
|
-- For each non-U4 type, emit a typedef (DW_TAG_typedef) named after the type and referencing the base_type "unsigned int" (4 bytes).
|
||||||
@@ -1938,7 +1938,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if entry.byte_size == nil then return nil end
|
if entry.byte_size == nil then return nil end
|
||||||
local members = {} ---@type DwarfTypeLayoutMember[]
|
local members = {} ---@type DwarfTypeLayoutMember[]
|
||||||
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
||||||
if f.offset == nil or f.byte_size == nil then return nil end
|
if f.offset == nil or f.byte_size == nil then return nil end
|
||||||
members[#members + 1] = {
|
members[#members + 1] = {
|
||||||
@@ -2028,7 +2028,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
for _, tn in ipairs(sorted_typed_types) do ---@type integer, string
|
for _, tn in ipairs(sorted_typed_types) do ---@type integer, string
|
||||||
if tn ~= "U4" then
|
if tn ~= "U4" then
|
||||||
local depth = used_typed_views[tn] ---@type integer
|
local depth = used_typed_views[tn] ---@type integer
|
||||||
local struct_offset = emit_struct_layout(tn) ---@type integer
|
local struct_offset = emit_struct_layout(tn) ---@type integer
|
||||||
if not struct_offset then
|
if not struct_offset then
|
||||||
local innermost_offset = next_offset() ---@type integer
|
local innermost_offset = next_offset() ---@type integer
|
||||||
@@ -2084,8 +2084,8 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
type_chain_offsets["U4|1"] = u4_chain_offset
|
type_chain_offsets["U4|1"] = u4_chain_offset
|
||||||
|
|
||||||
-- 2) Emit one DW_TAG_structure_type per unique Binds_X.
|
-- 2) Emit one DW_TAG_structure_type per unique Binds_X.
|
||||||
local struct_section_offsets = {} ---@type table<string, integer> -- bag
|
local struct_section_offsets = {} ---@type table<string, integer> -- bag
|
||||||
local sorted_struct_names = {} ---@type string[]
|
local sorted_struct_names = {} ---@type string[]
|
||||||
for k in pairs(rbind_structs) do sorted_struct_names[#sorted_struct_names + 1] = k end ---@type string
|
for k in pairs(rbind_structs) do sorted_struct_names[#sorted_struct_names + 1] = k end ---@type string
|
||||||
table.sort(sorted_struct_names)
|
table.sort(sorted_struct_names)
|
||||||
for _, binds_name in ipairs(sorted_struct_names) do ---@type integer, string
|
for _, binds_name in ipairs(sorted_struct_names) do ---@type integer, string
|
||||||
@@ -2119,13 +2119,13 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- Each abstract DIE is a CU-level child (sibling of the per-atom subprograms below).
|
-- Each abstract DIE is a CU-level child (sibling of the per-atom subprograms below).
|
||||||
-- The abstract DIE's section offset is later used by inlined_subroutine DIEs (which embed `DW_AT_abstract_origin = ref4 → abstract DIE`).
|
-- The abstract DIE's section offset is later used by inlined_subroutine DIEs (which embed `DW_AT_abstract_origin = ref4 → abstract DIE`).
|
||||||
-- Each abstract DIE also carries DW_AT_decl_file + DW_AT_decl_line pointing at the component's definition site (file path + body line).
|
-- Each abstract DIE also carries DW_AT_decl_file + DW_AT_decl_line pointing at the component's definition site (file path + body line).
|
||||||
local component_defs = collect_component_defs(atom_table) ---@type table<string, DwarfComponentSite> -- bag
|
local component_defs = collect_component_defs(atom_table) ---@type table<string, DwarfComponentSite> -- bag
|
||||||
local abstract_offsets = {} ---@type table<string, integer> -- bag -- name -> section offset
|
local abstract_offsets = {} ---@type table<string, integer> -- bag -- name -> section offset
|
||||||
local sorted_comp_names = {} ---@type string[]
|
local sorted_comp_names = {} ---@type string[]
|
||||||
for name in pairs(component_defs) do sorted_comp_names[#sorted_comp_names + 1] = name end ---@type string
|
for name in pairs(component_defs) do sorted_comp_names[#sorted_comp_names + 1] = name end ---@type string
|
||||||
table.sort(sorted_comp_names)
|
table.sort(sorted_comp_names)
|
||||||
-- DW_INL_inlined (1) = "this subroutine was inlined" — accurate for the mac_* components.
|
-- DW_INL_inlined (1) = "this subroutine was inlined" — accurate for the mac_* components.
|
||||||
local DW_INL_inlined = 0x01 ---@type integer
|
local DW_INL_inlined = 0x01 ---@type integer
|
||||||
for _, comp_name in ipairs(sorted_comp_names) do ---@type integer, string
|
for _, comp_name in ipairs(sorted_comp_names) do ---@type integer, string
|
||||||
local def = component_defs[comp_name] ---@type DwarfComponentSite
|
local def = component_defs[comp_name] ---@type DwarfComponentSite
|
||||||
abstract_offsets[comp_name] = next_offset()
|
abstract_offsets[comp_name] = next_offset()
|
||||||
@@ -2159,10 +2159,10 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- (e) enum-site atom_type(<T>) default: register_alias_registry[R_Name].default_type (per-alias fallback declared in lottes_tape.h)
|
-- (e) enum-site atom_type(<T>) default: register_alias_registry[R_Name].default_type (per-alias fallback declared in lottes_tape.h)
|
||||||
-- (f) void* fallback: the void_chain_offset built in section 1b; gdb renders `(void *) 0x...` (hex)
|
-- (f) void* fallback: the void_chain_offset built in section 1b; gdb renders `(void *) 0x...` (hex)
|
||||||
-- An R_Name absent from the registry AND missed by all of (a..e) skips emission for that alias entirely.
|
-- An R_Name absent from the registry AND missed by all of (a..e) skips emission for that alias entirely.
|
||||||
local atom_view_ctx_fields = nil ---@type table<string, TypeField>|nil -- bag -- populated by step (b); map field_name -> field entry
|
local atom_view_ctx_fields = nil ---@type table<string, TypeField>|nil -- bag -- populated by step (b); map field_name -> field entry
|
||||||
local reg_to_field_ctx = nil ---@type table<integer, string>|nil -- bag -- populated by step (b); map GPR index -> field name
|
local reg_to_field_ctx = nil ---@type table<integer, string>|nil -- bag -- populated by step (b); map GPR index -> field name
|
||||||
local atom_view_phase_fields = nil ---@type table<string, TypeField>|nil -- bag -- populated by step (d); map field_name -> field entry
|
local atom_view_phase_fields = nil ---@type table<string, TypeField>|nil -- bag -- populated by step (d); map field_name -> field entry
|
||||||
local reg_to_field_phase = nil ---@type table<integer, string>|nil -- bag -- populated by step (d); map GPR index -> field name
|
local reg_to_field_phase = nil ---@type table<integer, string>|nil -- bag -- populated by step (d); map GPR index -> field name
|
||||||
-- atom-name -> atom lookup is precomputed once as atom_by_name_global.
|
-- atom-name -> atom lookup is precomputed once as atom_by_name_global.
|
||||||
local field_type_by_name = {} ---@type table<string, TypeField> -- bag
|
local field_type_by_name = {} ---@type table<string, TypeField> -- bag
|
||||||
if atom.rbind and atom.rbind.fields then
|
if atom.rbind and atom.rbind.fields then
|
||||||
@@ -2194,7 +2194,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
-- step (d) inputs: this atom's `atom_phase(<label>)`. Find the FIRST atom in the same phase group that has its own rbind
|
-- step (d) inputs: this atom's `atom_phase(<label>)`. Find the FIRST atom in the same phase group that has its own rbind
|
||||||
-- and is in source-order (declared before this atom in any source file); locate it via the per-atom_info entry whose phase == this atom's name.
|
-- and is in source-order (declared before this atom in any source file); locate it via the per-atom_info entry whose phase == this atom's name.
|
||||||
local my_phase_label = nil ---@type string|nil
|
local my_phase_label = nil ---@type string|nil
|
||||||
for _, ai in ipairs(registries.atom_infos or {}) do ---@type integer, AtomInfoEntry
|
for _, ai in ipairs(registries.atom_infos or {}) do ---@type integer, AtomInfoEntry
|
||||||
if ai.atom_name == atom.name and ai.phase then
|
if ai.atom_name == atom.name and ai.phase then
|
||||||
my_phase_label = ai.phase
|
my_phase_label = ai.phase
|
||||||
@@ -2240,7 +2240,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
--- @param alias_code integer
|
--- @param alias_code integer
|
||||||
--- @return integer|nil
|
--- @return integer|nil
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local ctx_field_name = reg_to_field_ctx and reg_to_field_ctx[alias_code] ---@type string|nil
|
local ctx_field_name = reg_to_field_ctx and reg_to_field_ctx[alias_code] ---@type string|nil
|
||||||
local ctx_f = ctx_field_name and atom_view_ctx_fields and atom_view_ctx_fields[ctx_field_name] ---@type TypeField|nil
|
local ctx_f = ctx_field_name and atom_view_ctx_fields and atom_view_ctx_fields[ctx_field_name] ---@type TypeField|nil
|
||||||
if ctx_f and ctx_f.pointer_depth and ctx_f.pointer_depth > 0 then
|
if ctx_f and ctx_f.pointer_depth and ctx_f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[ctx_f.type_name .. "|" .. ctx_f.pointer_depth]
|
return type_chain_offsets[ctx_f.type_name .. "|" .. ctx_f.pointer_depth]
|
||||||
@@ -2251,7 +2251,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
--- @param alias_code integer
|
--- @param alias_code integer
|
||||||
--- @return integer|nil
|
--- @return integer|nil
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local field_name = reg_to_field[alias_code] ---@type string|nil
|
local field_name = reg_to_field[alias_code] ---@type string|nil
|
||||||
local f = field_name and field_type_by_name[field_name] ---@type TypeField|nil
|
local f = field_name and field_type_by_name[field_name] ---@type TypeField|nil
|
||||||
if f and f.pointer_depth and f.pointer_depth > 0 then
|
if f and f.pointer_depth and f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[f.type_name .. "|" .. f.pointer_depth]
|
return type_chain_offsets[f.type_name .. "|" .. f.pointer_depth]
|
||||||
@@ -2262,7 +2262,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
--- @param alias_code integer
|
--- @param alias_code integer
|
||||||
--- @return integer|nil
|
--- @return integer|nil
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local phase_field_name = reg_to_field_phase and reg_to_field_phase[alias_code] ---@type string|nil
|
local phase_field_name = reg_to_field_phase and reg_to_field_phase[alias_code] ---@type string|nil
|
||||||
local phase_f = phase_field_name and atom_view_phase_fields and atom_view_phase_fields[phase_field_name] ---@type TypeField|nil
|
local phase_f = phase_field_name and atom_view_phase_fields and atom_view_phase_fields[phase_field_name] ---@type TypeField|nil
|
||||||
if phase_f and phase_f.pointer_depth and phase_f.pointer_depth > 0 then
|
if phase_f and phase_f.pointer_depth and phase_f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[phase_f.type_name .. "|" .. phase_f.pointer_depth]
|
return type_chain_offsets[phase_f.type_name .. "|" .. phase_f.pointer_depth]
|
||||||
@@ -2282,11 +2282,11 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
|
|
||||||
-- Iterate `by_alias` in sorted order; Lua's pairs() is non-deterministic, so sorting ensures byte-identical DWARF output across builds.
|
-- Iterate `by_alias` in sorted order; Lua's pairs() is non-deterministic, so sorting ensures byte-identical DWARF output across builds.
|
||||||
for _, r_name in ipairs(by_alias_order) do ---@type integer, string
|
for _, r_name in ipairs(by_alias_order) do ---@type integer, string
|
||||||
local alias = by_alias[r_name] ---@type AliasEntry|nil
|
local alias = by_alias[r_name] ---@type AliasEntry|nil
|
||||||
local rr_name = "RR_" .. strip_r_prefix(r_name) ---@type string
|
local rr_name = "RR_" .. strip_r_prefix(r_name) ---@type string
|
||||||
local alias_code = alias.code ---@type integer
|
local alias_code = alias.code ---@type integer
|
||||||
local type_offset = type_chain_offsets["void|1"] ---@type integer
|
local type_offset = type_chain_offsets["void|1"] ---@type integer
|
||||||
for _, step in ipairs(PRECEDENCE_STEPS) do ---@type integer, DwarfPrecedenceStep
|
for _, step in ipairs(PRECEDENCE_STEPS) do ---@type integer, DwarfPrecedenceStep
|
||||||
local candidate = step(r_name, alias_code) ---@type integer
|
local candidate = step(r_name, alias_code) ---@type integer
|
||||||
if candidate then type_offset = candidate; break end
|
if candidate then type_offset = candidate; break end
|
||||||
end
|
end
|
||||||
@@ -2303,7 +2303,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- Two PC ranges cover every field: [atom.addr, last_load+8) describes each field as tape memory (DW_OP_bregN + offset) piece,
|
-- Two PC ranges cover every field: [atom.addr, last_load+8) describes each field as tape memory (DW_OP_bregN + offset) piece,
|
||||||
-- and [last_load+8, atom.end) describes each field as a GPR (DW_OP_regN) piece.
|
-- and [last_load+8, atom.end) describes each field as a GPR (DW_OP_regN) piece.
|
||||||
if atom.rbind then
|
if atom.rbind then
|
||||||
local binds_name = atom.rbind.binds ---@type string
|
local binds_name = atom.rbind.binds ---@type string
|
||||||
local loclists_offset = loclists_offsets[atom.name] or 0 ---@type integer
|
local loclists_offset = loclists_offsets[atom.name] or 0 ---@type integer
|
||||||
emit_die("bind_var_loclist", {
|
emit_die("bind_var_loclist", {
|
||||||
name = "bind_args",
|
name = "bind_args",
|
||||||
@@ -2322,7 +2322,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- This invocation emits no inlined_subroutine DIE; the whole-PC-range non-statement rows in .debug_line provide full skip semantics.
|
-- This invocation emits no inlined_subroutine DIE; the whole-PC-range non-statement rows in .debug_line provide full skip semantics.
|
||||||
-- Stepping from the preceding atom statement lands at the first unskipped row after this invocation's range.
|
-- Stepping from the preceding atom statement lands at the first unskipped row after this invocation's range.
|
||||||
else
|
else
|
||||||
local inv_low = atom.addr + inv.start_pos * MIPS_BYTES_PER_WORD ---@type integer
|
local inv_low = atom.addr + inv.start_pos * MIPS_BYTES_PER_WORD ---@type integer
|
||||||
local inv_high = atom.addr + (inv.end_pos + 1) * MIPS_BYTES_PER_WORD ---@type integer
|
local inv_high = atom.addr + (inv.end_pos + 1) * MIPS_BYTES_PER_WORD ---@type integer
|
||||||
emit_die("inlined_subroutine", {
|
emit_die("inlined_subroutine", {
|
||||||
abstract_origin = ref4_of(abstract_offsets[inv.component_name]),
|
abstract_origin = ref4_of(abstract_offsets[inv.component_name]),
|
||||||
@@ -2368,7 +2368,7 @@ local function build_debug_abbrev_section(existing, main_abbrev_offset)
|
|||||||
-- Duplicate the main table declarations, excluding its terminating 0 byte.
|
-- Duplicate the main table declarations, excluding its terminating 0 byte.
|
||||||
-- (1-indexed sub: existing:sub(main_abbrev_offset + 1, table_end) reads bytes from 0-based [main_abbrev_offset .. table_end - 1].)
|
-- (1-indexed sub: existing:sub(main_abbrev_offset + 1, table_end) reads bytes from 0-based [main_abbrev_offset .. table_end - 1].)
|
||||||
local main_table_dup = existing:sub(main_abbrev_offset + 1, table_end) ---@type string
|
local main_table_dup = existing:sub(main_abbrev_offset + 1, table_end) ---@type string
|
||||||
local new_abbrevs = build_new_abbrev() ---@type string -- includes its own terminating 0
|
local new_abbrevs = build_new_abbrev() ---@type string -- includes its own terminating 0
|
||||||
|
|
||||||
-- The MAIN CU's debug_abbrev_offset points to the duplicate's start (= #existing).
|
-- The MAIN CU's debug_abbrev_offset points to the duplicate's start (= #existing).
|
||||||
-- Codes 100..106 follow the duplicate's declarations inside that same table.
|
-- Codes 100..106 follow the duplicate's declarations inside that same table.
|
||||||
@@ -2407,12 +2407,12 @@ end
|
|||||||
local function build_debug_info_section(existing, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
local function build_debug_info_section(existing, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
||||||
-- 1) Build the inserted children bytes (just before the main CU's root terminator).
|
-- 1) Build the inserted children bytes (just before the main CU's root terminator).
|
||||||
local inserted = build_inserted_children(main_cu_start, main_cu_end_excl, atom_table, rbind_structs, loclists_offsets, registries) ---@type string
|
local inserted = build_inserted_children(main_cu_start, main_cu_end_excl, atom_table, rbind_structs, loclists_offsets, registries) ---@type string
|
||||||
local inserted_len = #inserted ---@type integer
|
local inserted_len = #inserted ---@type integer
|
||||||
|
|
||||||
-- 2) Patch main CU's unit_length += inserted_len.
|
-- 2) Patch main CU's unit_length += inserted_len.
|
||||||
local old_unit_length = elf_dwarf.read_u32_le(existing, main_cu_start) ---@type integer
|
local old_unit_length = elf_dwarf.read_u32_le(existing, main_cu_start) ---@type integer
|
||||||
local new_unit_length = old_unit_length + inserted_len ---@type integer
|
local new_unit_length = old_unit_length + inserted_len ---@type integer
|
||||||
local new_unit_length_bytes = elf_dwarf.write_u32_le(new_unit_length) ---@type string
|
local new_unit_length_bytes = elf_dwarf.write_u32_le(new_unit_length) ---@type string
|
||||||
|
|
||||||
-- 3) Patch main CU's debug_abbrev_offset (bytes [main_cu_start + 8 .. + 11]).
|
-- 3) Patch main CU's debug_abbrev_offset (bytes [main_cu_start + 8 .. + 11]).
|
||||||
local new_abbrev_offset_bytes = elf_dwarf.write_u32_le(new_abbrev_offset) ---@type string
|
local new_abbrev_offset_bytes = elf_dwarf.write_u32_le(new_abbrev_offset) ---@type string
|
||||||
@@ -2425,8 +2425,8 @@ local function build_debug_info_section(existing, main_cu_start, main_cu_end_exc
|
|||||||
-- [main_cu_start + 8 .. + 11] debug_abbrev_offset (PATCHED)
|
-- [main_cu_start + 8 .. + 11] debug_abbrev_offset (PATCHED)
|
||||||
-- [main_cu_start + 12 .. main_cu_end_excl - 2] existing DIE bytes, unchanged
|
-- [main_cu_start + 12 .. main_cu_end_excl - 2] existing DIE bytes, unchanged
|
||||||
-- [main_cu_end_excl - 1] root children-terminator, unchanged 0
|
-- [main_cu_end_excl - 1] root children-terminator, unchanged 0
|
||||||
local pre_end = main_cu_end_excl - 2 ---@type integer -- 0-based end of existing DIE bytes (inclusive)
|
local pre_end = main_cu_end_excl - 2 ---@type integer -- 0-based end of existing DIE bytes (inclusive)
|
||||||
local root_terminator = main_cu_end_excl - 1 ---@type integer -- 0-based position of the final 0 byte
|
local root_terminator = main_cu_end_excl - 1 ---@type integer -- 0-based position of the final 0 byte
|
||||||
|
|
||||||
return existing:sub(1, main_cu_start) -- crt CU
|
return existing:sub(1, main_cu_start) -- crt CU
|
||||||
.. new_unit_length_bytes -- patched unit_length (4 bytes)
|
.. new_unit_length_bytes -- patched unit_length (4 bytes)
|
||||||
@@ -2487,10 +2487,10 @@ local SECTION_WRITERS = { ---@type table<string, DwarfSectionPathWriter>
|
|||||||
--- @param basename string -- output file basename (e.g. "hello_gte")
|
--- @param basename string -- output file basename (e.g. "hello_gte")
|
||||||
--- @return table<string, string>[] -- bag: one <section>_bin -> path per row
|
--- @return table<string, string>[] -- bag: one <section>_bin -> path per row
|
||||||
local function write_sections(results, ctx, basename)
|
local function write_sections(results, ctx, basename)
|
||||||
local outputs = {} ---@type table<string, string>[] -- bag: one <section>_bin -> path per row
|
local outputs = {} ---@type table<string, string>[] -- bag: one <section>_bin -> path per row
|
||||||
for _, r in ipairs(results) do ---@type integer, DwarfSectionBlob
|
for _, r in ipairs(results) do ---@type integer, DwarfSectionBlob
|
||||||
local path = SECTION_WRITERS[r.name](ctx.out_root, basename) ---@type string
|
local path = SECTION_WRITERS[r.name](ctx.out_root, basename) ---@type string
|
||||||
local f = io.open(path, "wb") ---@type file*|nil
|
local f = io.open(path, "wb") ---@type file*|nil
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", path))
|
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", path))
|
||||||
else
|
else
|
||||||
@@ -2545,11 +2545,11 @@ function M.run(ctx)
|
|||||||
-- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation).
|
-- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation).
|
||||||
-- `corpus` is the sole canonical source projection.
|
-- `corpus` is the sole canonical source projection.
|
||||||
local corpus = (ctx.shared and ctx.shared.corpus) or {} ---@type Corpus
|
local corpus = (ctx.shared and ctx.shared.corpus) or {} ---@type Corpus
|
||||||
local registries = collect_per_source_registries(corpus) ---@type DwarfRegistries
|
local registries = collect_per_source_registries(corpus) ---@type DwarfRegistries
|
||||||
-- Read nm symbols (the ONLY disk-side input to the atom table) and join them against `corpus.atoms_by_name` + `atom.paths` for word rows + invocation ancestry.
|
-- Read nm symbols (the ONLY disk-side input to the atom table) and join them against `corpus.atoms_by_name` + `atom.paths` for word rows + invocation ancestry.
|
||||||
-- Disk source-map/provenance text is not consulted (those are diagnostic artifacts; semantic inputs are in memory).
|
-- Disk source-map/provenance text is not consulted (those are diagnostic artifacts; semantic inputs are in memory).
|
||||||
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path) ---@type table<string, NmAddr> -- bag
|
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path) ---@type table<string, NmAddr> -- bag
|
||||||
local atom_table = build_atom_table(corpus, addrs) ---@type DwarfAtom[]
|
local atom_table = build_atom_table(corpus, addrs) ---@type DwarfAtom[]
|
||||||
|
|
||||||
-- Detect rbind atoms + index Binds_* struct fields from the corpus.
|
-- Detect rbind atoms + index Binds_* struct fields from the corpus.
|
||||||
-- The merged registries are threaded through so parse_body_load_pairs resolves R_<reg> via register_alias_registry.
|
-- The merged registries are threaded through so parse_body_load_pairs resolves R_<reg> via register_alias_registry.
|
||||||
@@ -2573,13 +2573,13 @@ function M.run(ctx)
|
|||||||
|
|
||||||
-- Step 0: layout validation. Bail out safely if the .debug_info layout doesn't match what we expect (crt CU + DWARF5 main CU + final 0 byte).
|
-- Step 0: layout validation. Bail out safely if the .debug_info layout doesn't match what we expect (crt CU + DWARF5 main CU + final 0 byte).
|
||||||
-- A layout mismatch means the gcc emission changed; the safest response is to leave existing sections unchanged and emit no synthetic data, so the build's debug-info step never silently produces broken DWARF.
|
-- A layout mismatch means the gcc emission changed; the safest response is to leave existing sections unchanged and emit no synthetic data, so the build's debug-info step never silently produces broken DWARF.
|
||||||
local existing_info = existing_sections[".debug_info"] or "" ---@type string
|
local existing_info = existing_sections[".debug_info"] or "" ---@type string
|
||||||
local existing_abbrev = existing_sections[".debug_abbrev"] or "" ---@type string
|
local existing_abbrev = existing_sections[".debug_abbrev"] or "" ---@type string
|
||||||
local main_cu_start, main_cu_end_excl, main_abbrev_offset = find_main_cu_layout(existing_info) ---@type integer|nil, integer|nil, integer|nil
|
local main_cu_start, main_cu_end_excl, main_abbrev_offset = find_main_cu_layout(existing_info) ---@type integer|nil, integer|nil, integer|nil
|
||||||
if not main_cu_start then
|
if not main_cu_start then
|
||||||
io.stderr:write("[dwarf_injection] layout validation failed; writing existing sections unchanged\n")
|
io.stderr:write("[dwarf_injection] layout validation failed; writing existing sections unchanged\n")
|
||||||
local existing_str = existing_sections[".debug_str"] or "" ---@type string
|
local existing_str = existing_sections[".debug_str"] or "" ---@type string
|
||||||
local results_safe = { ---@type DwarfSectionBlob[]
|
local results_safe = { ---@type DwarfSectionBlob[]
|
||||||
{ name = "debug_info", data = existing_info },
|
{ name = "debug_info", data = existing_info },
|
||||||
{ name = "debug_abbrev", data = existing_abbrev },
|
{ name = "debug_abbrev", data = existing_abbrev },
|
||||||
{ name = "debug_str", data = existing_str },
|
{ name = "debug_str", data = existing_str },
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ local M = {} ---@type EmissionModelPass
|
|||||||
-- Bootstrap: load `duffle_paths.lua` via debug.getinfo so the module works standalone (run as `luajit passes/emission_model.lua`) and when require'd from the orchestrator.
|
-- Bootstrap: load `duffle_paths.lua` via debug.getinfo so the module works standalone (run as `luajit passes/emission_model.lua`) and when require'd from the orchestrator.
|
||||||
-- ─────────────────────────────────────────────────────────────────────────
|
-- ─────────────────────────────────────────────────────────────────────────
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- ─────────────────────────────────────────────────────────────────────────
|
-- ─────────────────────────────────────────────────────────────────────────
|
||||||
-- Helpers
|
-- Helpers
|
||||||
@@ -155,8 +155,8 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- The walker assigns line 2 to the body's first content line because line 1 is the trailing `\n` after `{`. Body-text line k therefore maps to `root_body_line + (k - 1)`.
|
-- The walker assigns line 2 to the body's first content line because line 1 is the trailing `\n` after `{`. Body-text line k therefore maps to `root_body_line + (k - 1)`.
|
||||||
-- `body_off - 1` points at the opening `{`, whose line index identifies the header line. `body_off` points after `{` and would shift every word row forward by one line.
|
-- `body_off - 1` points at the opening `{`, whose line index identifies the header line. `body_off` points after `{` and would shift every word row forward by one line.
|
||||||
local root_body_line = root_line_of(atom_record.body_off - 1) or atom_record.line or 0 ---@type integer
|
local root_body_line = root_line_of(atom_record.body_off - 1) or atom_record.line or 0 ---@type integer
|
||||||
local component_index = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
local component_index = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
||||||
local word_items = {} ---@type EmissionItem[]
|
local word_items = {} ---@type EmissionItem[]
|
||||||
|
|
||||||
for _, item in ipairs(projection.items) do ---@type integer, EmissionItem
|
for _, item in ipairs(projection.items) do ---@type integer, EmissionItem
|
||||||
if item.kind == "word" then word_items[#word_items + 1] = item end
|
if item.kind == "word" then word_items[#word_items + 1] = item end
|
||||||
@@ -173,7 +173,7 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- The innermost open invocation identifies which line index the walker used.
|
-- The innermost open invocation identifies which line index the walker used.
|
||||||
-- A component `line_of` makes `item.line` physical; the atom's `body_text` line index makes it body-relative.
|
-- A component `line_of` makes `item.line` physical; the atom's `body_text` line index makes it body-relative.
|
||||||
if ids and #ids > 0 then
|
if ids and #ids > 0 then
|
||||||
local inner_id = ids[#ids] ---@type integer
|
local inner_id = ids[#ids] ---@type integer
|
||||||
local inner_inv = inner_id and projection.invocations[inner_id] ---@type InvocationRecord|nil
|
local inner_inv = inner_id and projection.invocations[inner_id] ---@type InvocationRecord|nil
|
||||||
if inner_inv then
|
if inner_inv then
|
||||||
local component = component_index[inner_inv.component_name] ---@type ComponentBodyEntry|nil
|
local component = component_index[inner_inv.component_name] ---@type ComponentBodyEntry|nil
|
||||||
@@ -191,7 +191,7 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- Stamp the root source path onto invocation records whose `call_path` the walker left empty.
|
-- Stamp the root source path onto invocation records whose `call_path` the walker left empty.
|
||||||
-- The walker passes `body_entry.source` to `emit_invoke_begin`; `M.project_emission` creates the root `body_entry` with source `""`, leaving its `call_path` empty.
|
-- The walker passes `body_entry.source` to `emit_invoke_begin`; `M.project_emission` creates the root `body_entry` with source `""`, leaving its `call_path` empty.
|
||||||
-- This stamp gives every invocation a physical `call_path` matching `passes/atoms_source_map.lua`'s in-memory provenance projection.
|
-- This stamp gives every invocation a physical `call_path` matching `passes/atoms_source_map.lua`'s in-memory provenance projection.
|
||||||
local root_path = src.path or "" ---@type string
|
local root_path = src.path or "" ---@type string
|
||||||
for _, inv in ipairs(projection.invocations) do ---@type integer, InvocationRecord
|
for _, inv in ipairs(projection.invocations) do ---@type integer, InvocationRecord
|
||||||
if inv.call_path == nil or inv.call_path == "" then
|
if inv.call_path == nil or inv.call_path == "" then
|
||||||
inv.call_path = root_path
|
inv.call_path = root_path
|
||||||
@@ -212,9 +212,9 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- Component words already carry physical `item.line` values from the walker's COMPONENT line index, so `body_line_for` returns them unchanged.
|
-- Component words already carry physical `item.line` values from the walker's COMPONENT line index, so `body_line_for` returns them unchanged.
|
||||||
for _, inv in ipairs(projection.invocations) do ---@type integer, InvocationRecord
|
for _, inv in ipairs(projection.invocations) do ---@type integer, InvocationRecord
|
||||||
local sw = inv.start_word ---@type integer
|
local sw = inv.start_word ---@type integer
|
||||||
local ew = inv.end_word ---@type integer
|
local ew = inv.end_word ---@type integer
|
||||||
local bls = {} ---@type integer[]
|
local bls = {} ---@type integer[]
|
||||||
for i = sw, ew do ---@type integer
|
for i = sw, ew do ---@type integer
|
||||||
local it = projection.items and projection.items[i] ---@type EmissionItem|nil
|
local it = projection.items and projection.items[i] ---@type EmissionItem|nil
|
||||||
if it and it.kind == "word" then
|
if it and it.kind == "word" then
|
||||||
local fake_event = { invocation_ids = { inv.id } } ---@type WordEvent
|
local fake_event = { invocation_ids = { inv.id } } ---@type WordEvent
|
||||||
@@ -233,8 +233,8 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
item.line = body_line
|
item.line = body_line
|
||||||
we.body_line = body_line
|
we.body_line = body_line
|
||||||
|
|
||||||
local call_line = body_line ---@type integer
|
local call_line = body_line ---@type integer
|
||||||
local outer_id = we.outermost_invocation_id or 0 ---@type integer
|
local outer_id = we.outermost_invocation_id or 0 ---@type integer
|
||||||
local outer_inv = projection.invocations[outer_id] ---@type InvocationRecord|nil
|
local outer_inv = projection.invocations[outer_id] ---@type InvocationRecord|nil
|
||||||
if outer_inv then
|
if outer_inv then
|
||||||
-- `outer_inv.call_line` is physical after the conversion loop above, so use it directly.
|
-- `outer_inv.call_line` is physical after the conversion loop above, so use it directly.
|
||||||
@@ -255,10 +255,10 @@ end
|
|||||||
--- @param corpus Corpus
|
--- @param corpus Corpus
|
||||||
--- @return EmissionProjection
|
--- @return EmissionProjection
|
||||||
local function project_atom(atom_record, src, corpus)
|
local function project_atom(atom_record, src, corpus)
|
||||||
local body = atom_record.body or "" ---@type string
|
local body = atom_record.body or "" ---@type string
|
||||||
local wc = corpus.word_counts or {} ---@type WordCounts
|
local wc = corpus.word_counts or {} ---@type WordCounts
|
||||||
local cbi = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
local cbi = corpus.component_body_index or {} ---@type table<string, ComponentBodyEntry>
|
||||||
local schema = nil ---@type RegUseSchema|nil
|
local schema = nil ---@type RegUseSchema|nil
|
||||||
if atom_record.reg_use_schema_name then
|
if atom_record.reg_use_schema_name then
|
||||||
schema = corpus.reg_use_schemas and corpus.reg_use_schemas[atom_record.reg_use_schema_name]
|
schema = corpus.reg_use_schemas and corpus.reg_use_schemas[atom_record.reg_use_schema_name]
|
||||||
end
|
end
|
||||||
@@ -322,7 +322,7 @@ function M.run(ctx)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local proj = project_atom(atom, src, corpus) ---@type EmissionProjection
|
local proj = project_atom(atom, src, corpus) ---@type EmissionProjection
|
||||||
for _, e in ipairs(proj.errors) do ---@type integer, EmitError
|
for _, e in ipairs(proj.errors) do ---@type integer, EmitError
|
||||||
-- Preserve `kind` (cycle / count_mismatch / unbalanced) so readers dispatch on the diagnostic class and leave the message string as display text.
|
-- Preserve `kind` (cycle / count_mismatch / unbalanced) so readers dispatch on the diagnostic class and leave the message string as display text.
|
||||||
errors[#errors + 1] = {
|
errors[#errors + 1] = {
|
||||||
kind = e.kind,
|
kind = e.kind,
|
||||||
@@ -344,7 +344,7 @@ function M.run(ctx)
|
|||||||
-- Recognized kinds (atom | atom_proc | raw_atom | comp_bare | comp_proc) each receive the atom.paths projection via duffle.project_emission.
|
-- Recognized kinds (atom | atom_proc | raw_atom | comp_bare | comp_proc) each receive the atom.paths projection via duffle.project_emission.
|
||||||
-- Components are macros inlined into atom bodies; focused tests and isolated component analyses consume atom.paths directly.
|
-- Components are macros inlined into atom bodies; focused tests and isolated component analyses consume atom.paths directly.
|
||||||
for _, src in ipairs(corpus.source_order) do ---@type integer, SourceFile
|
for _, src in ipairs(corpus.source_order) do ---@type integer, SourceFile
|
||||||
local scan = src.scan or {} ---@type SourceScan
|
local scan = src.scan or {} ---@type SourceScan
|
||||||
for _, atom in ipairs(scan.atoms or {}) do ---@type integer, AtomEntry
|
for _, atom in ipairs(scan.atoms or {}) do ---@type integer, AtomEntry
|
||||||
process_atom(atom, src)
|
process_atom(atom, src)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Constants
|
-- Constants
|
||||||
@@ -29,7 +29,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type
|
|||||||
|
|
||||||
-- Offset macro/enum naming prefixes (the emitted header uses these).
|
-- Offset macro/enum naming prefixes (the emitted header uses these).
|
||||||
local OFFSET_MACRO_PREFIX = "_atom_offset_" ---@type string
|
local OFFSET_MACRO_PREFIX = "_atom_offset_" ---@type string
|
||||||
local OFFSET_ENUM_PREFIX = "atom_offset_" ---@type string
|
local OFFSET_ENUM_PREFIX = "atom_offset_" ---@type string
|
||||||
|
|
||||||
-- Column width for the `#define _atom_offset_F_T = N` alignment.
|
-- Column width for the `#define _atom_offset_F_T = N` alignment.
|
||||||
local OFFSET_MACRO_COL = 44 ---@type integer
|
local OFFSET_MACRO_COL = 44 ---@type integer
|
||||||
@@ -114,7 +114,7 @@ local MARKER_PROJECTORS = { ---@type table<string, fun(state: MarkerProjectState
|
|||||||
--- @return OffsetBranch[]
|
--- @return OffsetBranch[]
|
||||||
local function project_markers(markers)
|
local function project_markers(markers)
|
||||||
local state = { labels = {}, branches = {} } ---@type MarkerProjectState
|
local state = { labels = {}, branches = {} } ---@type MarkerProjectState
|
||||||
for _, marker in ipairs(markers or {}) do ---@type integer, EmissionMarker
|
for _, marker in ipairs(markers or {}) do ---@type integer, EmissionMarker
|
||||||
local project = MARKER_PROJECTORS[marker.kind] ---@type (fun(state: MarkerProjectState, marker: EmissionMarker): nil)|nil
|
local project = MARKER_PROJECTORS[marker.kind] ---@type (fun(state: MarkerProjectState, marker: EmissionMarker): nil)|nil
|
||||||
if project then project(state, marker) end
|
if project then project(state, marker) end
|
||||||
end
|
end
|
||||||
@@ -140,7 +140,7 @@ end
|
|||||||
--- @param errors PassFinding[]
|
--- @param errors PassFinding[]
|
||||||
--- @return BranchOffset[]
|
--- @return BranchOffset[]
|
||||||
local function compute_offsets(labels, branches, errors)
|
local function compute_offsets(labels, branches, errors)
|
||||||
local results = {} ---@type BranchOffset[]
|
local results = {} ---@type BranchOffset[]
|
||||||
for _, br in ipairs(branches) do ---@type integer, OffsetBranch
|
for _, br in ipairs(branches) do ---@type integer, OffsetBranch
|
||||||
local target = labels[br.target] ---@type integer|nil
|
local target = labels[br.target] ---@type integer|nil
|
||||||
if not target then
|
if not target then
|
||||||
@@ -205,7 +205,7 @@ local function emit_atom_offsets(add, atom)
|
|||||||
if #atom.offsets == 0 then return end
|
if #atom.offsets == 0 then return end
|
||||||
add("// --- atom: " .. atom.name .. " (" .. atom.total_words .. " words) ---")
|
add("// --- atom: " .. atom.name .. " (" .. atom.total_words .. " words) ---")
|
||||||
add("")
|
add("")
|
||||||
local consts = {} ---@type OffsetConst[]
|
local consts = {} ---@type OffsetConst[]
|
||||||
for _, r in ipairs(atom.offsets) do ---@type integer, BranchOffset
|
for _, r in ipairs(atom.offsets) do ---@type integer, BranchOffset
|
||||||
consts[#consts + 1] = make_offset_const(r)
|
consts[#consts + 1] = make_offset_const(r)
|
||||||
end
|
end
|
||||||
@@ -278,8 +278,8 @@ local function process_directory(ctx, dir, sources, errors)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||||
local scan = src.scan or {} ---@type SourceScan
|
local scan = src.scan or {} ---@type SourceScan
|
||||||
for _, atom in ipairs(scan.atoms or {}) do append_atom(atom) end ---@type integer, AtomEntry
|
for _, atom in ipairs(scan.atoms or {}) do append_atom(atom) end ---@type integer, AtomEntry
|
||||||
for _, atom in ipairs(scan.raw_atoms or {}) do append_atom(atom) end ---@type integer, AtomEntry
|
for _, atom in ipairs(scan.raw_atoms or {}) do append_atom(atom) end ---@type integer, AtomEntry
|
||||||
end
|
end
|
||||||
if #atoms_data == 0 then return nil end
|
if #atoms_data == 0 then return nil end
|
||||||
@@ -310,7 +310,7 @@ function M.run(ctx)
|
|||||||
|
|
||||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
||||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order) ---@type table<string, SourceFile[]>
|
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order) ---@type table<string, SourceFile[]>
|
||||||
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
||||||
local out_path = process_directory(ctx, dir, sources, errors) ---@type string|nil
|
local out_path = process_directory(ctx, dir, sources, errors) ---@type string|nil
|
||||||
if out_path then
|
if out_path then
|
||||||
outputs[#outputs + 1] = { offsets_h = out_path }
|
outputs[#outputs + 1] = { offsets_h = out_path }
|
||||||
|
|||||||
+69
-69
@@ -19,7 +19,7 @@
|
|||||||
-- Bootstrap: Load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
-- Bootstrap: Load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `<module>.atoms.md` without re-walking source tokens).
|
-- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `<module>.atoms.md` without re-walking source tokens).
|
||||||
-- The pass itself emits no per-source files anymore; we only consume the two pure renderers here.
|
-- The pass itself emits no per-source files anymore; we only consume the two pure renderers here.
|
||||||
@@ -32,13 +32,13 @@ local atoms_source_map = dofile(_bootstrap_dir .. "atoms_source_map.lua") ---@ty
|
|||||||
|
|
||||||
-- Section separators used in the rendered text reports.
|
-- Section separators used in the rendered text reports.
|
||||||
-- The thin rules are hand-tuned to align with the per-section content width; do not change without also checking the section renderers below.
|
-- The thin rules are hand-tuned to align with the per-section content width; do not change without also checking the section renderers below.
|
||||||
local RULE_THICK = "========================================================" ---@type string
|
local RULE_THICK = "========================================================" ---@type string
|
||||||
local SECTION_HEADER_ATOMS = "── Atoms ────────────────────────────────────────────────" ---@type string
|
local SECTION_HEADER_ATOMS = "── Atoms ────────────────────────────────────────────────" ---@type string
|
||||||
local SECTION_HEADER_ANNOTS = "── Annotations ──────────────────────────────────────────" ---@type string
|
local SECTION_HEADER_ANNOTS = "── Annotations ──────────────────────────────────────────" ---@type string
|
||||||
local SECTION_HEADER_BINDS = "── Binds_* structs ──────────────────────────────────────" ---@type string
|
local SECTION_HEADER_BINDS = "── Binds_* structs ──────────────────────────────────────" ---@type string
|
||||||
local SECTION_HEADER_MACROS = "── Macro word-count declarations ─────────────────────────" ---@type string
|
local SECTION_HEADER_MACROS = "── Macro word-count declarations ─────────────────────────" ---@type string
|
||||||
local SECTION_HEADER_ERRORS = "── Errors ──────────────────────────────────────────────" ---@type string
|
local SECTION_HEADER_ERRORS = "── Errors ──────────────────────────────────────────────" ---@type string
|
||||||
local SECTION_HEADER_WARNINGS = "── Warnings ────────────────────────────────────────────" ---@type string
|
local SECTION_HEADER_WARNINGS = "── Warnings ────────────────────────────────────────────" ---@type string
|
||||||
|
|
||||||
-- Lua pattern that captures the basename (last path segment) of a forward- or back-slash separated path.
|
-- Lua pattern that captures the basename (last path segment) of a forward- or back-slash separated path.
|
||||||
local BASENAME_PATTERN = "([^/\\]+)$" ---@type string
|
local BASENAME_PATTERN = "([^/\\]+)$" ---@type string
|
||||||
@@ -240,7 +240,7 @@ local function render_project_summary(all_results)
|
|||||||
"|--------|-------|--------|-------|--------|----------|--------|----------|------|",
|
"|--------|-------|--------|-------|--------|----------|--------|----------|------|",
|
||||||
}
|
}
|
||||||
local totals = { atoms = 0, annots = 0, binds = 0, macros = 0, findings = 0, errors = 0, warnings = 0, info = 0 } ---@type ProjectSummaryTotals
|
local totals = { atoms = 0, annots = 0, binds = 0, macros = 0, findings = 0, errors = 0, warnings = 0, info = 0 } ---@type ProjectSummaryTotals
|
||||||
for _, e in ipairs(all_results) do ---@type integer, ProjectSummaryRow
|
for _, e in ipairs(all_results) do ---@type integer, ProjectSummaryRow
|
||||||
lines[#lines + 1] = string.format("| %s | %d | %d | %d | %d | %d | %d | %d | %d |"
|
lines[#lines + 1] = string.format("| %s | %d | %d | %d | %d | %d | %d | %d | %d |"
|
||||||
, e.module, e.atoms, e.annots, e.binds, e.macros, e.findings, e.errors, e.warnings, e.info)
|
, e.module, e.atoms, e.annots, e.binds, e.macros, e.findings, e.errors, e.warnings, e.info)
|
||||||
totals.atoms = totals.atoms + e.atoms
|
totals.atoms = totals.atoms + e.atoms
|
||||||
@@ -266,7 +266,7 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function render_module_atoms_md(dir, dir_sources, wc)
|
local function render_module_atoms_md(dir, dir_sources, wc)
|
||||||
local dir_basename = source_basename(dir) ---@type string
|
local dir_basename = source_basename(dir) ---@type string
|
||||||
local lines = { ---@type string[]
|
local lines = { ---@type string[]
|
||||||
"# " .. dir_basename .. " — atoms (verbose source map)",
|
"# " .. dir_basename .. " — atoms (verbose source map)",
|
||||||
"> Per-word call-site + provenance. Auto-generated.",
|
"> Per-word call-site + provenance. Auto-generated.",
|
||||||
"",
|
"",
|
||||||
@@ -276,7 +276,7 @@ local function render_module_atoms_md(dir, dir_sources, wc)
|
|||||||
lines[#lines + 1] = "## " .. src_name
|
lines[#lines + 1] = "## " .. src_name
|
||||||
lines[#lines + 1] = ""
|
lines[#lines + 1] = ""
|
||||||
-- For each atom with a projection, render its sourcemap + provenance.
|
-- For each atom with a projection, render its sourcemap + provenance.
|
||||||
local atoms_list = {} ---@type AtomEntry[]
|
local atoms_list = {} ---@type AtomEntry[]
|
||||||
for _, atom in ipairs((src.scan or {}).atoms or {}) do ---@type integer, AtomEntry
|
for _, atom in ipairs((src.scan or {}).atoms or {}) do ---@type integer, AtomEntry
|
||||||
if atom.paths then atoms_list[#atoms_list + 1] = atom end
|
if atom.paths then atoms_list[#atoms_list + 1] = atom end
|
||||||
end
|
end
|
||||||
@@ -290,7 +290,7 @@ local function render_module_atoms_md(dir, dir_sources, wc)
|
|||||||
-- Per-source forward-slash path (same one `emit_atom_stanza` / `emit_provenance_stanza` would derive;
|
-- Per-source forward-slash path (same one `emit_atom_stanza` / `emit_provenance_stanza` would derive;
|
||||||
-- computed once per `## <source>` heading and reused by each atom's `WORD N CALL ...` field).
|
-- computed once per `## <source>` heading and reused by each atom's `WORD N CALL ...` field).
|
||||||
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
local rel_path = src.path:gsub("\\\\", "/") ---@type string
|
||||||
for _, atom in ipairs(atoms_list) do ---@type integer, AtomEntry
|
for _, atom in ipairs(atoms_list) do ---@type integer, AtomEntry
|
||||||
lines[#lines + 1] = string.format(
|
lines[#lines + 1] = string.format(
|
||||||
"### atom: %s (line %d, %d words)",
|
"### atom: %s (line %d, %d words)",
|
||||||
atom.name, atom.line or 0, #((atom.paths or {}).word_events or {}))
|
atom.name, atom.line or 0, #((atom.paths or {}).word_events or {}))
|
||||||
@@ -325,7 +325,7 @@ end
|
|||||||
--- @return KindCounts
|
--- @return KindCounts
|
||||||
local function count_kinds(decls)
|
local function count_kinds(decls)
|
||||||
local n = { atom = 0, atom_proc = 0, comp_bare = 0, comp_proc = 0 } ---@type KindCounts
|
local n = { atom = 0, atom_proc = 0, comp_bare = 0, comp_proc = 0 } ---@type KindCounts
|
||||||
for _, a in ipairs(decls or {}) do ---@type integer, AtomEntry
|
for _, a in ipairs(decls or {}) do ---@type integer, AtomEntry
|
||||||
if n[a.kind] ~= nil then n[a.kind] = n[a.kind] + 1 end
|
if n[a.kind] ~= nil then n[a.kind] = n[a.kind] + 1 end
|
||||||
end
|
end
|
||||||
return n
|
return n
|
||||||
@@ -341,7 +341,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return table<string, boolean>
|
--- @return table<string, boolean>
|
||||||
local function decl_names(view)
|
local function decl_names(view)
|
||||||
local names = {} ---@type table<string, boolean> -- bag: atom name -> true
|
local names = {} ---@type table<string, boolean> -- bag: atom name -> true
|
||||||
for _, a in ipairs(view.decls or {}) do ---@type integer, AtomEntry
|
for _, a in ipairs(view.decls or {}) do ---@type integer, AtomEntry
|
||||||
if a.name then names[a.name] = true end
|
if a.name then names[a.name] = true end
|
||||||
end
|
end
|
||||||
@@ -353,7 +353,7 @@ end
|
|||||||
--- @return boolean
|
--- @return boolean
|
||||||
local function path_in_module(path, view)
|
local function path_in_module(path, view)
|
||||||
if type(path) ~= "string" or path == "" then return false end
|
if type(path) ~= "string" or path == "" then return false end
|
||||||
local norm = path:gsub("\\", "/") ---@type string
|
local norm = path:gsub("\\", "/") ---@type string
|
||||||
local dir = (view.dir or ""):gsub("\\", "/") ---@type string
|
local dir = (view.dir or ""):gsub("\\", "/") ---@type string
|
||||||
if dir ~= "" and (norm == dir or norm:sub(1, #dir + 1) == dir .. "/") then
|
if dir ~= "" and (norm == dir or norm:sub(1, #dir + 1) == dir .. "/") then
|
||||||
return true
|
return true
|
||||||
@@ -369,17 +369,17 @@ end
|
|||||||
--- @param corpus Corpus
|
--- @param corpus Corpus
|
||||||
--- @return ModuleView
|
--- @return ModuleView
|
||||||
local function build_module_view(dir, dir_sources, corpus)
|
local function build_module_view(dir, dir_sources, corpus)
|
||||||
local decls = {} ---@type AtomEntry[]
|
local decls = {} ---@type AtomEntry[]
|
||||||
for _, src in ipairs(dir_sources or {}) do ---@type integer, SourceFile
|
for _, src in ipairs(dir_sources or {}) do ---@type integer, SourceFile
|
||||||
for _, a in ipairs((src.scan and src.scan.atoms) or {}) do ---@type integer, AtomEntry
|
for _, a in ipairs((src.scan and src.scan.atoms) or {}) do ---@type integer, AtomEntry
|
||||||
if not a.source_path then a.source_path = src.path end
|
if not a.source_path then a.source_path = src.path end
|
||||||
decls[#decls + 1] = a
|
decls[#decls + 1] = a
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local dir_basename = source_basename(dir) ---@type string
|
local dir_basename = source_basename(dir) ---@type string
|
||||||
local sa = (corpus.static_analysis_results or {})[dir_basename] or {} ---@type AtomAnalysis
|
local sa = (corpus.static_analysis_results or {})[dir_basename] or {} ---@type AtomAnalysis
|
||||||
local schemas = {} ---@type RegUseSchema[]
|
local schemas = {} ---@type RegUseSchema[]
|
||||||
for name, schema in pairs(corpus.reg_use_schemas or {}) do ---@type string, RegUseSchema
|
for name, schema in pairs(corpus.reg_use_schemas or {}) do ---@type string, RegUseSchema
|
||||||
for _, a in ipairs(decls) do ---@type integer, AtomEntry
|
for _, a in ipairs(decls) do ---@type integer, AtomEntry
|
||||||
if a.reg_use_schema_name == name then
|
if a.reg_use_schema_name == name then
|
||||||
schemas[#schemas + 1] = schema
|
schemas[#schemas + 1] = schema
|
||||||
@@ -424,11 +424,11 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_components(add, view)
|
local function render_section_components(add, view)
|
||||||
local rows = {} ---@type ComponentReportRow[]
|
local rows = {} ---@type ComponentReportRow[]
|
||||||
local index = (view.corpus and view.corpus.component_body_index) or {} ---@type table<string, ComponentBodyEntry>
|
local index = (view.corpus and view.corpus.component_body_index) or {} ---@type table<string, ComponentBodyEntry>
|
||||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||||
if a.kind == "comp_bare" or a.kind == "comp_proc" then
|
if a.kind == "comp_bare" or a.kind == "comp_proc" then
|
||||||
local idx = index[a.name] or {} ---@type ComponentBodyEntry
|
local idx = index[a.name] or {} ---@type ComponentBodyEntry
|
||||||
local args = idx.arg_names or {} ---@type string[]
|
local args = idx.arg_names or {} ---@type string[]
|
||||||
rows[#rows + 1] = {
|
rows[#rows + 1] = {
|
||||||
name = a.name,
|
name = a.name,
|
||||||
@@ -453,13 +453,13 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_reguse(add, view)
|
local function render_section_reguse(add, view)
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
for _, schema in ipairs(view.schemas or {}) do ---@type integer, RegUseSchema
|
for _, schema in ipairs(view.schemas or {}) do ---@type integer, RegUseSchema
|
||||||
wrote = true
|
wrote = true
|
||||||
add(string.format("### %s", schema.name or "?"))
|
add(string.format("### %s", schema.name or "?"))
|
||||||
for _, slot in ipairs(schema.slots or {}) do ---@type integer, RegUseSlot
|
for _, slot in ipairs(schema.slots or {}) do ---@type integer, RegUseSlot
|
||||||
local aliases = table.concat(slot.aliases or { slot.name }, ", ") ---@type string
|
local aliases = table.concat(slot.aliases or { slot.name }, ", ") ---@type string
|
||||||
local ro = slot.readonly and " readonly" or "" ---@type string
|
local ro = slot.readonly and " readonly" or "" ---@type string
|
||||||
add(string.format("- slot `%s` aliases %s%s", slot.name, aliases, ro))
|
add(string.format("- slot `%s` aliases %s%s", slot.name, aliases, ro))
|
||||||
end
|
end
|
||||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||||
@@ -469,11 +469,11 @@ local function render_section_reguse(add, view)
|
|||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
end
|
||||||
local bound = {} ---@type table<string, boolean> -- bag: schema name -> true
|
local bound = {} ---@type table<string, boolean> -- bag: schema name -> true
|
||||||
for _, schema in ipairs(view.schemas or {}) do ---@type integer, RegUseSchema
|
for _, schema in ipairs(view.schemas or {}) do ---@type integer, RegUseSchema
|
||||||
if schema.name then bound[schema.name] = true end
|
if schema.name then bound[schema.name] = true end
|
||||||
end
|
end
|
||||||
local errors = {} ---@type RegUseError[]
|
local errors = {} ---@type RegUseError[]
|
||||||
for _, err in ipairs((view.corpus and view.corpus.reg_use_errors) or {}) do ---@type integer, RegUseError
|
for _, err in ipairs((view.corpus and view.corpus.reg_use_errors) or {}) do ---@type integer, RegUseError
|
||||||
if bound[err.schema_name] or path_in_module(err.source_file, view) then
|
if bound[err.schema_name] or path_in_module(err.source_file, view) then
|
||||||
errors[#errors + 1] = err
|
errors[#errors + 1] = err
|
||||||
@@ -494,7 +494,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_annotations(add, view)
|
local function render_section_annotations(add, view)
|
||||||
local rows = {} ---@type AnnotReportRow[]
|
local rows = {} ---@type AnnotReportRow[]
|
||||||
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
||||||
for _, info in ipairs((src.scan and src.scan.atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
for _, info in ipairs((src.scan and src.scan.atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
||||||
rows[#rows + 1] = {
|
rows[#rows + 1] = {
|
||||||
@@ -522,7 +522,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_component_annotations(add, view)
|
local function render_section_component_annotations(add, view)
|
||||||
local rows = {} ---@type CompAnnotReportRow[]
|
local rows = {} ---@type CompAnnotReportRow[]
|
||||||
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
||||||
for _, info in ipairs((src.scan and src.scan.component_atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
for _, info in ipairs((src.scan and src.scan.component_atom_infos) or {}) do ---@type integer, AtomInfoEntry
|
||||||
rows[#rows + 1] = {
|
rows[#rows + 1] = {
|
||||||
@@ -548,7 +548,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_binds(add, view)
|
local function render_section_binds(add, view)
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
||||||
for _, b in ipairs((src.scan and src.scan.binds) or {}) do ---@type integer, BindsEntry
|
for _, b in ipairs((src.scan and src.scan.binds) or {}) do ---@type integer, BindsEntry
|
||||||
wrote = true
|
wrote = true
|
||||||
@@ -567,11 +567,11 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_phases(add, view)
|
local function render_section_phases(add, view)
|
||||||
local corpus = view.corpus or {} ---@type Corpus
|
local corpus = view.corpus or {} ---@type Corpus
|
||||||
local names = decl_names(view) ---@type table<string, boolean>
|
local names = decl_names(view) ---@type table<string, boolean>
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
for phase, entry in pairs(corpus.atom_phases or {}) do ---@type string, AtomPhaseGroup
|
for phase, entry in pairs(corpus.atom_phases or {}) do ---@type string, AtomPhaseGroup
|
||||||
local here = {} ---@type string[]
|
local here = {} ---@type string[]
|
||||||
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, string
|
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, string
|
||||||
if names[atom_name] then here[#here + 1] = atom_name end
|
if names[atom_name] then here[#here + 1] = atom_name end
|
||||||
end
|
end
|
||||||
@@ -600,8 +600,8 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_aliases(add, view)
|
local function render_section_aliases(add, view)
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
local seen = {} ---@type table<string, AliasEntry>
|
local seen = {} ---@type table<string, AliasEntry>
|
||||||
for _, src in ipairs(view.sources or {}) do ---@type integer, SourceFile
|
for _, src in ipairs(view.sources or {}) do ---@type integer, SourceFile
|
||||||
for name, entry in pairs((src.scan and src.scan.register_alias_registry) or {}) do ---@type string, AliasEntry
|
for name, entry in pairs((src.scan and src.scan.register_alias_registry) or {}) do ---@type string, AliasEntry
|
||||||
if not seen[name] then
|
if not seen[name] then
|
||||||
@@ -625,19 +625,19 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_autoreg(add, view)
|
local function render_section_autoreg(add, view)
|
||||||
local allowed = decl_names(view) ---@type table<string, boolean>
|
local allowed = decl_names(view) ---@type table<string, boolean>
|
||||||
for phase, entry in pairs((view.corpus and view.corpus.atom_phases) or {}) do ---@type string, AtomPhaseGroup
|
for phase, entry in pairs((view.corpus and view.corpus.atom_phases) or {}) do ---@type string, AtomPhaseGroup
|
||||||
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, string
|
for _, atom_name in ipairs(entry.atoms or {}) do ---@type integer, string
|
||||||
if allowed[atom_name] then allowed[phase] = true end
|
if allowed[atom_name] then allowed[phase] = true end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
local seen = {} ---@type table<string, boolean> -- bag: label\\0scope -> already dumped
|
local seen = {} ---@type table<string, boolean> -- bag: label\\0scope -> already dumped
|
||||||
--- @param label string
|
--- @param label string
|
||||||
--- @param table_map table<string, GprAllocMap>|nil
|
--- @param table_map table<string, GprAllocMap>|nil
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function dump(label, table_map)
|
local function dump(label, table_map)
|
||||||
local scopes = {} ---@type string[]
|
local scopes = {} ---@type string[]
|
||||||
for scope in pairs(table_map or {}) do ---@type string
|
for scope in pairs(table_map or {}) do ---@type string
|
||||||
if allowed[scope] and not seen[label .. "\0" .. scope] then
|
if allowed[scope] and not seen[label .. "\0" .. scope] then
|
||||||
scopes[#scopes + 1] = scope
|
scopes[#scopes + 1] = scope
|
||||||
@@ -647,7 +647,7 @@ local function render_section_autoreg(add, view)
|
|||||||
for _, scope in ipairs(scopes) do ---@type integer, string
|
for _, scope in ipairs(scopes) do ---@type integer, string
|
||||||
seen[label .. "\0" .. scope] = true
|
seen[label .. "\0" .. scope] = true
|
||||||
wrote = true
|
wrote = true
|
||||||
local syms = {} ---@type string[]
|
local syms = {} ---@type string[]
|
||||||
for sym, gpr in pairs(table_map[scope] or {}) do ---@type string, string
|
for sym, gpr in pairs(table_map[scope] or {}) do ---@type string, string
|
||||||
if type(gpr) == "string" and gpr ~= sym then
|
if type(gpr) == "string" and gpr ~= sym then
|
||||||
syms[#syms + 1] = string.format("%s → %s", sym, gpr)
|
syms[#syms + 1] = string.format("%s → %s", sym, gpr)
|
||||||
@@ -674,9 +674,9 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_collisions(add, view)
|
local function render_section_collisions(add, view)
|
||||||
local rows = {} ---@type CorpusCollision[]
|
local rows = {} ---@type CorpusCollision[]
|
||||||
for _, c in ipairs((view.corpus and view.corpus.collisions) or {}) do ---@type integer, CorpusCollision
|
for _, c in ipairs((view.corpus and view.corpus.collisions) or {}) do ---@type integer, CorpusCollision
|
||||||
local first = c.first_site or {} ---@type CollisionSite
|
local first = c.first_site or {} ---@type CollisionSite
|
||||||
local other = c.conflicting_site or {} ---@type CollisionSite
|
local other = c.conflicting_site or {} ---@type CollisionSite
|
||||||
if path_in_module(first.path, view) or path_in_module(other.path, view) then
|
if path_in_module(first.path, view) or path_in_module(other.path, view) then
|
||||||
rows[#rows + 1] = c
|
rows[#rows + 1] = c
|
||||||
@@ -684,7 +684,7 @@ local function render_section_collisions(add, view)
|
|||||||
end
|
end
|
||||||
if #rows == 0 then add("_(none)_"); add(""); return end
|
if #rows == 0 then add("_(none)_"); add(""); return end
|
||||||
for _, c in ipairs(rows) do ---@type integer, CorpusCollision
|
for _, c in ipairs(rows) do ---@type integer, CorpusCollision
|
||||||
local first = c.first_site or {} ---@type CollisionSite
|
local first = c.first_site or {} ---@type CollisionSite
|
||||||
local other = c.conflicting_site or {} ---@type CollisionSite
|
local other = c.conflicting_site or {} ---@type CollisionSite
|
||||||
add(string.format("- `%s` `%s` first %s:%s conflict %s:%s",
|
add(string.format("- `%s` `%s` first %s:%s conflict %s:%s",
|
||||||
c.kind or "?", c.name or "?",
|
c.kind or "?", c.name or "?",
|
||||||
@@ -698,7 +698,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_findings(add, view)
|
local function render_section_findings(add, view)
|
||||||
local by_atom = {} ---@type table<string, CheckFinding[]>
|
local by_atom = {} ---@type table<string, CheckFinding[]>
|
||||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||||
local key = f.atom or "?" ---@type string
|
local key = f.atom or "?" ---@type string
|
||||||
by_atom[key] = by_atom[key] or {}
|
by_atom[key] = by_atom[key] or {}
|
||||||
@@ -712,7 +712,7 @@ local function render_section_findings(add, view)
|
|||||||
local function emit(name, fs)
|
local function emit(name, fs)
|
||||||
add("### " .. name)
|
add("### " .. name)
|
||||||
for _, f in ipairs(fs) do ---@type integer, CheckFinding
|
for _, f in ipairs(fs) do ---@type integer, CheckFinding
|
||||||
local msg = f.msg or "" ---@type string
|
local msg = f.msg or "" ---@type string
|
||||||
local slot = slot_suffix(f.gpr_key or f.producer_destination) ---@type string|nil
|
local slot = slot_suffix(f.gpr_key or f.producer_destination) ---@type string|nil
|
||||||
if slot and not msg:find("(slot ", 1, true) then
|
if slot and not msg:find("(slot ", 1, true) then
|
||||||
msg = msg .. " (slot " .. slot .. ")"
|
msg = msg .. " (slot " .. slot .. ")"
|
||||||
@@ -727,7 +727,7 @@ local function render_section_findings(add, view)
|
|||||||
emit(a.name, by_atom[a.name])
|
emit(a.name, by_atom[a.name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local leftovers = {} ---@type string[]
|
local leftovers = {} ---@type string[]
|
||||||
for name in pairs(by_atom) do ---@type string
|
for name in pairs(by_atom) do ---@type string
|
||||||
if not seen[name] then leftovers[#leftovers + 1] = name end
|
if not seen[name] then leftovers[#leftovers + 1] = name end
|
||||||
end
|
end
|
||||||
@@ -739,7 +739,7 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_relations(add, view)
|
local function render_section_relations(add, view)
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||||
local rels = (a.paths and a.paths.relations) or {} ---@type AtomRelation[]
|
local rels = (a.paths and a.paths.relations) or {} ---@type AtomRelation[]
|
||||||
if #rels > 0 then
|
if #rels > 0 then
|
||||||
@@ -747,8 +747,8 @@ local function render_section_relations(add, view)
|
|||||||
add("### " .. a.name)
|
add("### " .. a.name)
|
||||||
for _, rel in ipairs(rels) do ---@type integer, AtomRelation
|
for _, rel in ipairs(rels) do ---@type integer, AtomRelation
|
||||||
local dest = rel.destination or rel.producer_destination or "—" ---@type string
|
local dest = rel.destination or rel.producer_destination or "—" ---@type string
|
||||||
local slot = slot_suffix(dest) ---@type string|nil
|
local slot = slot_suffix(dest) ---@type string|nil
|
||||||
local dest_s = tostring(dest) ---@type string
|
local dest_s = tostring(dest) ---@type string
|
||||||
if slot then dest_s = dest_s .. " (slot " .. slot .. ")" end
|
if slot then dest_s = dest_s .. " (slot " .. slot .. ")" end
|
||||||
add(string.format("- `%s` words %s → %s dest %s",
|
add(string.format("- `%s` words %s → %s dest %s",
|
||||||
rel.semantic or "?",
|
rel.semantic or "?",
|
||||||
@@ -804,12 +804,12 @@ end
|
|||||||
local function aliases_for_key(key, atom, view)
|
local function aliases_for_key(key, atom, view)
|
||||||
local slot = key:match("^reguse:.+:(.+)$") ---@type string|nil
|
local slot = key:match("^reguse:.+:(.+)$") ---@type string|nil
|
||||||
if not slot then return "—" end
|
if not slot then return "—" end
|
||||||
local schema_name = atom.reg_use_schema_name ---@type string|nil
|
local schema_name = atom.reg_use_schema_name ---@type string|nil
|
||||||
local schema = view.corpus and view.corpus.reg_use_schemas and view.corpus.reg_use_schemas[schema_name] ---@type RegUseSchema|nil
|
local schema = view.corpus and view.corpus.reg_use_schemas and view.corpus.reg_use_schemas[schema_name] ---@type RegUseSchema|nil
|
||||||
if not schema then return "—" end
|
if not schema then return "—" end
|
||||||
for _, s in ipairs(schema.slots or {}) do ---@type integer, RegUseSlot
|
for _, s in ipairs(schema.slots or {}) do ---@type integer, RegUseSlot
|
||||||
if s.name == slot then
|
if s.name == slot then
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for _, alias in ipairs(s.aliases or {}) do ---@type integer, string
|
for _, alias in ipairs(s.aliases or {}) do ---@type integer, string
|
||||||
if alias ~= slot then names[#names + 1] = alias end
|
if alias ~= slot then names[#names + 1] = alias end
|
||||||
end
|
end
|
||||||
@@ -829,7 +829,7 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function physical_for_key(key, atom, view)
|
local function physical_for_key(key, atom, view)
|
||||||
if PHYSICAL_GPR[key] then return key end
|
if PHYSICAL_GPR[key] then return key end
|
||||||
local corpus = view.corpus or {} ---@type Corpus
|
local corpus = view.corpus or {} ---@type Corpus
|
||||||
local alias = (corpus.register_alias_registry or {})[key] ---@type AliasEntry|string|nil
|
local alias = (corpus.register_alias_registry or {})[key] ---@type AliasEntry|string|nil
|
||||||
if type(alias) == "table" then
|
if type(alias) == "table" then
|
||||||
local phys = alias.physical or alias.gpr or alias.code_name ---@type string|nil
|
local phys = alias.physical or alias.gpr or alias.code_name ---@type string|nil
|
||||||
@@ -840,7 +840,7 @@ local function physical_for_key(key, atom, view)
|
|||||||
end
|
end
|
||||||
local atom_map = (corpus.atom_auto_regs or {})[atom.name] ---@type GprAllocMap|nil
|
local atom_map = (corpus.atom_auto_regs or {})[atom.name] ---@type GprAllocMap|nil
|
||||||
if type(atom_map) == "table" then
|
if type(atom_map) == "table" then
|
||||||
local slot = key:match("^reguse:.+:(.+)$") or key ---@type string
|
local slot = key:match("^reguse:.+:(.+)$") or key ---@type string
|
||||||
local bound = atom_map[slot] or atom_map["R_" .. slot] ---@type string|nil
|
local bound = atom_map[slot] or atom_map["R_" .. slot] ---@type string|nil
|
||||||
if type(bound) == "string" and PHYSICAL_GPR[bound] then return bound end
|
if type(bound) == "string" and PHYSICAL_GPR[bound] then return bound end
|
||||||
end
|
end
|
||||||
@@ -851,15 +851,15 @@ end
|
|||||||
--- @param atom AtomEntry
|
--- @param atom AtomEntry
|
||||||
--- @return string
|
--- @return string
|
||||||
local function last_relation_for(key, atom)
|
local function last_relation_for(key, atom)
|
||||||
local last = nil ---@type AtomRelation|nil
|
local last = nil ---@type AtomRelation|nil
|
||||||
for _, rel in ipairs((atom.paths and atom.paths.relations) or {}) do ---@type integer, AtomRelation
|
for _, rel in ipairs((atom.paths and atom.paths.relations) or {}) do ---@type integer, AtomRelation
|
||||||
local dest = rel.destination or rel.producer_destination ---@type string|nil
|
local dest = rel.destination or rel.producer_destination ---@type string|nil
|
||||||
if dest == key then last = rel end
|
if dest == key then last = rel end
|
||||||
end
|
end
|
||||||
if not last then return "—" end
|
if not last then return "—" end
|
||||||
local sem = last.semantic or "?" ---@type string
|
local sem = last.semantic or "?" ---@type string
|
||||||
local a = last.producer_word ---@type integer|nil
|
local a = last.producer_word ---@type integer|nil
|
||||||
local b = last.consumer_word ---@type integer|nil
|
local b = last.consumer_word ---@type integer|nil
|
||||||
if a and b then return string.format("%s w%s→%s", sem, tostring(a), tostring(b)) end
|
if a and b then return string.format("%s w%s→%s", sem, tostring(a), tostring(b)) end
|
||||||
return sem
|
return sem
|
||||||
end
|
end
|
||||||
@@ -868,11 +868,11 @@ end
|
|||||||
--- @param view ModuleView
|
--- @param view ModuleView
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function render_section_forward(add, view)
|
local function render_section_forward(add, view)
|
||||||
local wrote = false ---@type boolean
|
local wrote = false ---@type boolean
|
||||||
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
for _, a in ipairs(view.decls) do ---@type integer, AtomEntry
|
||||||
local gpr = a.paths and a.paths.forward_state and a.paths.forward_state.gpr_values ---@type table<string, GprLatticeSlot>|nil
|
local gpr = a.paths and a.paths.forward_state and a.paths.forward_state.gpr_values ---@type table<string, GprLatticeSlot>|nil
|
||||||
local keys = {} ---@type string[]
|
local keys = {} ---@type string[]
|
||||||
for k in pairs(gpr or {}) do ---@type string
|
for k in pairs(gpr or {}) do ---@type string
|
||||||
if k == "R_0" then
|
if k == "R_0" then
|
||||||
-- hidden
|
-- hidden
|
||||||
elseif HIDDEN_UNLESS_WRITTEN[k] and not encoder_wrote_key(a, k) then
|
elseif HIDDEN_UNLESS_WRITTEN[k] and not encoder_wrote_key(a, k) then
|
||||||
@@ -888,7 +888,7 @@ local function render_section_forward(add, view)
|
|||||||
add("|---|---|---|---|---|")
|
add("|---|---|---|---|---|")
|
||||||
table.sort(keys)
|
table.sort(keys)
|
||||||
for _, k in ipairs(keys) do ---@type integer, string
|
for _, k in ipairs(keys) do ---@type integer, string
|
||||||
local slot = gpr[k] ---@type GprLatticeSlot|nil
|
local slot = gpr[k] ---@type GprLatticeSlot|nil
|
||||||
local lattice = "—" ---@type string
|
local lattice = "—" ---@type string
|
||||||
if slot and slot.kind == "constant" then
|
if slot and slot.kind == "constant" then
|
||||||
lattice = tostring(slot.value)
|
lattice = tostring(slot.value)
|
||||||
@@ -928,7 +928,7 @@ local SECTION_RENDERERS = { ---@type SectionRenderer[]
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function render_module_meta_report(view)
|
local function render_module_meta_report(view)
|
||||||
local dir_basename = source_basename(view.dir) ---@type string
|
local dir_basename = source_basename(view.dir) ---@type string
|
||||||
local lines = { ---@type string[]
|
local lines = { ---@type string[]
|
||||||
"# " .. dir_basename .. " — atom meta report",
|
"# " .. dir_basename .. " — atom meta report",
|
||||||
"> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.",
|
"> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.",
|
||||||
"",
|
"",
|
||||||
@@ -937,14 +937,14 @@ local function render_module_meta_report(view)
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function add(s) lines[#lines + 1] = s end
|
local function add(s) lines[#lines + 1] = s end
|
||||||
|
|
||||||
local kinds = count_kinds(view.decls) ---@type KindCounts
|
local kinds = count_kinds(view.decls) ---@type KindCounts
|
||||||
local n_annot, n_binds, n_macros = 0, 0, 0 ---@type integer, integer, integer
|
local n_annot, n_binds, n_macros = 0, 0, 0 ---@type integer, integer, integer
|
||||||
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
for _, src in ipairs(view.sources) do ---@type integer, SourceFile
|
||||||
n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
|
n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
|
||||||
n_binds = n_binds + #((src.scan and src.scan.binds) or {})
|
n_binds = n_binds + #((src.scan and src.scan.binds) or {})
|
||||||
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
||||||
end
|
end
|
||||||
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
||||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||||
if f.kind == "error" then n_err = n_err + 1
|
if f.kind == "error" then n_err = n_err + 1
|
||||||
elseif f.kind == "warning" then n_warn = n_warn + 1
|
elseif f.kind == "warning" then n_warn = n_warn + 1
|
||||||
@@ -1044,8 +1044,8 @@ local M = {} ---@type ReportPass
|
|||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @return PassResult
|
--- @return PassResult
|
||||||
function M.run(ctx)
|
function M.run(ctx)
|
||||||
local outputs = {} ---@type PassOutputEntry[]
|
local outputs = {} ---@type PassOutputEntry[]
|
||||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||||
local by_dir = (corpus and corpus.sources_by_dir) or {} ---@type table<string, SourceFile[]>
|
local by_dir = (corpus and corpus.sources_by_dir) or {} ---@type table<string, SourceFile[]>
|
||||||
|
|
||||||
-- `out_path_root`: when the conventional `out_root` is `build/gen` (any spelling — relative, absolute, separator variants).
|
-- `out_path_root`: when the conventional `out_root` is `build/gen` (any spelling — relative, absolute, separator variants).
|
||||||
@@ -1072,7 +1072,7 @@ function M.run(ctx)
|
|||||||
-- Per-renderer dispatch for the per-module renderers (once = false).
|
-- Per-renderer dispatch for the per-module renderers (once = false).
|
||||||
for _, renderer in ipairs(REPORT_RENDERERS) do ---@type integer, ReportRenderer
|
for _, renderer in ipairs(REPORT_RENDERERS) do ---@type integer, ReportRenderer
|
||||||
if not renderer.once then
|
if not renderer.once then
|
||||||
local body = renderer.gather(ctx, dir, dir_sources) ---@type string
|
local body = renderer.gather(ctx, dir, dir_sources) ---@type string
|
||||||
local out_path = out_root_effective .. "/" .. renderer.basename(dir_basename) .. "." .. renderer.ext ---@type string
|
local out_path = out_root_effective .. "/" .. renderer.basename(dir_basename) .. "." .. renderer.ext ---@type string
|
||||||
duffle.write_file(out_path, body)
|
duffle.write_file(out_path, body)
|
||||||
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
||||||
@@ -1080,13 +1080,13 @@ function M.run(ctx)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local view = build_module_view(dir, dir_sources, corpus) ---@type ModuleView
|
local view = build_module_view(dir, dir_sources, corpus) ---@type ModuleView
|
||||||
local n_annot, n_binds, n_macros = 0, 0, 0 ---@type integer, integer, integer
|
local n_annot, n_binds, n_macros = 0, 0, 0 ---@type integer, integer, integer
|
||||||
for _, src in ipairs(dir_sources) do ---@type integer, SourceFile
|
for _, src in ipairs(dir_sources) do ---@type integer, SourceFile
|
||||||
n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
|
n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
|
||||||
n_binds = n_binds + #((src.scan and src.scan.binds) or {})
|
n_binds = n_binds + #((src.scan and src.scan.binds) or {})
|
||||||
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
n_macros = n_macros + #((src.scan and src.scan.macros) or {})
|
||||||
end
|
end
|
||||||
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
local n_err, n_warn, n_info = 0, 0, 0 ---@type integer, integer, integer
|
||||||
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
for _, f in ipairs(view.findings or {}) do ---@type integer, CheckFinding
|
||||||
if f.kind == "error" then n_err = n_err + 1
|
if f.kind == "error" then n_err = n_err + 1
|
||||||
elseif f.kind == "warning" then n_warn = n_warn + 1
|
elseif f.kind == "warning" then n_warn = n_warn + 1
|
||||||
@@ -1109,7 +1109,7 @@ function M.run(ctx)
|
|||||||
-- Project-wide renderer (once = true): write the summary file.
|
-- Project-wide renderer (once = true): write the summary file.
|
||||||
for _, renderer in ipairs(REPORT_RENDERERS) do ---@type integer, ReportRenderer
|
for _, renderer in ipairs(REPORT_RENDERERS) do ---@type integer, ReportRenderer
|
||||||
if renderer.once then
|
if renderer.once then
|
||||||
local body = renderer.gather(ctx, nil, nil, all_modules) ---@type string
|
local body = renderer.gather(ctx, nil, nil, all_modules) ---@type string
|
||||||
local out_path = out_root_effective .. "/" .. renderer.basename("") .. "." .. renderer.ext ---@type string
|
local out_path = out_root_effective .. "/" .. renderer.basename("") .. "." .. renderer.ext ---@type string
|
||||||
duffle.write_file(out_path, body)
|
duffle.write_file(out_path, body)
|
||||||
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
||||||
|
|||||||
+150
-150
@@ -22,7 +22,7 @@
|
|||||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when required).
|
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when required).
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- Forward declarations for helpers used by earlier parsers (parse_enum_body_fields needs parse_enum_int_literal;
|
-- Forward declarations for helpers used by earlier parsers (parse_enum_body_fields needs parse_enum_int_literal;
|
||||||
-- parse_typedef_binds needs duffle.find_byte).
|
-- parse_typedef_binds needs duffle.find_byte).
|
||||||
@@ -271,16 +271,16 @@ local QUALIFIER_KEYWORDS = { ---@type table<string, boolean> -- bag: C qualifie
|
|||||||
-- "ac_" prefix length on component names (e.g., `MipsAtomComp_(ac_X, ...)`).
|
-- "ac_" prefix length on component names (e.g., `MipsAtomComp_(ac_X, ...)`).
|
||||||
-- The components pass strips this prefix to derive the macro name (e.g., `mac_X`).
|
-- The components pass strips this prefix to derive the macro name (e.g., `mac_X`).
|
||||||
local AC_PREFIX = "ac_" ---@type string
|
local AC_PREFIX = "ac_" ---@type string
|
||||||
local AC_PREFIX_LEN = 3 ---@type integer
|
local AC_PREFIX_LEN = 3 ---@type integer
|
||||||
|
|
||||||
-- The function-decl keyword that precedes a MipsAtomComp_Proc_ call.
|
-- The function-decl keyword that precedes a MipsAtomComp_Proc_ call.
|
||||||
-- Used by the backward walk in duffle.find_function_decl_for.
|
-- Used by the backward walk in duffle.find_function_decl_for.
|
||||||
local SLICE_MIPS_CODE = "Slice_MipsCode" ---@type string
|
local SLICE_MIPS_CODE = "Slice_MipsCode" ---@type string
|
||||||
local SLICE_MIPS_CODE_LEN = #SLICE_MIPS_CODE ---@type integer
|
local SLICE_MIPS_CODE_LEN = #SLICE_MIPS_CODE ---@type integer
|
||||||
|
|
||||||
-- The return type that precedes a MipsAtom_Proc_ function declaration.
|
-- The return type that precedes a MipsAtom_Proc_ function declaration.
|
||||||
-- Used by the backward walk in duffle.find_atom_proc_decl_for.
|
-- Used by the backward walk in duffle.find_atom_proc_decl_for.
|
||||||
local MIPS_ATOM_PTR = "MipsAtom*" ---@type string
|
local MIPS_ATOM_PTR = "MipsAtom*" ---@type string
|
||||||
local MIPS_ATOM_PTR_LEN = #MIPS_ATOM_PTR ---@type integer
|
local MIPS_ATOM_PTR_LEN = #MIPS_ATOM_PTR ---@type integer
|
||||||
|
|
||||||
--- Strip the "ac_" prefix from a component name.
|
--- Strip the "ac_" prefix from a component name.
|
||||||
@@ -302,7 +302,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function push_debug_skip_marker(out, marker)
|
local function push_debug_skip_marker(out, marker)
|
||||||
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
||||||
local prior = markers[#markers] ---@type DebugSkipMarker
|
local prior = markers[#markers] ---@type DebugSkipMarker
|
||||||
if prior and prior.pending then
|
if prior and prior.pending then
|
||||||
prior.pending = false
|
prior.pending = false
|
||||||
prior.superseded_by_marker_line = marker.marker_line
|
prior.superseded_by_marker_line = marker.marker_line
|
||||||
@@ -353,7 +353,7 @@ end
|
|||||||
--- @param start_pos integer -- exclusive upper bound for the captured block
|
--- @param start_pos integer -- exclusive upper bound for the captured block
|
||||||
--- @return string
|
--- @return string
|
||||||
local function preceding_comment_walk_backward(source, start_pos)
|
local function preceding_comment_walk_backward(source, start_pos)
|
||||||
local pieces = {} ---@type string[]
|
local pieces = {} ---@type string[]
|
||||||
local scan_pos = start_pos ---@type integer
|
local scan_pos = start_pos ---@type integer
|
||||||
while scan_pos > 0 do
|
while scan_pos > 0 do
|
||||||
local non_ws = scan_pos - 1 ---@type integer
|
local non_ws = scan_pos - 1 ---@type integer
|
||||||
@@ -370,8 +370,8 @@ local function preceding_comment_walk_backward(source, start_pos)
|
|||||||
if non_ws >= 2 and source:sub(non_ws - 1, non_ws) == "*/" then
|
if non_ws >= 2 and source:sub(non_ws - 1, non_ws) == "*/" then
|
||||||
-- Block comment close: walk back over `/*` candidates.
|
-- Block comment close: walk back over `/*` candidates.
|
||||||
local prefix = source:sub(1, non_ws - 1) ---@type string
|
local prefix = source:sub(1, non_ws - 1) ---@type string
|
||||||
local open_at = nil ---@type integer
|
local open_at = nil ---@type integer
|
||||||
for scan = #prefix - 1, 1, -1 do ---@type integer
|
for scan = #prefix - 1, 1, -1 do ---@type integer
|
||||||
if prefix:sub(scan, scan + 1) == "/*" then
|
if prefix:sub(scan, scan + 1) == "/*" then
|
||||||
open_at = scan
|
open_at = scan
|
||||||
break
|
break
|
||||||
@@ -430,7 +430,7 @@ end
|
|||||||
--- @return boolean|nil -- true iff the marker is the positive bare form
|
--- @return boolean|nil -- true iff the marker is the positive bare form
|
||||||
local function attach_debug_skip_marker(out, target_kind)
|
local function attach_debug_skip_marker(out, target_kind)
|
||||||
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
||||||
local marker = markers[#markers] ---@type DebugSkipMarker
|
local marker = markers[#markers] ---@type DebugSkipMarker
|
||||||
if not (marker and marker.pending) then return nil end
|
if not (marker and marker.pending) then return nil end
|
||||||
|
|
||||||
marker.pending = false
|
marker.pending = false
|
||||||
@@ -459,13 +459,13 @@ end
|
|||||||
local function register_atom(out, kind, declaration_line, name, body, body_off, raw_name, pos, after_paren, source)
|
local function register_atom(out, kind, declaration_line, name, body, body_off, raw_name, pos, after_paren, source)
|
||||||
-- 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 ---@type DebugSkipMarker|nil
|
local pending_marker = nil ---@type DebugSkipMarker|nil
|
||||||
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
||||||
local m = markers[#markers] ---@type DebugSkipMarker
|
local m = markers[#markers] ---@type DebugSkipMarker
|
||||||
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) ---@type boolean|nil
|
local positive = attach_debug_skip_marker(out, kind) ---@type boolean|nil
|
||||||
local comment = "" ---@type string
|
local comment = "" ---@type string
|
||||||
if kind == "comp_bare" or kind == "comp_proc" then
|
if kind == "comp_bare" or kind == "comp_proc" then
|
||||||
-- Scanner-owned declaration-comment attachment.
|
-- Scanner-owned declaration-comment attachment.
|
||||||
-- The walker does not need to detect marker shape.
|
-- The walker does not need to detect marker shape.
|
||||||
@@ -513,9 +513,9 @@ 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) ---@type integer
|
local start = duffle.skip_ws_and_cmt(text, pos) ---@type integer
|
||||||
local ident, after = duffle.read_ident(text, start) ---@type string|nil, integer
|
local ident, after = duffle.read_ident(text, start) ---@type string|nil, integer
|
||||||
if not ident then return nil end
|
if not ident then return nil end
|
||||||
local depth = 0 ---@type integer
|
local depth = 0 ---@type integer
|
||||||
local cursor = duffle.skip_ws_and_cmt(text, after) ---@type integer
|
local cursor = duffle.skip_ws_and_cmt(text, after) ---@type integer
|
||||||
while cursor <= #text and text:sub(cursor, cursor) == "*" do
|
while cursor <= #text and text:sub(cursor, cursor) == "*" do
|
||||||
depth = depth + 1
|
depth = depth + 1
|
||||||
@@ -564,8 +564,8 @@ local TYPE_CHAIN_MAX_DEPTH = 8 ---@type integer
|
|||||||
--- @param build_field fun(first: string, first_end: integer, after_first: integer): (TypeField|nil, integer)
|
--- @param build_field fun(first: string, first_end: integer, after_first: integer): (TypeField|nil, integer)
|
||||||
--- @return TypeField[]
|
--- @return TypeField[]
|
||||||
local function walk_body_fields(body, build_field)
|
local function walk_body_fields(body, build_field)
|
||||||
local fields = {} ---@type TypeField[]
|
local fields = {} ---@type TypeField[]
|
||||||
local body_pos = 1 ---@type integer
|
local body_pos = 1 ---@type integer
|
||||||
local body_len = #body ---@type integer
|
local body_len = #body ---@type integer
|
||||||
while body_pos <= body_len do
|
while body_pos <= body_len do
|
||||||
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
||||||
@@ -574,7 +574,7 @@ local function walk_body_fields(body, build_field)
|
|||||||
if not first then
|
if not first then
|
||||||
body_pos = body_pos + 1
|
body_pos = body_pos + 1
|
||||||
else
|
else
|
||||||
local after_first = duffle.skip_ws_and_cmt(body, first_end) ---@type integer
|
local after_first = duffle.skip_ws_and_cmt(body, first_end) ---@type integer
|
||||||
local result, new_pos = build_field(first, first_end, after_first) ---@type TypeField|nil, integer
|
local result, new_pos = build_field(first, first_end, after_first) ---@type TypeField|nil, integer
|
||||||
if result then fields[#fields + 1] = result end
|
if result then fields[#fields + 1] = result end
|
||||||
body_pos = new_pos or first_end
|
body_pos = new_pos or first_end
|
||||||
@@ -595,8 +595,8 @@ end
|
|||||||
--- @param body string
|
--- @param body string
|
||||||
--- @return TypeField[]
|
--- @return TypeField[]
|
||||||
local function parse_struct_body_fields(body)
|
local function parse_struct_body_fields(body)
|
||||||
local fields = {} ---@type TypeField[]
|
local fields = {} ---@type TypeField[]
|
||||||
local body_pos = 1 ---@type integer
|
local body_pos = 1 ---@type integer
|
||||||
local body_len = #body ---@type integer
|
local body_len = #body ---@type integer
|
||||||
while body_pos <= body_len do
|
while body_pos <= body_len do
|
||||||
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
||||||
@@ -647,11 +647,11 @@ local function parse_enum_body_fields(body)
|
|||||||
--- @param after_name integer
|
--- @param after_name integer
|
||||||
--- @return TypeField, integer
|
--- @return TypeField, integer
|
||||||
return walk_body_fields(body, function(entry_name, name_end, after_name)
|
return walk_body_fields(body, function(entry_name, name_end, after_name)
|
||||||
local value ---@type integer|nil
|
local value ---@type integer|nil
|
||||||
local new_pos ---@type integer
|
local new_pos ---@type integer
|
||||||
if body:sub(after_name, after_name) == "=" then
|
if body:sub(after_name, after_name) == "=" then
|
||||||
local val_pos = duffle.skip_ws_and_cmt(body, after_name + 1) ---@type integer
|
local val_pos = duffle.skip_ws_and_cmt(body, after_name + 1) ---@type integer
|
||||||
local v, end_pos = parse_enum_int_literal(body, val_pos) ---@type integer|nil, integer
|
local v, end_pos = parse_enum_int_literal(body, val_pos) ---@type integer|nil, integer
|
||||||
if v ~= nil then
|
if v ~= nil then
|
||||||
value = v
|
value = v
|
||||||
new_pos = end_pos
|
new_pos = end_pos
|
||||||
@@ -696,8 +696,8 @@ local function resolve_typedef_byte_size(type_name, type_name_registry, visited,
|
|||||||
|
|
||||||
-- Struct_ entries with unresolved byte_size can still resolve when their fields are all resolved.
|
-- Struct_ entries with unresolved byte_size can still resolve when their fields are all resolved.
|
||||||
if entry.kind == "struct" and entry.fields then
|
if entry.kind == "struct" and entry.fields then
|
||||||
local sum = 0 ---@type integer
|
local sum = 0 ---@type integer
|
||||||
local all_have = true ---@type boolean
|
local all_have = true ---@type boolean
|
||||||
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
||||||
if f.byte_size == nil then all_have = false; break end
|
if f.byte_size == nil then all_have = false; break end
|
||||||
sum = sum + f.byte_size
|
sum = sum + f.byte_size
|
||||||
@@ -730,7 +730,7 @@ local function propagate_type_sizes(out)
|
|||||||
-- The visited map is empty when handed to the resolver.
|
-- The visited map is empty when handed to the resolver.
|
||||||
-- The resolver marks visited as it enters each node, so the cycle guard fires only on RECURSIVE re-entry (not on the initial call).
|
-- The resolver marks visited as it enters each node, so the cycle guard fires only on RECURSIVE re-entry (not on the initial call).
|
||||||
for _ = 1, TYPE_CHAIN_MAX_DEPTH do ---@type integer
|
for _ = 1, TYPE_CHAIN_MAX_DEPTH do ---@type integer
|
||||||
local any_change = false ---@type boolean
|
local any_change = false ---@type boolean
|
||||||
for name, entry in pairs(reg) do ---@type string, TypeNameEntry
|
for name, entry in pairs(reg) do ---@type string, TypeNameEntry
|
||||||
if entry.byte_size == nil then
|
if entry.byte_size == nil then
|
||||||
local resolved = resolve_typedef_byte_size(name, reg, {}, 1) ---@type integer
|
local resolved = resolve_typedef_byte_size(name, reg, {}, 1) ---@type integer
|
||||||
@@ -747,23 +747,23 @@ local function propagate_type_sizes(out)
|
|||||||
-- Iterate to a fixed point: struct A may reference struct B which hasn't been resolved yet on the first pass.
|
-- Iterate to a fixed point: struct A may reference struct B which hasn't been resolved yet on the first pass.
|
||||||
-- Each pass updates as many fields + aggregates as possible; the loop terminates when no struct's byte_size changes between passes.
|
-- Each pass updates as many fields + aggregates as possible; the loop terminates when no struct's byte_size changes between passes.
|
||||||
for _ = 1, TYPE_CHAIN_MAX_DEPTH do ---@type integer
|
for _ = 1, TYPE_CHAIN_MAX_DEPTH do ---@type integer
|
||||||
local any_change = false ---@type boolean
|
local any_change = false ---@type boolean
|
||||||
for _, entry in pairs(reg) do ---@type string, TypeNameEntry
|
for _, entry in pairs(reg) do ---@type string, TypeNameEntry
|
||||||
if entry.kind == "array" and entry.byte_size == nil and entry.counts then
|
if entry.kind == "array" and entry.byte_size == nil and entry.counts then
|
||||||
local elem_size = BUILTIN_BYTE_SIZES[entry.elem] ---@type integer
|
local elem_size = BUILTIN_BYTE_SIZES[entry.elem] ---@type integer
|
||||||
or (reg[entry.elem] and reg[entry.elem].byte_size)
|
or (reg[entry.elem] and reg[entry.elem].byte_size)
|
||||||
if elem_size then
|
if elem_size then
|
||||||
local n = 1 ---@type integer
|
local n = 1 ---@type integer
|
||||||
for _, c in ipairs(entry.counts) do n = n * c end ---@type integer, string
|
for _, c in ipairs(entry.counts) do n = n * c end ---@type integer, string
|
||||||
entry.byte_size = elem_size * n
|
entry.byte_size = elem_size * n
|
||||||
any_change = true
|
any_change = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if entry.kind == "struct" and entry.fields then
|
if entry.kind == "struct" and entry.fields then
|
||||||
local byte_off = 0 ---@type integer
|
local byte_off = 0 ---@type integer
|
||||||
local gap_seen = false ---@type boolean
|
local gap_seen = false ---@type boolean
|
||||||
local sum = 0 ---@type integer
|
local sum = 0 ---@type integer
|
||||||
local all_have = true ---@type boolean
|
local all_have = true ---@type boolean
|
||||||
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
for _, f in ipairs(entry.fields) do ---@type integer, TypeField
|
||||||
-- Resolve field byte_size (pointer / builtin / typedef chain).
|
-- Resolve field byte_size (pointer / builtin / typedef chain).
|
||||||
if f.byte_size == nil then
|
if f.byte_size == nil then
|
||||||
@@ -817,7 +817,7 @@ end
|
|||||||
--- @param sub_inner string
|
--- @param sub_inner string
|
||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function scan_reg_list(sub_inner)
|
local function scan_reg_list(sub_inner)
|
||||||
local regs = {} ---@type string[]
|
local regs = {} ---@type string[]
|
||||||
local sub_inner_pos = 1 ---@type integer
|
local sub_inner_pos = 1 ---@type integer
|
||||||
while sub_inner_pos <= #sub_inner do
|
while sub_inner_pos <= #sub_inner do
|
||||||
sub_inner_pos = duffle.skip_ws_and_cmt(sub_inner, sub_inner_pos)
|
sub_inner_pos = duffle.skip_ws_and_cmt(sub_inner, sub_inner_pos)
|
||||||
@@ -904,8 +904,8 @@ end
|
|||||||
--- @return string|nil, string[]|nil, string[]|nil, string|nil, table<string, RegTypeOverride>|nil, string|nil, string|nil
|
--- @return string|nil, string[]|nil, string[]|nil, string|nil, table<string, RegTypeOverride>|nil, string|nil, string|nil
|
||||||
local function scan_atom_info_subcalls(info_inner, info_line)
|
local function scan_atom_info_subcalls(info_inner, info_line)
|
||||||
local binds, reads, writes = nil, nil, nil ---@type string|nil, string[]|nil, string[]|nil
|
local binds, reads, writes = nil, nil, nil ---@type string|nil, string[]|nil, string[]|nil
|
||||||
local view_binds, reg_overrides = nil, nil ---@type string|nil, table<string, RegTypeOverride>|nil
|
local view_binds, reg_overrides = nil, nil ---@type string|nil, table<string, RegTypeOverride>|nil
|
||||||
local ctx_atom_name, phase_label = nil, nil ---@type string|nil, string|nil
|
local ctx_atom_name, phase_label = nil, nil ---@type string|nil, string|nil
|
||||||
|
|
||||||
-- Per-subcall handler table. Each handler takes (sub_inner, info_line) and mutates the outer locals above.
|
-- Per-subcall handler table. Each handler takes (sub_inner, info_line) and mutates the outer locals above.
|
||||||
-- atom_reads/atom_writes share a handler (same shape; just different output target).
|
-- atom_reads/atom_writes share a handler (same shape; just different output target).
|
||||||
@@ -917,8 +917,8 @@ 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) ---@type string[]
|
local entries = duffle.split_top_level_commas(sub_inner) ---@type string[]
|
||||||
local regs = {} ---@type string[]
|
local regs = {} ---@type string[]
|
||||||
for _, entry in ipairs(entries) do ---@type integer, string
|
for _, entry in ipairs(entries) do ---@type integer, string
|
||||||
local reg_name, override, malformed = parse_atom_info_reg_entry(entry) ---@type string|nil, AtomInfoOverride|nil, boolean
|
local reg_name, override, malformed = parse_atom_info_reg_entry(entry) ---@type string|nil, AtomInfoOverride|nil, boolean
|
||||||
if reg_name then
|
if reg_name then
|
||||||
regs[#regs + 1] = reg_name
|
regs[#regs + 1] = reg_name
|
||||||
@@ -960,7 +960,7 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
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]) ---@type string
|
local reg_name = duffle.trim(args[1]) ---@type string
|
||||||
local type_name, depth = nil, 0 ---@type string|nil, integer
|
local type_name, depth = nil, 0 ---@type string|nil, integer
|
||||||
if args[2] then
|
if args[2] then
|
||||||
local parsed_name, parsed_depth = parse_type_chain(args[2], 1) ---@type string|nil, integer
|
local parsed_name, parsed_depth = parse_type_chain(args[2], 1) ---@type string|nil, integer
|
||||||
if parsed_name then
|
if parsed_name then
|
||||||
@@ -1013,7 +1013,7 @@ local function scan_atom_info_subcalls(info_inner, info_line)
|
|||||||
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end) ---@type integer
|
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end) ---@type integer
|
||||||
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) ---@type string|nil, integer
|
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open) ---@type string|nil, integer
|
||||||
local handler = SUBCALL_HANDLERS[sub_ident] ---@type fun(sub_inner: string, info_line: integer|nil): nil|nil
|
local handler = SUBCALL_HANDLERS[sub_ident] ---@type fun(sub_inner: string, info_line: integer|nil): nil|nil
|
||||||
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
|
||||||
@@ -1046,29 +1046,29 @@ end
|
|||||||
-- symbol references resolve via the cross-source `_code_macros` registry built in two passes by `M.run`.
|
-- symbol references resolve via the cross-source `_code_macros` registry built in two passes by `M.run`.
|
||||||
|
|
||||||
-- Byte constants (local to this file).
|
-- Byte constants (local to this file).
|
||||||
local BYTE_HASH = 0x23 ---@type integer -- '#'
|
local BYTE_HASH = 0x23 ---@type integer -- '#'
|
||||||
local BYTE_NEWLINE = 0x0A ---@type integer -- '\n'
|
local BYTE_NEWLINE = 0x0A ---@type integer -- '\n'
|
||||||
local BYTE_DASH = 0x2D ---@type integer -- '-'
|
local BYTE_DASH = 0x2D ---@type integer -- '-'
|
||||||
local BYTE_COMMA = 0x2C ---@type integer -- ','
|
local BYTE_COMMA = 0x2C ---@type integer -- ','
|
||||||
local BYTE_SEMI = 0x3B ---@type integer -- ';'
|
local BYTE_SEMI = 0x3B ---@type integer -- ';'
|
||||||
local BYTE_EQUAL = 0x3D ---@type integer -- '='
|
local BYTE_EQUAL = 0x3D ---@type integer -- '='
|
||||||
local BYTE_R = 0x52 ---@type integer -- 'R'
|
local BYTE_R = 0x52 ---@type integer -- 'R'
|
||||||
local BYTE_UNDERSCORE = 0x5F ---@type integer -- '_'
|
local BYTE_UNDERSCORE = 0x5F ---@type integer -- '_'
|
||||||
local BYTE_0 = 0x30 ---@type integer -- '0'
|
local BYTE_0 = 0x30 ---@type integer -- '0'
|
||||||
local BYTE_9 = 0x39 ---@type integer -- '9'
|
local BYTE_9 = 0x39 ---@type integer -- '9'
|
||||||
local BYTE_a = 0x61 ---@type integer -- 'a'
|
local BYTE_a = 0x61 ---@type integer -- 'a'
|
||||||
local BYTE_f = 0x66 ---@type integer -- 'f'
|
local BYTE_f = 0x66 ---@type integer -- 'f'
|
||||||
local BYTE_A = 0x41 ---@type integer -- 'A'
|
local BYTE_A = 0x41 ---@type integer -- 'A'
|
||||||
local BYTE_F = 0x46 ---@type integer -- 'F'
|
local BYTE_F = 0x46 ---@type integer -- 'F'
|
||||||
local BYTE_x = 0x78 ---@type integer -- 'x'
|
local BYTE_x = 0x78 ---@type integer -- 'x'
|
||||||
local BYTE_X = 0x58 ---@type integer -- 'X'
|
local BYTE_X = 0x58 ---@type integer -- 'X'
|
||||||
local BYTE_OPEN_BRACE = 0x7B ---@type integer -- '{'
|
local BYTE_OPEN_BRACE = 0x7B ---@type integer -- '{'
|
||||||
local BYTE_CLOSE_BRACE= 0x7D ---@type integer -- '}'
|
local BYTE_CLOSE_BRACE= 0x7D ---@type integer -- '}'
|
||||||
local BYTE_SLASH = 0x2F ---@type integer -- '/'
|
local BYTE_SLASH = 0x2F ---@type integer -- '/'
|
||||||
local BYTE_STAR = 0x2A ---@type integer -- '*'
|
local BYTE_STAR = 0x2A ---@type integer -- '*'
|
||||||
local BYTE_SPACE = 0x20 ---@type integer -- ' '
|
local BYTE_SPACE = 0x20 ---@type integer -- ' '
|
||||||
local BYTE_TAB = 0x09 ---@type integer -- '\t'
|
local BYTE_TAB = 0x09 ---@type integer -- '\t'
|
||||||
local BYTE_CR = 0x0D ---@type integer -- '\r'
|
local BYTE_CR = 0x0D ---@type integer -- '\r'
|
||||||
|
|
||||||
-- Maximum chain depth when resolving `R_*_Code` symbol RHS references.
|
-- Maximum chain depth when resolving `R_*_Code` symbol RHS references.
|
||||||
-- Eight hops is enough for any production chain (R_TapePtr_Code -> R_T8_Code -> ...).
|
-- Eight hops is enough for any production chain (R_TapePtr_Code -> R_T8_Code -> ...).
|
||||||
@@ -1164,7 +1164,7 @@ parse_enum_int_literal = function(text, start)
|
|||||||
local peek = text:byte(pos + 1) ---@type integer
|
local peek = text:byte(pos + 1) ---@type integer
|
||||||
if peek == BYTE_x or peek == BYTE_X then
|
if peek == BYTE_x or peek == BYTE_X then
|
||||||
pos = pos + 2
|
pos = pos + 2
|
||||||
local value = 0 ---@type integer|nil
|
local value = 0 ---@type integer|nil
|
||||||
local has_digit = false ---@type boolean
|
local has_digit = false ---@type boolean
|
||||||
while pos <= len do
|
while pos <= len do
|
||||||
local d = hex_digit_value(text:byte(pos)) ---@type integer|nil
|
local d = hex_digit_value(text:byte(pos)) ---@type integer|nil
|
||||||
@@ -1286,25 +1286,25 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
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) ---@type integer
|
local rest = duffle.skip_ws_and_cmt(source, directive_start + 1) ---@type integer
|
||||||
local kw, kw_end = duffle.read_ident(source, rest) ---@type string|nil, integer
|
local kw, kw_end = duffle.read_ident(source, rest) ---@type string|nil, integer
|
||||||
if kw ~= "define" then return end
|
if kw ~= "define" then return end
|
||||||
|
|
||||||
local after_kw = duffle.skip_ws_and_cmt(source, kw_end) ---@type integer
|
local after_kw = duffle.skip_ws_and_cmt(source, kw_end) ---@type integer
|
||||||
local macro_name, macro_end = duffle.read_ident(source, after_kw) ---@type string|nil, integer
|
local macro_name, macro_end = duffle.read_ident(source, after_kw) ---@type string|nil, integer
|
||||||
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
|
||||||
|
|
||||||
-- Save the raw RHS (post-`=` text up to the line end) into the cross-source body table
|
-- Save the raw RHS (post-`=` text up to the line end) into the cross-source body table
|
||||||
-- FIRST so the chain walker can fall back to it when the defining `#define` lives in a different source.
|
-- FIRST so the chain walker can fall back to it when the defining `#define` lives in a different source.
|
||||||
local rhs_pos = duffle.skip_ws_and_cmt(source, macro_end) ---@type integer
|
local rhs_pos = duffle.skip_ws_and_cmt(source, macro_end) ---@type integer
|
||||||
local rhs_end = duffle.find_byte(source, BYTE_NEWLINE, rhs_pos) or (#source + 1) ---@type integer|nil
|
local rhs_end = duffle.find_byte(source, BYTE_NEWLINE, rhs_pos) or (#source + 1) ---@type integer|nil
|
||||||
local rhs_text = duffle.trim(source:sub(rhs_pos, rhs_end - 1)) ---@type string
|
local rhs_text = duffle.trim(source:sub(rhs_pos, rhs_end - 1)) ---@type string
|
||||||
if rhs_text ~= "" then
|
if rhs_text ~= "" then
|
||||||
code_macro_bodies[macro_name] = rhs_text
|
code_macro_bodies[macro_name] = rhs_text
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve RHS via per-chain visited + depth-bounded recursion.
|
-- Resolve RHS via per-chain visited + depth-bounded recursion.
|
||||||
local visited = { [macro_name] = true } ---@type table<string, boolean> -- bag: already-walked ident -> true
|
local visited = { [macro_name] = true } ---@type table<string, boolean> -- bag: already-walked ident -> true
|
||||||
local value = resolve_code_macro_value(source, rhs_pos, code_macros, code_macro_bodies, visited, 1) ---@type integer|nil
|
local value = resolve_code_macro_value(source, rhs_pos, code_macros, code_macro_bodies, visited, 1) ---@type integer|nil
|
||||||
if value ~= nil then code_macros[macro_name] = value end
|
if value ~= nil then code_macros[macro_name] = value end
|
||||||
end
|
end
|
||||||
@@ -1318,7 +1318,7 @@ end
|
|||||||
--- @param code_macro_bodies table<string, string> -- bag: R_*_Code -> raw RHS text
|
--- @param code_macro_bodies table<string, string> -- bag: R_*_Code -> raw RHS text
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function scan_source_pre_pass(source, code_macros, code_macro_bodies)
|
local function scan_source_pre_pass(source, code_macros, code_macro_bodies)
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local src_len = #source ---@type integer
|
local src_len = #source ---@type integer
|
||||||
while pos <= src_len do
|
while pos <= src_len do
|
||||||
pos = duffle.skip_ws_and_cmt(source, pos)
|
pos = duffle.skip_ws_and_cmt(source, pos)
|
||||||
@@ -1443,10 +1443,10 @@ local function parse_dbg_skip_marker(source, pos, ident_end, line_of, out)
|
|||||||
|
|
||||||
-- Diagnostic-only detection of an invalid following `(...)`.
|
-- Diagnostic-only detection of an invalid following `(...)`.
|
||||||
-- The cursor is advanced past the `()` either way to keep token order coherent for the next scan iteration.
|
-- The cursor is advanced past the `()` either way to keep token order coherent for the next scan iteration.
|
||||||
local marker_end = ident_end ---@type integer
|
local marker_end = ident_end ---@type integer
|
||||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local open_paren = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local has_parens = false ---@type boolean
|
local has_parens = false ---@type boolean
|
||||||
local args = nil ---@type string[]
|
local args = nil ---@type string[]
|
||||||
if source:sub(open_paren, open_paren) == "(" then
|
if source:sub(open_paren, open_paren) == "(" then
|
||||||
local inner, after_paren = duffle.read_parens(source, open_paren) ---@type string|nil, integer
|
local inner, after_paren = duffle.read_parens(source, open_paren) ---@type string|nil, integer
|
||||||
marker_end = after_paren
|
marker_end = after_paren
|
||||||
@@ -1479,13 +1479,13 @@ end
|
|||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_auto_reg_marker(source, pos, ident_end, line_of, out)
|
local function parse_auto_reg_marker(source, pos, ident_end, line_of, out)
|
||||||
local marker_kind = source:sub(pos, ident_end - 1) ---@type string -- "atom_auto_reg" or "phase_auto_reg"
|
local marker_kind = source:sub(pos, ident_end - 1) ---@type string -- "atom_auto_reg" or "phase_auto_reg"
|
||||||
local scope_kind = marker_kind == "atom_auto_reg" and "atom" or "phase" ---@type string
|
local scope_kind = marker_kind == "atom_auto_reg" and "atom" or "phase" ---@type string
|
||||||
|
|
||||||
local inner, after_paren = read_parens_after(source, ident_end) ---@type string|nil, integer
|
local inner, after_paren = read_parens_after(source, ident_end) ---@type string|nil, integer
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
||||||
local scope_name = args[1] and duffle.trim(args[1]) or nil ---@type string
|
local scope_name = args[1] and duffle.trim(args[1]) or nil ---@type string
|
||||||
local sym = args[2] and duffle.trim(args[2]) or nil ---@type string
|
local sym = args[2] and duffle.trim(args[2]) or nil ---@type string
|
||||||
|
|
||||||
@@ -1521,8 +1521,8 @@ local function parse_atom_dbg_reg_default(source, pos, ident_end, line_of, out)
|
|||||||
-- Annotation pass surfaces this; we still consume the marker.
|
-- Annotation pass surfaces this; we still consume the marker.
|
||||||
return after_paren
|
return after_paren
|
||||||
end
|
end
|
||||||
local reg_name = duffle.trim(args[1]) ---@type string
|
local reg_name = duffle.trim(args[1]) ---@type string
|
||||||
local type_part = args[2] or "void" ---@type string
|
local type_part = args[2] or "void" ---@type string
|
||||||
local type_name, depth = parse_type_chain(type_part, 1) ---@type string|nil, integer
|
local type_name, depth = parse_type_chain(type_part, 1) ---@type string|nil, integer
|
||||||
if not type_name then type_name, depth = duffle.trim(type_part), 0 end
|
if not type_name then type_name, depth = duffle.trim(type_part), 0 end
|
||||||
out.types[reg_name] = {
|
out.types[reg_name] = {
|
||||||
@@ -1551,13 +1551,13 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
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) ---@type integer
|
local lookahead = duffle.skip_ws_and_cmt(source, after_paren) ---@type integer
|
||||||
local look_ident, look_end = duffle.read_ident(source, lookahead) ---@type string|nil, integer
|
local look_ident, look_end = duffle.read_ident(source, lookahead) ---@type string|nil, integer
|
||||||
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) ---@type integer
|
local info_open = duffle.skip_ws_and_cmt(source, look_end) ---@type integer
|
||||||
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) ---@type string|nil, integer
|
local info_inner, info_after = duffle.read_parens(source, info_open) ---@type string|nil, integer
|
||||||
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) ---@type integer
|
local info_line = line_of(info_open) ---@type integer
|
||||||
local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line) ---@type string|nil, string[]|nil, string[]|nil, string|nil, table<string, RegTypeOverride>|nil, string|nil, string|nil
|
local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line) ---@type string|nil, string[]|nil, string[]|nil, string|nil, table<string, RegTypeOverride>|nil, string|nil, string|nil
|
||||||
dest = dest or out.atom_infos
|
dest = dest or out.atom_infos
|
||||||
dest[#dest + 1] = {
|
dest[#dest + 1] = {
|
||||||
@@ -1638,7 +1638,7 @@ local DECL_FORMS = { ---@type table<string, DeclForm>
|
|||||||
--- @param open_paren integer
|
--- @param open_paren integer
|
||||||
--- @return string|nil, integer|nil
|
--- @return string|nil, integer|nil
|
||||||
local function last_brace_body(inner, open_paren)
|
local function last_brace_body(inner, open_paren)
|
||||||
local last_brace_pos = nil ---@type integer
|
local last_brace_pos = nil ---@type integer
|
||||||
for search_pos = #inner, 1, -1 do ---@type integer
|
for search_pos = #inner, 1, -1 do ---@type integer
|
||||||
if inner:sub(search_pos, search_pos) == "{" then
|
if inner:sub(search_pos, search_pos) == "{" then
|
||||||
last_brace_pos = search_pos
|
last_brace_pos = search_pos
|
||||||
@@ -1663,8 +1663,8 @@ local function reguse_hook(source, pos, line_of, out, extras)
|
|||||||
local reg_use_schema_name, reg_use_param_name ---@type string|nil, string|nil
|
local reg_use_schema_name, reg_use_param_name ---@type string|nil, string|nil
|
||||||
if extras.args_inner then
|
if extras.args_inner then
|
||||||
local arg_tokens = duffle.split_top_level_commas(extras.args_inner) ---@type string[]
|
local arg_tokens = duffle.split_top_level_commas(extras.args_inner) ---@type string[]
|
||||||
for _, tok in ipairs(arg_tokens) do ---@type integer, string
|
for _, tok in ipairs(arg_tokens) do ---@type integer, string
|
||||||
local trimmed = duffle.trim(tok) ---@type string
|
local trimmed = duffle.trim(tok) ---@type string
|
||||||
local schema_suffix, param = trimmed:match("RegUse_([%w_]+)%s+([%w_]+)$") ---@type string, string
|
local schema_suffix, param = trimmed:match("RegUse_([%w_]+)%s+([%w_]+)$") ---@type string, string
|
||||||
if schema_suffix then
|
if schema_suffix then
|
||||||
if reg_use_schema_name then
|
if reg_use_schema_name then
|
||||||
@@ -1703,14 +1703,14 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
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) ---@type string|nil
|
local ident = duffle.read_ident(source, pos) ---@type string|nil
|
||||||
local form = ident and DECL_FORMS[ident] ---@type DeclForm|nil
|
local form = ident and DECL_FORMS[ident] ---@type DeclForm|nil
|
||||||
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) ---@type string|nil, integer, integer
|
local inner, after_paren, open_paren = read_parens_after(source, ident_end) ---@type string|nil, integer, integer
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
local extras = {} ---@type DeclExtras
|
local extras = {} ---@type DeclExtras
|
||||||
local raw_name ---@type string
|
local raw_name ---@type string
|
||||||
if form.name == "paren_ident" then
|
if form.name == "paren_ident" then
|
||||||
raw_name = duffle.read_ident(inner, 1)
|
raw_name = duffle.read_ident(inner, 1)
|
||||||
if form.strip and not raw_name then return open_paren + 1 end
|
if form.strip and not raw_name then return open_paren + 1 end
|
||||||
@@ -1787,12 +1787,12 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_mips_code(source, pos, ident_end, line_of, out)
|
local function parse_mips_code(source, pos, ident_end, line_of, out)
|
||||||
local next_pos = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local next_pos = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local next_ident, next_after = duffle.read_ident(source, next_pos) ---@type string|nil, integer
|
local next_ident, next_after = duffle.read_ident(source, next_pos) ---@type string|nil, integer
|
||||||
if not next_ident or #next_ident <= 5 or next_ident:sub(1, 5) ~= "code_" then
|
if not next_ident or #next_ident <= 5 or next_ident:sub(1, 5) ~= "code_" then
|
||||||
return ident_end
|
return ident_end
|
||||||
end
|
end
|
||||||
|
|
||||||
local atom_name = next_ident:sub(6) ---@type string
|
local atom_name = next_ident:sub(6) ---@type string
|
||||||
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end) ---@type string|nil, integer, integer
|
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end) ---@type string|nil, integer, integer
|
||||||
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)
|
||||||
@@ -1820,7 +1820,7 @@ end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
local function register_struct_type(body, name, pos, line_of, out)
|
local function register_struct_type(body, name, pos, line_of, out)
|
||||||
local fields = parse_struct_body_fields(body) ---@type TypeField[]
|
local fields = parse_struct_body_fields(body) ---@type TypeField[]
|
||||||
local source_pos = line_of(pos) ---@type integer
|
local source_pos = line_of(pos) ---@type integer
|
||||||
out.type_name_registry[name] = {
|
out.type_name_registry[name] = {
|
||||||
name = name,
|
name = name,
|
||||||
kind = "struct",
|
kind = "struct",
|
||||||
@@ -1911,10 +1911,10 @@ local parse_reg_use_schema_body ---@type fun(body: string, type_registry: table<
|
|||||||
--- @param type_registry table<string, TypeNameEntry>|nil
|
--- @param type_registry table<string, TypeNameEntry>|nil
|
||||||
--- @return string[]|nil
|
--- @return string[]|nil
|
||||||
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 ---@type string
|
local reg_name = "Reg_" .. type_name ---@type string
|
||||||
local entry = type_registry and type_registry[reg_name] ---@type TypeNameEntry|nil
|
local entry = type_registry and type_registry[reg_name] ---@type TypeNameEntry|nil
|
||||||
if entry and entry.fields and #entry.fields > 0 then
|
if entry and entry.fields and #entry.fields > 0 then
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for _, field in ipairs(entry.fields) do ---@type integer, TypeField
|
for _, field in ipairs(entry.fields) do ---@type integer, TypeField
|
||||||
if field.name then names[#names + 1] = field.name end
|
if field.name then names[#names + 1] = field.name end
|
||||||
end
|
end
|
||||||
@@ -1923,7 +1923,7 @@ local function fields_for_reg_type(type_name, type_registry)
|
|||||||
if entry and entry.body and parse_reg_use_schema_body then
|
if entry and entry.body and parse_reg_use_schema_body then
|
||||||
local schema = parse_reg_use_schema_body(entry.body, type_registry) ---@type RegUseSchema|nil
|
local schema = parse_reg_use_schema_body(entry.body, type_registry) ---@type RegUseSchema|nil
|
||||||
if schema and schema.slots then
|
if schema and schema.slots then
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for _, slot in ipairs(schema.slots) do ---@type integer, RegUseSlot
|
for _, slot in ipairs(schema.slots) do ---@type integer, RegUseSlot
|
||||||
if slot.name then names[#names + 1] = slot.name end
|
if slot.name then names[#names + 1] = slot.name end
|
||||||
end
|
end
|
||||||
@@ -1940,11 +1940,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 ---@type boolean
|
local require_types = opts.require_types == true ---@type boolean
|
||||||
local pending = false ---@type boolean
|
local pending = false ---@type boolean
|
||||||
local slots = {} ---@type RegUseSlot[]
|
local slots = {} ---@type RegUseSlot[]
|
||||||
local alias_to_slot = {} ---@type table<string, string> -- bag: alias path -> slot name
|
local alias_to_slot = {} ---@type table<string, string> -- bag: alias path -> slot name
|
||||||
local slot_names = {} ---@type table<string, boolean> -- bag: slot name -> true
|
local slot_names = {} ---@type table<string, boolean> -- bag: slot name -> true
|
||||||
local errors = {} ---@type RegUseError[]
|
local errors = {} ---@type RegUseError[]
|
||||||
|
|
||||||
--- @param path string
|
--- @param path string
|
||||||
--- @param slot string
|
--- @param slot string
|
||||||
@@ -2017,9 +2017,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 = {} ---@type RegUseView[]
|
local views = {} ---@type RegUseView[]
|
||||||
local union_readonly = nil ---@type boolean
|
local union_readonly = nil ---@type boolean
|
||||||
local inner_pos = 1 ---@type integer
|
local inner_pos = 1 ---@type integer
|
||||||
|
|
||||||
--- @param flag boolean
|
--- @param flag boolean
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
@@ -2073,7 +2073,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
pending = true
|
pending = true
|
||||||
else
|
else
|
||||||
for _, inst in ipairs(inst_names) do ---@type integer, string
|
for _, inst in ipairs(inst_names) do ---@type integer, string
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for _, field in ipairs(typed_fields) do ---@type integer, string
|
for _, field in ipairs(typed_fields) do ---@type integer, string
|
||||||
names[#names + 1] = inst .. "." .. field
|
names[#names + 1] = inst .. "." .. field
|
||||||
end
|
end
|
||||||
@@ -2102,7 +2102,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local lane_names = {} ---@type string[]
|
local lane_names = {} ---@type string[]
|
||||||
local s_pos = 1 ---@type integer
|
local s_pos = 1 ---@type integer
|
||||||
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
|
||||||
@@ -2145,8 +2145,8 @@ 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) ---@type integer
|
local s_after = duffle.skip_ws_and_cmt(struct_inner, s_ty_end) ---@type integer
|
||||||
local s_readonly = false ---@type boolean
|
local s_readonly = false ---@type boolean
|
||||||
local maybe_const, maybe_end = duffle.read_ident(struct_inner, s_after) ---@type string|nil, integer
|
local maybe_const, maybe_end = duffle.read_ident(struct_inner, s_after) ---@type string|nil, integer
|
||||||
if maybe_const == "const" then
|
if maybe_const == "const" then
|
||||||
s_readonly = true
|
s_readonly = true
|
||||||
@@ -2177,8 +2177,8 @@ 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) ---@type integer
|
local m_after = duffle.skip_ws_and_cmt(inner, m_type_end) ---@type integer
|
||||||
local m_readonly = false ---@type boolean
|
local m_readonly = false ---@type boolean
|
||||||
local maybe_const, maybe_end = duffle.read_ident(inner, m_after) ---@type string|nil, integer
|
local maybe_const, maybe_end = duffle.read_ident(inner, m_after) ---@type string|nil, integer
|
||||||
if maybe_const == "const" then
|
if maybe_const == "const" then
|
||||||
m_readonly = true
|
m_readonly = true
|
||||||
@@ -2200,7 +2200,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local after_close = duffle.skip_ws_and_cmt(body, after_braces) ---@type integer
|
local after_close = duffle.skip_ws_and_cmt(body, after_braces) ---@type integer
|
||||||
local inst_name, inst_end = duffle.read_ident(body, after_close) ---@type string|nil, integer
|
local inst_name, inst_end = duffle.read_ident(body, after_close) ---@type string|nil, integer
|
||||||
|
|
||||||
if #views == 0 then
|
if #views == 0 then
|
||||||
if not pending then
|
if not pending then
|
||||||
@@ -2208,13 +2208,13 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local has_lanes = false ---@type boolean
|
local has_lanes = false ---@type boolean
|
||||||
for _, v in ipairs(views) do ---@type integer, RegUseView
|
for _, v in ipairs(views) do ---@type integer, RegUseView
|
||||||
if v.lanes then has_lanes = true end
|
if v.lanes then has_lanes = true end
|
||||||
end
|
end
|
||||||
|
|
||||||
if has_lanes then
|
if has_lanes then
|
||||||
local width = nil ---@type integer
|
local width = nil ---@type integer
|
||||||
for _, v in ipairs(views) do ---@type integer, RegUseView
|
for _, v in ipairs(views) do ---@type integer, RegUseView
|
||||||
if not v.lanes then
|
if not v.lanes then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
@@ -2231,7 +2231,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
for i = 1, width do ---@type integer
|
for i = 1, width do ---@type integer
|
||||||
local slot_name = views[1].names[i] ---@type string
|
local slot_name = views[1].names[i] ---@type string
|
||||||
if inst_name then slot_name = inst_name .. "." .. slot_name end
|
if inst_name then slot_name = inst_name .. "." .. slot_name end
|
||||||
local aliases = {} ---@type string[]
|
local aliases = {} ---@type string[]
|
||||||
for _, v in ipairs(views) do ---@type integer, RegUseView
|
for _, v in ipairs(views) do ---@type integer, RegUseView
|
||||||
local n = v.names[i] ---@type integer
|
local n = v.names[i] ---@type integer
|
||||||
if inst_name then n = inst_name .. "." .. n end
|
if inst_name then n = inst_name .. "." .. n end
|
||||||
@@ -2242,12 +2242,12 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local members = {} ---@type string[]
|
local members = {} ---@type string[]
|
||||||
for _, v in ipairs(views) do ---@type integer, RegUseView
|
for _, v in ipairs(views) do ---@type integer, RegUseView
|
||||||
for _, n in ipairs(v.names) do members[#members + 1] = n end ---@type integer, integer
|
for _, n in ipairs(v.names) do members[#members + 1] = n end ---@type integer, integer
|
||||||
end
|
end
|
||||||
local aliases = {} ---@type string[]
|
local aliases = {} ---@type string[]
|
||||||
local slot_name ---@type string
|
local slot_name ---@type string
|
||||||
if inst_name then
|
if inst_name then
|
||||||
slot_name = inst_name
|
slot_name = inst_name
|
||||||
for _, m in ipairs(members) do ---@type integer, string
|
for _, m in ipairs(members) do ---@type integer, string
|
||||||
@@ -2293,7 +2293,7 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
end
|
end
|
||||||
after = duffle.skip_ws_and_cmt(body, after_paren)
|
after = duffle.skip_ws_and_cmt(body, after_paren)
|
||||||
end
|
end
|
||||||
local readonly = false ---@type boolean
|
local readonly = false ---@type boolean
|
||||||
local maybe_const, maybe_end = duffle.read_ident(body, after) ---@type string|nil, integer
|
local maybe_const, maybe_end = duffle.read_ident(body, after) ---@type string|nil, integer
|
||||||
if maybe_const == "const" then
|
if maybe_const == "const" then
|
||||||
readonly = true
|
readonly = true
|
||||||
@@ -2426,7 +2426,7 @@ local function parse_typedef_array(source, pos, id2_end, line_of, out, after_typ
|
|||||||
if not inner then return id2_end end
|
if not inner then return id2_end end
|
||||||
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
local args = duffle.split_top_level_commas(inner) ---@type string[]
|
||||||
if #args < 2 then return after_paren end
|
if #args < 2 then return after_paren end
|
||||||
local elem = duffle.trim(args[1]) ---@type string
|
local elem = duffle.trim(args[1]) ---@type string
|
||||||
local len = tonumber(duffle.trim(args[2]), 10) ---@type string
|
local len = tonumber(duffle.trim(args[2]), 10) ---@type string
|
||||||
if type(elem) ~= "string" or elem == "" or not len or len < 1 or len ~= math.floor(len) then
|
if type(elem) ~= "string" or elem == "" or not len or len < 1 or len ~= math.floor(len) then
|
||||||
return after_paren
|
return after_paren
|
||||||
@@ -2466,7 +2466,7 @@ local TYPE_FORMS = { ---@type table<string, fun(source: string, pos: integer, id
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
||||||
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local id2, id2_end = duffle.read_ident(source, after_typedef) ---@type string|nil, integer
|
local id2, id2_end = duffle.read_ident(source, after_typedef) ---@type string|nil, integer
|
||||||
if not id2 then return ident_end end
|
if not id2 then return ident_end end
|
||||||
local form = TYPE_FORMS[id2] ---@type DeclForm|nil
|
local form = TYPE_FORMS[id2] ---@type DeclForm|nil
|
||||||
if form then
|
if form then
|
||||||
@@ -2534,7 +2534,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
-- C-array suffix: `typedef S2 A3x3_S2[3][3];` → kind=array, not a typedef alias.
|
-- 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.
|
-- Malformed `[` / non-decimal dims fall through to the typedef-alias path.
|
||||||
if last_ident and not tset_arg then
|
if last_ident and not tset_arg then
|
||||||
local dims = {} ---@type integer[]
|
local dims = {} ---@type integer[]
|
||||||
local dim_scan = duffle.skip_ws_and_cmt(source, last_ident_end) ---@type integer
|
local dim_scan = duffle.skip_ws_and_cmt(source, last_ident_end) ---@type integer
|
||||||
while dim_scan < semi_pos and source:sub(dim_scan, dim_scan) == "[" do
|
while dim_scan < semi_pos and source:sub(dim_scan, dim_scan) == "[" do
|
||||||
local close = source:find("]", dim_scan + 1, true) ---@type boolean
|
local close = source:find("]", dim_scan + 1, true) ---@type boolean
|
||||||
@@ -2561,7 +2561,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
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) ---@type string
|
local underlying_span = source:sub(after_typedef, tset_pos - 1) ---@type string
|
||||||
local underlying = duffle.trim(underlying_span) ---@type string
|
local underlying = duffle.trim(underlying_span) ---@type string
|
||||||
register_typedef_alias(underlying, tset_arg, pos, line_of, out)
|
register_typedef_alias(underlying, tset_arg, pos, line_of, out)
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
return tset_arg_end or (semi_pos + 1)
|
return tset_arg_end or (semi_pos + 1)
|
||||||
@@ -2570,7 +2570,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
if last_ident then
|
if last_ident then
|
||||||
-- Shape 3: alias is the last ident before `;`; the underlying span is the trimmed text from the start of id2 up to (but not including) the alias ident.
|
-- Shape 3: alias is the last ident before `;`; the underlying span is the trimmed text from the start of id2 up to (but not including) the alias ident.
|
||||||
local underlying_span = source:sub(after_typedef, last_ident_pos - 1) ---@type string
|
local underlying_span = source:sub(after_typedef, last_ident_pos - 1) ---@type string
|
||||||
local underlying = duffle.trim(underlying_span) ---@type string
|
local underlying = duffle.trim(underlying_span) ---@type string
|
||||||
register_typedef_alias(underlying, last_ident, pos, line_of, out)
|
register_typedef_alias(underlying, last_ident, pos, line_of, out)
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
return last_ident_end
|
return last_ident_end
|
||||||
@@ -2593,17 +2593,17 @@ local function parse_pragma_macro(source, pos, ident_end, line_of, out)
|
|||||||
str = duffle.trim(str)
|
str = duffle.trim(str)
|
||||||
if str:sub(1, 1) ~= '"' or str:sub(-1) ~= '"' then return str_end end
|
if str:sub(1, 1) ~= '"' or str:sub(-1) ~= '"' then return str_end end
|
||||||
|
|
||||||
local inner = str:sub(2, -2) ---@type string
|
local inner = str:sub(2, -2) ---@type string
|
||||||
local space = duffle.find_byte(inner, 32, 1) ---@type integer|nil
|
local space = duffle.find_byte(inner, 32, 1) ---@type integer|nil
|
||||||
if not space then return str_end end
|
if not space then return str_end end
|
||||||
|
|
||||||
local name = inner:sub(1, space - 1) ---@type string
|
local name = inner:sub(1, space - 1) ---@type string
|
||||||
local rest = inner:sub(space + 1) ---@type integer
|
local rest = inner:sub(space + 1) ---@type integer
|
||||||
local eq = duffle.find_byte(rest, 61, 1) ---@type integer|nil
|
local eq = duffle.find_byte(rest, 61, 1) ---@type integer|nil
|
||||||
if not eq then return str_end end
|
if not eq then return str_end end
|
||||||
|
|
||||||
local key = duffle.trim(rest:sub(1, eq - 1)) ---@type string
|
local key = duffle.trim(rest:sub(1, eq - 1)) ---@type string
|
||||||
local val = duffle.trim(rest:sub(eq + 1)) ---@type string
|
local val = duffle.trim(rest:sub(eq + 1)) ---@type string
|
||||||
if key == "tape_atom words" or key == "words" then
|
if key == "tape_atom words" or key == "words" then
|
||||||
out.macros[#out.macros + 1] = { line = line_of(pos), name = name, words = tonumber(val) or 0 }
|
out.macros[#out.macros + 1] = { line = line_of(pos), name = name, words = tonumber(val) or 0 }
|
||||||
end
|
end
|
||||||
@@ -2661,7 +2661,7 @@ end
|
|||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_enum_entry(source, body, body_offset, line_of, out, entry_name, name_body_pos, value_start)
|
local function parse_enum_entry(source, body, body_offset, line_of, out, entry_name, name_body_pos, value_start)
|
||||||
-- value_start is within `body`, just past the `=`.
|
-- value_start is within `body`, just past the `=`.
|
||||||
local after_ws = duffle.skip_ws_and_cmt(body, value_start) ---@type integer
|
local after_ws = duffle.skip_ws_and_cmt(body, value_start) ---@type integer
|
||||||
local value, value_end = parse_enum_value(body, after_ws, out) ---@type integer|nil, integer
|
local value, value_end = parse_enum_value(body, after_ws, out) ---@type integer|nil, integer
|
||||||
if value == nil then return value_start end
|
if value == nil then return value_start end
|
||||||
|
|
||||||
@@ -2675,13 +2675,13 @@ local function parse_enum_entry(source, body, body_offset, line_of, out, entry_n
|
|||||||
out.atom_entry_comments[entry_name] = trailing_cmt
|
out.atom_entry_comments[entry_name] = trailing_cmt
|
||||||
end
|
end
|
||||||
|
|
||||||
local after_value = duffle.skip_ws_and_cmt(body, value_end) ---@type integer
|
local after_value = duffle.skip_ws_and_cmt(body, value_end) ---@type integer
|
||||||
local has_atom_reg, end_after_atom_reg = check_bare_atom_reg(body, after_value) ---@type boolean, integer
|
local has_atom_reg, end_after_atom_reg = check_bare_atom_reg(body, after_value) ---@type boolean, integer
|
||||||
|
|
||||||
-- Only register R_* entries whose value is followed by bare `atom_reg`.
|
-- Only register R_* entries whose value is followed by bare `atom_reg`.
|
||||||
if has_atom_reg and entry_name:byte(1) == BYTE_R and entry_name:byte(2) == BYTE_UNDERSCORE then
|
if has_atom_reg and entry_name:byte(1) == BYTE_R and entry_name:byte(2) == BYTE_UNDERSCORE then
|
||||||
local entry_source_pos = body_offset + name_body_pos - 1 ---@type integer
|
local entry_source_pos = body_offset + name_body_pos - 1 ---@type integer
|
||||||
local entry = { ---@type SiteCarrier
|
local entry = { ---@type SiteCarrier
|
||||||
name = entry_name,
|
name = entry_name,
|
||||||
code = value,
|
code = value,
|
||||||
source_line = line_of(entry_source_pos),
|
source_line = line_of(entry_source_pos),
|
||||||
@@ -2690,7 +2690,7 @@ local function parse_enum_entry(source, body, body_offset, line_of, out, entry_n
|
|||||||
has_atom_reg = true,
|
has_atom_reg = true,
|
||||||
}
|
}
|
||||||
-- Adjacent enum-site default view, if any. Tolerant of malformed `atom_type(...)`.
|
-- Adjacent enum-site default view, if any. Tolerant of malformed `atom_type(...)`.
|
||||||
local after_atom_reg = duffle.skip_ws_and_cmt(body, end_after_atom_reg) ---@type integer
|
local after_atom_reg = duffle.skip_ws_and_cmt(body, end_after_atom_reg) ---@type integer
|
||||||
local dflt_type_name, dflt_depth, end_after_atom_type = parse_enum_atom_type_default(body, after_atom_reg) ---@type string|nil, integer, integer
|
local dflt_type_name, dflt_depth, end_after_atom_type = parse_enum_atom_type_default(body, after_atom_reg) ---@type string|nil, integer, integer
|
||||||
if dflt_type_name then
|
if dflt_type_name then
|
||||||
entry.default_type = dflt_type_name
|
entry.default_type = dflt_type_name
|
||||||
@@ -2716,7 +2716,7 @@ end
|
|||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function parse_enum_body(source, body, body_offset, line_of, out)
|
local function parse_enum_body(source, body, body_offset, line_of, out)
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local body_len = #body ---@type integer
|
local body_len = #body ---@type integer
|
||||||
while pos <= body_len do
|
while pos <= body_len do
|
||||||
pos = duffle.skip_ws_and_cmt(body, pos)
|
pos = duffle.skip_ws_and_cmt(body, pos)
|
||||||
@@ -2773,7 +2773,7 @@ local function parse_enum(source, pos, ident_end, line_of, out)
|
|||||||
-- pos points at the `e` of `enum`; ident_end points past `enum`.
|
-- pos points at the `e` of `enum`; ident_end points past `enum`.
|
||||||
-- Optional enum tag (e.g. `enum Foo { ... }`): a single ident between `enum` and `{` that is not followed by `(`.
|
-- Optional enum tag (e.g. `enum Foo { ... }`): a single ident between `enum` and `{` that is not followed by `(`.
|
||||||
local after_ident = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local after_ident = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
local tag_ident, tag_end = duffle.read_ident(source, after_ident) ---@type string|nil, integer
|
local tag_ident, tag_end = duffle.read_ident(source, after_ident) ---@type string|nil, integer
|
||||||
if tag_ident and source:byte(tag_end) ~= 0x28 then -- not '('
|
if tag_ident and source:byte(tag_end) ~= 0x28 then -- not '('
|
||||||
after_ident = tag_end
|
after_ident = tag_end
|
||||||
end
|
end
|
||||||
@@ -2814,14 +2814,14 @@ end
|
|||||||
local function parse_addrs_assign(source, pos, ident_end, line_of, out)
|
local function parse_addrs_assign(source, pos, ident_end, line_of, out)
|
||||||
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
if source:sub(after, after) ~= "[" then return ident_end end
|
if source:sub(after, after) ~= "[" then return ident_end end
|
||||||
local inner, after_br = duffle.read_brackets(source, after) ---@type string|nil, integer
|
local inner, after_br = duffle.read_brackets(source, after) ---@type string|nil, integer
|
||||||
local idx = inner and tonumber(duffle.trim(inner)) ---@type string
|
local idx = inner and tonumber(duffle.trim(inner)) ---@type string
|
||||||
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) ---@type integer
|
local rhs = duffle.skip_ws_and_cmt(source, after_br + 1) ---@type integer
|
||||||
local rhs_ident = duffle.read_ident(source, rhs) ---@type string|nil
|
local rhs_ident = duffle.read_ident(source, rhs) ---@type string|nil
|
||||||
if rhs_ident then out._addrs[idx] = rhs_ident end
|
if rhs_ident then out._addrs[idx] = rhs_ident end
|
||||||
return rhs
|
return rhs
|
||||||
end
|
end
|
||||||
@@ -2835,7 +2835,7 @@ end
|
|||||||
local function parse_tb_emit_(source, pos, ident_end, line_of, out)
|
local function parse_tb_emit_(source, pos, ident_end, line_of, out)
|
||||||
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
if source:sub(after, after) ~= "(" then return ident_end end
|
if source:sub(after, after) ~= "(" then return ident_end end
|
||||||
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
|
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
|
||||||
local name = duffle.trim(inner or ""):match("^([%w_]+)") ---@type string
|
local name = duffle.trim(inner or ""):match("^([%w_]+)") ---@type string
|
||||||
if name then
|
if name then
|
||||||
out._chain = out._chain or {}
|
out._chain = out._chain or {}
|
||||||
@@ -2853,11 +2853,11 @@ end
|
|||||||
local function parse_tb_emit(source, pos, ident_end, line_of, out)
|
local function parse_tb_emit(source, pos, ident_end, line_of, out)
|
||||||
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
local after = duffle.skip_ws_and_cmt(source, ident_end) ---@type integer
|
||||||
if source:sub(after, after) ~= "(" then return ident_end end
|
if source:sub(after, after) ~= "(" then return ident_end end
|
||||||
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
|
local inner, after_p = duffle.read_parens(source, after) ---@type string|nil, integer
|
||||||
local args = duffle.split_top_level_commas(inner or "") ---@type string[]
|
local args = duffle.split_top_level_commas(inner or "") ---@type string[]
|
||||||
local last = duffle.trim(args[#args] or "") ---@type string
|
local last = duffle.trim(args[#args] or "") ---@type string
|
||||||
local idx = last:match("^addrs%s*%[%s*(%d+)%s*%]$") ---@type integer
|
local idx = last:match("^addrs%s*%[%s*(%d+)%s*%]$") ---@type integer
|
||||||
local name ---@type string
|
local name ---@type string
|
||||||
if idx then name = out._addrs[tonumber(idx)]
|
if idx then name = out._addrs[tonumber(idx)]
|
||||||
else name = last:match("([%w_]+)$")
|
else name = last:match("([%w_]+)$")
|
||||||
end
|
end
|
||||||
@@ -2919,7 +2919,7 @@ local DECL_PARSERS = { ---@type table<string, fun(source: string, pos: integer,
|
|||||||
--- @return SourceScan
|
--- @return SourceScan
|
||||||
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) ---@type fun(pos: integer): integer
|
local line_of = duffle.LineIndex(source) ---@type fun(pos: integer): integer
|
||||||
local out = { ---@type SourceScan
|
local out = { ---@type SourceScan
|
||||||
atoms = {},
|
atoms = {},
|
||||||
raw_atoms = {},
|
raw_atoms = {},
|
||||||
binds = {},
|
binds = {},
|
||||||
@@ -2963,7 +2963,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
_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 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
local src_len = #source ---@type integer
|
local src_len = #source ---@type integer
|
||||||
|
|
||||||
while pos <= src_len do
|
while pos <= src_len do
|
||||||
@@ -2991,7 +2991,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
-- If a pending marker is still open, consume it so it cannot drift to a later declaration.
|
-- If a pending marker is still open, consume it so it cannot drift to a later declaration.
|
||||||
-- Unsupported identifiers never create marker records.
|
-- Unsupported identifiers never create marker records.
|
||||||
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
||||||
local marker = markers[#markers] ---@type DebugSkipMarker
|
local marker = markers[#markers] ---@type DebugSkipMarker
|
||||||
if marker and marker.pending then
|
if marker and marker.pending then
|
||||||
if ident == "FI_" then
|
if ident == "FI_" then
|
||||||
marker.proc_prelude = true
|
marker.proc_prelude = true
|
||||||
@@ -3003,8 +3003,8 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
local markers = out.debug_skip_markers ---@type DebugSkipMarker[]
|
||||||
local marker = markers[#markers] ---@type DebugSkipMarker
|
local marker = markers[#markers] ---@type DebugSkipMarker
|
||||||
local c = source:sub(pos, pos) ---@type string
|
local c = source:sub(pos, pos) ---@type string
|
||||||
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")
|
||||||
@@ -3081,16 +3081,16 @@ local function type_shape(entry)
|
|||||||
if type(entry) ~= "table" then return "" end
|
if type(entry) ~= "table" then return "" end
|
||||||
if entry.kind == "struct" then
|
if entry.kind == "struct" then
|
||||||
local fields = entry.fields or {} ---@type TypeField[]
|
local fields = entry.fields or {} ---@type TypeField[]
|
||||||
local parts = {} ---@type string[]
|
local parts = {} ---@type string[]
|
||||||
for _, f in ipairs(fields) do ---@type integer, TypeField
|
for _, f in ipairs(fields) do ---@type integer, TypeField
|
||||||
parts[#parts + 1] = string.format("%s:%s*%s",
|
parts[#parts + 1] = string.format("%s:%s*%s",
|
||||||
tostring(f.name), tostring(f.type_name), tostring(f.pointer_depth or 0))
|
tostring(f.name), tostring(f.type_name), tostring(f.pointer_depth or 0))
|
||||||
end
|
end
|
||||||
return "struct[" .. table.concat(parts, ",") .. "]"
|
return "struct[" .. table.concat(parts, ",") .. "]"
|
||||||
elseif entry.kind == "enum" then
|
elseif entry.kind == "enum" then
|
||||||
local fields = entry.fields or {} ---@type TypeField[]
|
local fields = entry.fields or {} ---@type TypeField[]
|
||||||
local parts = {} ---@type string[]
|
local parts = {} ---@type string[]
|
||||||
for _, f in ipairs(fields) do ---@type integer, TypeField
|
for _, f in ipairs(fields) do ---@type integer, TypeField
|
||||||
parts[#parts + 1] = string.format("%s=%s", tostring(f.name), tostring(f.value))
|
parts[#parts + 1] = string.format("%s=%s", tostring(f.name), tostring(f.value))
|
||||||
end
|
end
|
||||||
return "enum[" .. table.concat(parts, ",") .. "]"
|
return "enum[" .. table.concat(parts, ",") .. "]"
|
||||||
@@ -3109,8 +3109,8 @@ end
|
|||||||
local function bind_shape(entry)
|
local function bind_shape(entry)
|
||||||
if type(entry) ~= "table" then return "" end
|
if type(entry) ~= "table" then return "" end
|
||||||
local fields = entry.fields or {} ---@type TypeField[]
|
local fields = entry.fields or {} ---@type TypeField[]
|
||||||
local parts = {} ---@type string[]
|
local parts = {} ---@type string[]
|
||||||
for _, f in ipairs(fields) do ---@type integer, TypeField
|
for _, f in ipairs(fields) do ---@type integer, TypeField
|
||||||
parts[#parts + 1] = string.format("%s:%s*%s",
|
parts[#parts + 1] = string.format("%s:%s*%s",
|
||||||
tostring(f.name), tostring(f.type_name), tostring(f.pointer_depth or 0))
|
tostring(f.name), tostring(f.type_name), tostring(f.pointer_depth or 0))
|
||||||
end
|
end
|
||||||
@@ -3134,14 +3134,14 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function view_shape(entry)
|
local function view_shape(entry)
|
||||||
if type(entry) ~= "table" then return "" end
|
if type(entry) ~= "table" then return "" end
|
||||||
local overrides = entry.reg_type_overrides or {} ---@type table<string, RegTypeOverride>
|
local overrides = entry.reg_type_overrides or {} ---@type table<string, RegTypeOverride>
|
||||||
local keys = {} ---@type string[]
|
local keys = {} ---@type string[]
|
||||||
for k in pairs(overrides) do keys[#keys + 1] = k end ---@type string
|
for k in pairs(overrides) do keys[#keys + 1] = k end ---@type string
|
||||||
--- @param a string
|
--- @param a string
|
||||||
--- @param b string
|
--- @param b string
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
|
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
|
||||||
local parts = {} ---@type string[]
|
local parts = {} ---@type string[]
|
||||||
for _, k in ipairs(keys) do ---@type integer, string
|
for _, k in ipairs(keys) do ---@type integer, string
|
||||||
local ov = overrides[k] ---@type RegTypeOverride
|
local ov = overrides[k] ---@type RegTypeOverride
|
||||||
parts[#parts + 1] = string.format("%s=%s*%s", tostring(k),
|
parts[#parts + 1] = string.format("%s=%s*%s", tostring(k),
|
||||||
@@ -3166,8 +3166,8 @@ end
|
|||||||
--- @return string
|
--- @return string
|
||||||
local function phase_shape(entry)
|
local function phase_shape(entry)
|
||||||
if type(entry) ~= "table" then return "" end
|
if type(entry) ~= "table" then return "" end
|
||||||
local atoms = entry.atoms or {} ---@type string[]
|
local atoms = entry.atoms or {} ---@type string[]
|
||||||
local sorted = {} ---@type string[]
|
local sorted = {} ---@type string[]
|
||||||
for _, a in ipairs(atoms) do sorted[#sorted + 1] = a end ---@type integer, string
|
for _, a in ipairs(atoms) do sorted[#sorted + 1] = a end ---@type integer, string
|
||||||
--- @param a string
|
--- @param a string
|
||||||
--- @param b string
|
--- @param b string
|
||||||
@@ -3194,9 +3194,9 @@ local function merge_named_with_sites(registry, name, new_entry, site, collision
|
|||||||
registry[name].sites = { site }
|
registry[name].sites = { site }
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local existing = registry[name] ---@type SiteCarrier
|
local existing = registry[name] ---@type SiteCarrier
|
||||||
local new_shape = shape_fn(new_entry) ---@type string
|
local new_shape = shape_fn(new_entry) ---@type string
|
||||||
local old_shape = shape_fn(existing) ---@type string
|
local old_shape = shape_fn(existing) ---@type string
|
||||||
if new_shape == old_shape and new_shape ~= "" then
|
if new_shape == old_shape and new_shape ~= "" then
|
||||||
-- Identical shape: coalesce by appending the site.
|
-- Identical shape: coalesce by appending the site.
|
||||||
existing.sites = existing.sites or { build_site(existing.source_file, existing.source_line) }
|
existing.sites = existing.sites or { build_site(existing.source_file, existing.source_line) }
|
||||||
@@ -3381,7 +3381,7 @@ local SCHEMA_BODY_ERROR = { ---@type table<string, boolean> -- bag: reguse erro
|
|||||||
--- @param corpus Corpus
|
--- @param corpus Corpus
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function resolve_reg_use_schemas(corpus)
|
local function resolve_reg_use_schemas(corpus)
|
||||||
local kept = {} ---@type RegUseError[]
|
local kept = {} ---@type RegUseError[]
|
||||||
for _, err in ipairs(corpus.reg_use_errors or {}) do ---@type integer, RegUseError
|
for _, err in ipairs(corpus.reg_use_errors or {}) do ---@type integer, RegUseError
|
||||||
if not SCHEMA_BODY_ERROR[err.kind] then
|
if not SCHEMA_BODY_ERROR[err.kind] then
|
||||||
kept[#kept + 1] = err
|
kept[#kept + 1] = err
|
||||||
@@ -3466,8 +3466,8 @@ function M.run(ctx)
|
|||||||
-- Same `code_macros` table is shared with pass 2 below.
|
-- Same `code_macros` table is shared with pass 2 below.
|
||||||
for macro_name, _ in pairs(code_macro_bodies) do ---@type string, string
|
for macro_name, _ in pairs(code_macro_bodies) do ---@type string, string
|
||||||
if code_macros[macro_name] == nil then
|
if code_macros[macro_name] == nil then
|
||||||
local body = code_macro_bodies[macro_name] ---@type string
|
local body = code_macro_bodies[macro_name] ---@type string
|
||||||
local visited = { [macro_name] = true } ---@type table<string, boolean> -- bag: already-walked ident -> true
|
local visited = { [macro_name] = true } ---@type table<string, boolean> -- bag: already-walked ident -> true
|
||||||
local value = resolve_code_macro_value(body, 1, code_macros, code_macro_bodies, visited, 1) ---@type integer|nil
|
local value = resolve_code_macro_value(body, 1, code_macros, code_macro_bodies, visited, 1) ---@type integer|nil
|
||||||
if value ~= nil then code_macros[macro_name] = value end
|
if value ~= nil then code_macros[macro_name] = value end
|
||||||
end
|
end
|
||||||
|
|||||||
+264
-264
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@
|
|||||||
-- Uses `debug.getinfo` to find this file's own directory, so it works both standalone and when require'd from the orchestrator.
|
-- Uses `debug.getinfo` to find this file's own directory, so it works both standalone and when require'd from the orchestrator.
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Type declarations
|
-- Type declarations
|
||||||
|
|||||||
+21
-21
@@ -20,7 +20,7 @@
|
|||||||
-- That single statement: (a) sets `package.path` + `package.cpath`, (b) at the bottom returns `require("duffle")`.
|
-- That single statement: (a) sets `package.path` + `package.cpath`, (b) at the bottom returns `require("duffle")`.
|
||||||
-- So the dofile's return value is the duffle module.
|
-- So the dofile's return value is the duffle module.
|
||||||
local _is_entry_script = arg and arg[0] and arg[0]:match("ps1_meta%.lua$") ~= nil ---@type boolean
|
local _is_entry_script = arg and arg[0] and arg[0]:match("ps1_meta%.lua$") ~= nil ---@type boolean
|
||||||
local _bootstrap_src ---@type string
|
local _bootstrap_src ---@type string
|
||||||
if _is_entry_script then
|
if _is_entry_script then
|
||||||
_bootstrap_src = arg[0]
|
_bootstrap_src = arg[0]
|
||||||
else
|
else
|
||||||
@@ -220,7 +220,7 @@ local PASSES = { ---@type table<string, PassDescriptor>
|
|||||||
--- @param group_name string -- Build-phase group ("pre-link" | "post-link")
|
--- @param group_name string -- Build-phase group ("pre-link" | "post-link")
|
||||||
--- @return string[] -- Sorted root pass names belonging to that group
|
--- @return string[] -- Sorted root pass names belonging to that group
|
||||||
local function roots_for_group(group_name)
|
local function roots_for_group(group_name)
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for name, pass in pairs(PASSES) do ---@type string, PassDescriptor
|
for name, pass in pairs(PASSES) do ---@type string, PassDescriptor
|
||||||
if pass.groups then
|
if pass.groups then
|
||||||
for _, g in ipairs(pass.groups) do ---@type integer, string
|
for _, g in ipairs(pass.groups) do ---@type integer, string
|
||||||
@@ -284,7 +284,7 @@ local PASS_FLAG_TO_NAME = { ---@type table<string, string> -- bag: CLI flag ->
|
|||||||
--- @param args ParsedArgs
|
--- @param args ParsedArgs
|
||||||
--- @return nil
|
--- @return nil
|
||||||
local function request_all_passes(args)
|
local function request_all_passes(args)
|
||||||
local names = {} ---@type string[]
|
local names = {} ---@type string[]
|
||||||
for name in pairs(PASSES) do names[#names + 1] = name end ---@type string
|
for name in pairs(PASSES) do names[#names + 1] = name end ---@type string
|
||||||
table.sort(names)
|
table.sort(names)
|
||||||
for _, n in ipairs(names) do ---@type integer, string
|
for _, n in ipairs(names) do ---@type integer, string
|
||||||
@@ -366,7 +366,7 @@ local FLAG_VALUE_NAMES = { ---@type table<string, string> -- bag: flag -> value
|
|||||||
--- @return string
|
--- @return string
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function require_flag_value(argv, arg_idx, flag)
|
local function require_flag_value(argv, arg_idx, flag)
|
||||||
local value = argv[arg_idx + 1] ---@type string|nil
|
local value = argv[arg_idx + 1] ---@type string|nil
|
||||||
local next_known = type(value) == "string" ---@type boolean
|
local next_known = type(value) == "string" ---@type boolean
|
||||||
and (FLAG_HANDLERS[value] ~= nil or PASS_FLAG_TO_NAME[value] ~= nil)
|
and (FLAG_HANDLERS[value] ~= nil or PASS_FLAG_TO_NAME[value] ~= nil)
|
||||||
if value == nil or next_known then
|
if value == nil or next_known then
|
||||||
@@ -511,7 +511,7 @@ local function parse_args(argv)
|
|||||||
|
|
||||||
local pos = 1 ---@type integer
|
local pos = 1 ---@type integer
|
||||||
while pos <= #argv do
|
while pos <= #argv do
|
||||||
local a = argv[pos] ---@type string
|
local a = argv[pos] ---@type string
|
||||||
local handler = FLAG_HANDLERS[a] ---@type FlagHandler|nil
|
local handler = FLAG_HANDLERS[a] ---@type FlagHandler|nil
|
||||||
if handler then
|
if handler then
|
||||||
pos = handler(args, argv, pos) or pos
|
pos = handler(args, argv, pos) or pos
|
||||||
@@ -538,7 +538,7 @@ local function parse_args(argv)
|
|||||||
-- `project_root` names `<repo>`; the resolver derives `<project_root>/code` separately.
|
-- `project_root` names `<repo>`; the resolver derives `<project_root>/code` separately.
|
||||||
if not args.project_root then
|
if not args.project_root then
|
||||||
local metadata_dir = duffle.dirname(duffle.normalize_path(args.metadata)) ---@type string
|
local metadata_dir = duffle.dirname(duffle.normalize_path(args.metadata)) ---@type string
|
||||||
local code_root = duffle.dirname(metadata_dir) ---@type string
|
local code_root = duffle.dirname(metadata_dir) ---@type string
|
||||||
args.project_root = duffle.dirname(code_root)
|
args.project_root = duffle.dirname(code_root)
|
||||||
else
|
else
|
||||||
args.project_root = duffle.normalize_path(args.project_root)
|
args.project_root = duffle.normalize_path(args.project_root)
|
||||||
@@ -557,10 +557,10 @@ local function parse_args(argv)
|
|||||||
-- Post-link opt-ins (--gdb-runtime, --dwarf-injection) write output that depends on the linked ELF.
|
-- Post-link opt-ins (--gdb-runtime, --dwarf-injection) write output that depends on the linked ELF.
|
||||||
-- Without --elf the metaprogram can't satisfy those requests, so refuse loud and early.
|
-- Without --elf the metaprogram can't satisfy those requests, so refuse loud and early.
|
||||||
-- This covers the explicit --post-link batch, --dwarf-injection by itself, and --gdb-runtime by itself.
|
-- This covers the explicit --post-link batch, --dwarf-injection by itself, and --gdb-runtime by itself.
|
||||||
local flags = args.flags or {} ---@type PassFlags
|
local flags = args.flags or {} ---@type PassFlags
|
||||||
local elf_path = flags.elf_path ---@type string|nil
|
local elf_path = flags.elf_path ---@type string|nil
|
||||||
local has_elf = type(elf_path) == "string" and #elf_path > 0 ---@type boolean
|
local has_elf = type(elf_path) == "string" and #elf_path > 0 ---@type boolean
|
||||||
local post_links = flags.gdb_runtime or flags.dwarf_injection ---@type boolean
|
local post_links = flags.gdb_runtime or flags.dwarf_injection ---@type boolean
|
||||||
if post_links and not has_elf then
|
if post_links and not has_elf then
|
||||||
io.stderr:write("ps1_meta: --elf PATH is required for post-link output\n")
|
io.stderr:write("ps1_meta: --elf PATH is required for post-link output\n")
|
||||||
os.exit(EXIT_INTERNAL_ERROR)
|
os.exit(EXIT_INTERNAL_ERROR)
|
||||||
@@ -580,8 +580,8 @@ end
|
|||||||
--- @return PassCtx
|
--- @return PassCtx
|
||||||
local function build_ctx(args)
|
local function build_ctx(args)
|
||||||
local normalized_project_root = duffle.normalize_path(args.project_root) ---@type string
|
local normalized_project_root = duffle.normalize_path(args.project_root) ---@type string
|
||||||
local project_root = normalized_project_root ---@type string
|
local project_root = normalized_project_root ---@type string
|
||||||
local project_root_is_absolute = normalized_project_root:match("^%a:/") ---@type boolean
|
local project_root_is_absolute = normalized_project_root:match("^%a:/") ---@type boolean
|
||||||
or normalized_project_root:sub(1, 2) == "//"
|
or normalized_project_root:sub(1, 2) == "//"
|
||||||
or normalized_project_root:sub(1, 1) == "/"
|
or normalized_project_root:sub(1, 1) == "/"
|
||||||
if not project_root_is_absolute then
|
if not project_root_is_absolute then
|
||||||
@@ -606,13 +606,13 @@ local function build_ctx(args)
|
|||||||
else
|
else
|
||||||
local source_order = {} ---@type SourceFile[]
|
local source_order = {} ---@type SourceFile[]
|
||||||
local sources_by_path = {} ---@type table<Path, SourceFile>
|
local sources_by_path = {} ---@type table<Path, SourceFile>
|
||||||
local resolver = { ---@type SourceResolver
|
local resolver = { ---@type SourceResolver
|
||||||
resolved = {},
|
resolved = {},
|
||||||
skipped = {},
|
skipped = {},
|
||||||
shadowed = {},
|
shadowed = {},
|
||||||
}
|
}
|
||||||
for _, input_path in ipairs(args.sources) do ---@type integer, string
|
for _, input_path in ipairs(args.sources) do ---@type integer, string
|
||||||
local path = duffle.normalize_path(input_path) ---@type string
|
local path = duffle.normalize_path(input_path) ---@type string
|
||||||
local key_ok, key_or_error = pcall(duffle.canonical_path_key, path) ---@type boolean, string
|
local key_ok, key_or_error = pcall(duffle.canonical_path_key, path) ---@type boolean, string
|
||||||
if not key_ok then
|
if not key_ok then
|
||||||
error("ps1_meta: invalid --source " .. input_path .. ": " .. tostring(key_or_error), 0)
|
error("ps1_meta: invalid --source " .. input_path .. ": " .. tostring(key_or_error), 0)
|
||||||
@@ -706,9 +706,9 @@ end
|
|||||||
--- Keeping these blocks local makes the topological sort self-contained.
|
--- Keeping these blocks local makes the topological sort self-contained.
|
||||||
local function topo_sort(passes, requested_set)
|
local function topo_sort(passes, requested_set)
|
||||||
-- Dependency closure: include every pass transitively required by `requested_set`.
|
-- Dependency closure: include every pass transitively required by `requested_set`.
|
||||||
local needed = {} ---@type table<string, boolean> -- bag: pass name -> needed
|
local needed = {} ---@type table<string, boolean> -- bag: pass name -> needed
|
||||||
for _, name in ipairs(requested_set) do needed[name] = true end ---@type integer, string
|
for _, name in ipairs(requested_set) do needed[name] = true end ---@type integer, string
|
||||||
local changed = true ---@type boolean
|
local changed = true ---@type boolean
|
||||||
while changed do
|
while changed do
|
||||||
changed = false
|
changed = false
|
||||||
for name, _ in pairs(needed) do ---@type string, boolean
|
for name, _ in pairs(needed) do ---@type string, boolean
|
||||||
@@ -724,7 +724,7 @@ local function topo_sort(passes, requested_set)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- In-degree calculation: count each needed pass's needed dependencies.
|
-- In-degree calculation: count each needed pass's needed dependencies.
|
||||||
local in_degree = {} ---@type table<string, integer> -- bag: pass name -> in-degree
|
local in_degree = {} ---@type table<string, integer> -- bag: pass name -> in-degree
|
||||||
for name, _ in pairs(needed) do in_degree[name] = 0 end ---@type string, boolean
|
for name, _ in pairs(needed) do in_degree[name] = 0 end ---@type string, boolean
|
||||||
for name, _ in pairs(needed) do ---@type string, boolean
|
for name, _ in pairs(needed) do ---@type string, boolean
|
||||||
for _, dep in ipairs(passes[name].deps) do ---@type integer, string
|
for _, dep in ipairs(passes[name].deps) do ---@type integer, string
|
||||||
@@ -735,7 +735,7 @@ local function topo_sort(passes, requested_set)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Ready-queue seeding: add zero-in-degree passes in deterministic order.
|
-- Ready-queue seeding: add zero-in-degree passes in deterministic order.
|
||||||
local ready = {} ---@type string[]
|
local ready = {} ---@type string[]
|
||||||
for name, deg in pairs(in_degree) do ---@type string, integer
|
for name, deg in pairs(in_degree) do ---@type string, integer
|
||||||
if deg == 0 then ready[#ready + 1] = name end
|
if deg == 0 then ready[#ready + 1] = name end
|
||||||
end
|
end
|
||||||
@@ -765,8 +765,8 @@ local function topo_sort(passes, requested_set)
|
|||||||
-- Cycle detection: if `order` doesn't include all needed passes, some are stuck with in_degree > 0
|
-- Cycle detection: if `order` doesn't include all needed passes, some are stuck with in_degree > 0
|
||||||
-- (the cycle closed on itself before Kahn could process them).
|
-- (the cycle closed on itself before Kahn could process them).
|
||||||
-- Without this check, a fully-closed cycle (e.g. A -> B -> A) would silently return an empty order list, leaving the orchestrator to dispatch nothing.
|
-- Without this check, a fully-closed cycle (e.g. A -> B -> A) would silently return an empty order list, leaving the orchestrator to dispatch nothing.
|
||||||
local needed_count = 0 ---@type integer
|
local needed_count = 0 ---@type integer
|
||||||
for _ in pairs(needed) do needed_count = needed_count + 1 end ---@type string -- count hash entries; Lua's #t doesn't work
|
for _ in pairs(needed) do needed_count = needed_count + 1 end ---@type string -- count hash entries; Lua's #t doesn't work
|
||||||
if #order ~= needed_count then
|
if #order ~= needed_count then
|
||||||
for name, deg in pairs(in_degree) do ---@type string, integer
|
for name, deg in pairs(in_degree) do ---@type string, integer
|
||||||
if deg > 0 then
|
if deg > 0 then
|
||||||
@@ -802,11 +802,11 @@ end
|
|||||||
--- @param order string[]
|
--- @param order string[]
|
||||||
--- @return boolean -- true if any validation errors were reported
|
--- @return boolean -- true if any validation errors were reported
|
||||||
local function dispatch_passes(ctx, order)
|
local function dispatch_passes(ctx, order)
|
||||||
local had_errors = false ---@type boolean
|
local had_errors = false ---@type boolean
|
||||||
for _, pass_name in ipairs(order) do ---@type integer, string
|
for _, pass_name in ipairs(order) do ---@type integer, string
|
||||||
local pass = PASSES[pass_name] ---@type PassDescriptor
|
local pass = PASSES[pass_name] ---@type PassDescriptor
|
||||||
local mod = require(pass.module) ---@type PassModule
|
local mod = require(pass.module) ---@type PassModule
|
||||||
local result = mod.run(ctx) ---@type PassResult
|
local result = mod.run(ctx) ---@type PassResult
|
||||||
if report_validation_errors(pass_name, pass, result) then
|
if report_validation_errors(pass_name, pass, result) then
|
||||||
had_errors = true
|
had_errors = true
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user