mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
Lua metaprogram support for RegUse_ (needs review)
This commit is contained in:
+89
-7
@@ -2236,6 +2236,36 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local invocation_stack = {} -- stack of currently-open invocation records
|
local invocation_stack = {} -- stack of currently-open invocation records
|
||||||
local next_inv_id = 0
|
local next_inv_id = 0
|
||||||
|
|
||||||
|
local reg_use_schema = ctx_table.reg_use_schema
|
||||||
|
local reg_use_param = ctx_table.reg_use_param
|
||||||
|
local atom_name = ctx_table.atom_name
|
||||||
|
|
||||||
|
local slot_readonly = {}
|
||||||
|
if reg_use_schema then
|
||||||
|
for _, slot in ipairs(reg_use_schema.slots or {}) do
|
||||||
|
slot_readonly[slot.name] = slot.readonly == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_sub(sub_map, operand)
|
||||||
|
if sub_map and type(operand) == "string" and sub_map[operand] then
|
||||||
|
return sub_map[operand]
|
||||||
|
end
|
||||||
|
return operand
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resolve_gpr_key(operand)
|
||||||
|
if type(operand) ~= "string" then return nil end
|
||||||
|
if operand:sub(1, 2) == "R_" then return operand end
|
||||||
|
if not (reg_use_schema and reg_use_param) then return nil end
|
||||||
|
local prefix = reg_use_param .. "."
|
||||||
|
if operand:sub(1, #prefix) ~= prefix then return nil end
|
||||||
|
local member_path = operand:sub(#prefix + 1)
|
||||||
|
local slot = reg_use_schema.alias_to_slot[member_path]
|
||||||
|
if not slot then return nil, member_path end
|
||||||
|
return "reguse:" .. atom_name .. ":" .. slot, nil, slot
|
||||||
|
end
|
||||||
|
|
||||||
local function open_invocation_ids_snapshot()
|
local function open_invocation_ids_snapshot()
|
||||||
local ids = {}
|
local ids = {}
|
||||||
for _, inv in ipairs(invocation_stack) do
|
for _, inv in ipairs(invocation_stack) do
|
||||||
@@ -2246,7 +2276,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
|
|
||||||
local function emit_word(encoder, args, line, word_call_text,
|
local function emit_word(encoder, args, line, word_call_text,
|
||||||
def_source_now, def_line_now,
|
def_source_now, def_line_now,
|
||||||
immediate_call_text, root_call_text_w)
|
immediate_call_text, root_call_text_w, sub_map)
|
||||||
local inv_ids = open_invocation_ids_snapshot()
|
local inv_ids = open_invocation_ids_snapshot()
|
||||||
local outermost = inv_ids[1] or 0
|
local outermost = inv_ids[1] or 0
|
||||||
-- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text.
|
-- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text.
|
||||||
@@ -2254,6 +2284,42 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
-- The call that triggered the body expansion we're currently walking.
|
-- The call that triggered the body expansion we're currently walking.
|
||||||
local eff_call_text = immediate_call_text or word_call_text
|
local eff_call_text = immediate_call_text or word_call_text
|
||||||
local eff_root_call_text = root_call_text_w
|
local eff_root_call_text = root_call_text_w
|
||||||
|
local gpr_keys = nil
|
||||||
|
if reg_use_schema or sub_map then
|
||||||
|
gpr_keys = {}
|
||||||
|
for pos, arg in ipairs(args or {}) do
|
||||||
|
local effective = apply_sub(sub_map, arg)
|
||||||
|
local key, unresolved, slot = resolve_gpr_key(effective)
|
||||||
|
gpr_keys[pos] = key
|
||||||
|
if unresolved then
|
||||||
|
errors[#errors + 1] = {
|
||||||
|
kind = "reguse_unresolved",
|
||||||
|
line = line,
|
||||||
|
msg = string.format("RegUse operand %q does not resolve in schema %q",
|
||||||
|
effective, (reg_use_schema and reg_use_schema.name) or "?"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if key and slot and slot_readonly[slot] then
|
||||||
|
local effects = M.INSTRUCTION_GPR_EFFECTS or {}
|
||||||
|
local row = effects[encoder]
|
||||||
|
if row and row.writes then
|
||||||
|
for _, wpos in ipairs(row.writes) do
|
||||||
|
if wpos == pos then
|
||||||
|
errors[#errors + 1] = {
|
||||||
|
kind = "reguse_const_write",
|
||||||
|
line = line,
|
||||||
|
msg = string.format("RegUse slot %q is Reg const; %s writes it",
|
||||||
|
slot, encoder),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not reg_use_schema then
|
||||||
|
gpr_keys = nil
|
||||||
|
end
|
||||||
items[#items + 1] = {
|
items[#items + 1] = {
|
||||||
kind = "word",
|
kind = "word",
|
||||||
encoder = encoder,
|
encoder = encoder,
|
||||||
@@ -2265,6 +2331,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
root_call_text = eff_root_call_text,
|
root_call_text = eff_root_call_text,
|
||||||
invocation_ids = inv_ids,
|
invocation_ids = inv_ids,
|
||||||
outermost_invocation_id = outermost,
|
outermost_invocation_id = outermost,
|
||||||
|
gpr_keys = gpr_keys,
|
||||||
}
|
}
|
||||||
word_events[#word_events + 1] = {
|
word_events[#word_events + 1] = {
|
||||||
i = word_idx,
|
i = word_idx,
|
||||||
@@ -2277,6 +2344,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
invocation_ids = inv_ids,
|
invocation_ids = inv_ids,
|
||||||
outermost_invocation_id = outermost,
|
outermost_invocation_id = outermost,
|
||||||
word_count = 1,
|
word_count = 1,
|
||||||
|
gpr_keys = gpr_keys,
|
||||||
}
|
}
|
||||||
word_idx = word_idx + 1
|
word_idx = word_idx + 1
|
||||||
end
|
end
|
||||||
@@ -2520,6 +2588,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local line_of = body_entry.line_of or M.LineIndex("")
|
local line_of = body_entry.line_of or M.LineIndex("")
|
||||||
local def_source = body_entry.source or ""
|
local def_source = body_entry.source or ""
|
||||||
local def_line = body_entry.declaration or 0
|
local def_line = body_entry.declaration or 0
|
||||||
|
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.
|
||||||
local function process_token(bt)
|
local function process_token(bt)
|
||||||
@@ -2575,12 +2644,22 @@ 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)
|
||||||
|
local formal_names = ctx_table.component_index[bare]
|
||||||
|
and ctx_table.component_index[bare].arg_names
|
||||||
|
local child_map = nil
|
||||||
|
if formal_names then
|
||||||
|
child_map = {}
|
||||||
|
for i, fname in ipairs(formal_names) do
|
||||||
|
child_map[fname] = apply_sub(sub_map, args[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
walk_body_entry({
|
walk_body_entry({
|
||||||
body_tokens = comp.body_tokens or {},
|
body_tokens = comp.body_tokens or {},
|
||||||
body_off = comp.body_off or 0,
|
body_off = comp.body_off or 0,
|
||||||
line_of = comp.line_of,
|
line_of = comp.line_of,
|
||||||
source = comp.source,
|
source = comp.source,
|
||||||
declaration = comp.declaration,
|
declaration = comp.declaration,
|
||||||
|
sub_map = child_map,
|
||||||
},
|
},
|
||||||
inv.id,
|
inv.id,
|
||||||
invocation_root_call_text,
|
invocation_root_call_text,
|
||||||
@@ -2618,7 +2697,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local n = resolve_count(ident, tok_line)
|
local n = resolve_count(ident, tok_line)
|
||||||
local out_ident = (ident == "nop2") and "nop" or ident
|
local out_ident = (ident == "nop2") and "nop" or ident
|
||||||
for _ = 1, n do
|
for _ = 1, n do
|
||||||
emit_word(out_ident, args, tok_line, tok, def_source, def_line, walk_immediate_call_text, walk_root_call_text)
|
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
|
||||||
|
|
||||||
@@ -2683,7 +2762,7 @@ end
|
|||||||
--- @param components table -- bare-name → component definition (corpus.components); REQUIRED — consumed at the invocation-construction site to stamp
|
--- @param components table -- bare-name → component definition (corpus.components); REQUIRED — consumed at the invocation-construction site to stamp
|
||||||
--- `invocation.debug_skip`. A missing or non-table `components` raises a fail-loud error rather than silently falling back.
|
--- `invocation.debug_skip`. A missing or non-table `components` raises a fail-loud error rather than silently falling back.
|
||||||
--- @return EmissionProjection
|
--- @return EmissionProjection
|
||||||
function M.project_emission(body_text, component_index, word_counts, components)
|
function M.project_emission(body_text, component_index, word_counts, components, reg_use_ctx)
|
||||||
-- The recursive walk delegates to `_project_emission_inner` so component bodies (which arrive as
|
-- The recursive walk delegates to `_project_emission_inner` so component bodies (which arrive as
|
||||||
-- `{body_tokens, body_off, line_of, source, declaration}` records from `corpus.component_body_index`)
|
-- `{body_tokens, body_off, line_of, source, declaration}` records from `corpus.component_body_index`)
|
||||||
-- re-enter the same walker with the same shared output state.
|
-- re-enter the same walker with the same shared output state.
|
||||||
@@ -2726,6 +2805,10 @@ function M.project_emission(body_text, component_index, word_counts, components)
|
|||||||
component_index = component_index or {},
|
component_index = component_index or {},
|
||||||
word_counts = word_counts or {},
|
word_counts = word_counts or {},
|
||||||
components = components,
|
components = components,
|
||||||
|
reg_use_schema = reg_use_ctx and reg_use_ctx.reg_use_schema,
|
||||||
|
reg_use_param = reg_use_ctx and reg_use_ctx.reg_use_param,
|
||||||
|
atom_name = reg_use_ctx and reg_use_ctx.atom_name,
|
||||||
|
schema_name = reg_use_ctx and reg_use_ctx.schema_name,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2860,13 +2943,12 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
|
|||||||
if source:sub(next_pos, next_pos) == "(" then
|
if source:sub(next_pos, next_pos) == "(" then
|
||||||
local inner = M.read_parens(source, next_pos)
|
local inner = M.read_parens(source, next_pos)
|
||||||
if inner then
|
if inner then
|
||||||
-- strip the _proc suffix to get the atom name
|
|
||||||
local proc_suffix = "_proc"
|
local proc_suffix = "_proc"
|
||||||
|
local atom_name = ident
|
||||||
if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then
|
if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then
|
||||||
return ident:sub(1, #ident - #proc_suffix), inner
|
atom_name = ident:sub(1, #ident - #proc_suffix)
|
||||||
end
|
end
|
||||||
-- no _proc suffix — return as-is
|
return atom_name, inner, ident
|
||||||
return ident, inner
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- ident not followed by "(" — it's a qualifier; skip it
|
-- ident not followed by "(" — it's a qualifier; skip it
|
||||||
|
|||||||
@@ -159,6 +159,14 @@ local function extract_arg_names(args_str)
|
|||||||
return names
|
return names
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function formal_arg_names(args_str)
|
||||||
|
local names = extract_arg_names(args_str)
|
||||||
|
if not names then return nil end
|
||||||
|
if names[1] == "ab" then table.remove(names, 1) end
|
||||||
|
if #names == 0 then return nil end
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Component projection (read from pre-scanned SourceScan)
|
-- Component projection (read from pre-scanned SourceScan)
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -197,6 +205,7 @@ local function project_components(source, scan)
|
|||||||
body_off = a.body_off,
|
body_off = a.body_off,
|
||||||
body_tokens = a.body_tokens,
|
body_tokens = a.body_tokens,
|
||||||
args = args,
|
args = args,
|
||||||
|
arg_names = formal_arg_names(args),
|
||||||
comment = comment,
|
comment = comment,
|
||||||
kind = a.kind, -- "comp_bare" | "comp_proc"; provenance emitter reads this.
|
kind = a.kind, -- "comp_bare" | "comp_proc"; provenance emitter reads this.
|
||||||
debug_skip = a.debug_skip == true,
|
debug_skip = a.debug_skip == true,
|
||||||
@@ -466,18 +475,9 @@ end
|
|||||||
--- @param args_str string|nil
|
--- @param args_str string|nil
|
||||||
--- @return string
|
--- @return string
|
||||||
local function signature_from_args(args_str)
|
local function signature_from_args(args_str)
|
||||||
local arg_names = extract_arg_names(args_str)
|
local names = formal_arg_names(args_str)
|
||||||
if arg_names and #arg_names > 0 then
|
if names then
|
||||||
-- Drop the leading `ab` (atom-builder) first arg if present.
|
return table.concat(names, ", ")
|
||||||
-- Convention: `MipsAtomComp_Proc_` components always declare `ab` as the first function-arg
|
|
||||||
-- (type `MipsAtomBuilder_R`), mirroring the macro signature in `lottes_tape.h`.
|
|
||||||
if arg_names[1] == "ab" then
|
|
||||||
table.remove(arg_names, 1)
|
|
||||||
end
|
|
||||||
if #arg_names > 0 then
|
|
||||||
return table.concat(arg_names, ", ")
|
|
||||||
end
|
|
||||||
return "..." -- `ab` was the only arg; fall through to variadic
|
|
||||||
end
|
end
|
||||||
return "..."
|
return "..."
|
||||||
end
|
end
|
||||||
@@ -710,6 +710,7 @@ local function update_canonical_component_body_index(corpus, src, components, sc
|
|||||||
source = src.path,
|
source = src.path,
|
||||||
declaration = c.line,
|
declaration = c.line,
|
||||||
kind = c.kind,
|
kind = c.kind,
|
||||||
|
arg_names = c.arg_names,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -155,8 +155,28 @@ local function project_atom(atom_record, src, corpus)
|
|||||||
local body = atom_record.body or ""
|
local body = atom_record.body or ""
|
||||||
local wc = corpus.word_counts or {}
|
local wc = corpus.word_counts or {}
|
||||||
local cbi = corpus.component_body_index or {}
|
local cbi = corpus.component_body_index or {}
|
||||||
|
local schema = nil
|
||||||
|
if atom_record.reg_use_schema_name then
|
||||||
|
schema = corpus.reg_use_schemas and corpus.reg_use_schemas[atom_record.reg_use_schema_name]
|
||||||
|
end
|
||||||
-- That construction site stamps `invocation.debug_skip` while appending each record to `proj.invocations`.
|
-- That construction site stamps `invocation.debug_skip` while appending each record to `proj.invocations`.
|
||||||
local proj = duffle.project_emission(body, cbi, wc, corpus.components)
|
local proj = duffle.project_emission(body, cbi, wc, corpus.components, {
|
||||||
|
reg_use_schema = schema,
|
||||||
|
reg_use_param = atom_record.reg_use_param_name,
|
||||||
|
atom_name = atom_record.name,
|
||||||
|
schema_name = atom_record.reg_use_schema_name,
|
||||||
|
})
|
||||||
|
if atom_record.reg_use_schema_name and not schema then
|
||||||
|
proj.errors[#proj.errors + 1] = {
|
||||||
|
kind = "reguse_missing_schema",
|
||||||
|
msg = string.format("RegUse schema %q is missing", atom_record.reg_use_schema_name),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
for _, err in ipairs(corpus.reg_use_errors or {}) do
|
||||||
|
if err.schema_name == atom_record.reg_use_schema_name then
|
||||||
|
proj.errors[#proj.errors + 1] = err
|
||||||
|
end
|
||||||
|
end
|
||||||
local paths = {
|
local paths = {
|
||||||
tokens = atom_record.body_tokens or {},
|
tokens = atom_record.body_tokens or {},
|
||||||
line_in_body = duffle.build_body_line_index(body),
|
line_in_body = duffle.build_body_line_index(body),
|
||||||
|
|||||||
@@ -1416,13 +1416,49 @@ local function parse_mips_atom_proc(source, pos, ident_end, line_of, out)
|
|||||||
-- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which
|
-- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which
|
||||||
-- is now `aa`). The backward walk finds the function decl before open_paren
|
-- is now `aa`). The backward walk finds the function decl before open_paren
|
||||||
-- and strips the `_proc` suffix.
|
-- and strips the `_proc` suffix.
|
||||||
local raw_name = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN)
|
local raw_name, args_inner, func_ident = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN)
|
||||||
if not raw_name then raw_name = "?" end
|
if not raw_name then raw_name = "?" end
|
||||||
local name = strip_ac_prefix(raw_name)
|
local name = strip_ac_prefix(raw_name)
|
||||||
|
local reg_use_schema_name = nil
|
||||||
|
local reg_use_param_name = nil
|
||||||
|
if args_inner then
|
||||||
|
local arg_tokens = duffle.split_top_level_commas(args_inner)
|
||||||
|
for _, tok in ipairs(arg_tokens) do
|
||||||
|
local trimmed = duffle.trim(tok)
|
||||||
|
local schema_suffix, param = trimmed:match("RegUse_([%w_]+)%s+([%w_]+)$")
|
||||||
|
if schema_suffix then
|
||||||
|
if reg_use_schema_name then
|
||||||
|
out.reg_use_errors[#out.reg_use_errors + 1] = {
|
||||||
|
kind = "reguse_multiple_params",
|
||||||
|
schema_name = "RegUse_" .. schema_suffix,
|
||||||
|
source_line = line_of(pos),
|
||||||
|
}
|
||||||
|
else
|
||||||
|
reg_use_schema_name = "RegUse_" .. schema_suffix
|
||||||
|
reg_use_param_name = param
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
|
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
|
||||||
local body_off = open_paren + 2 + last_brace_pos
|
local body_off = open_paren + 2 + last_brace_pos
|
||||||
register_atom(out, "atom_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
register_atom(out, "atom_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
||||||
|
|
||||||
|
local entry = out.atoms[#out.atoms]
|
||||||
|
entry.reg_use_schema_name = reg_use_schema_name
|
||||||
|
entry.reg_use_param_name = reg_use_param_name
|
||||||
|
if reg_use_schema_name and func_ident then
|
||||||
|
local expected = "RegUse_" .. func_ident
|
||||||
|
if reg_use_schema_name ~= expected then
|
||||||
|
out.reg_use_errors[#out.reg_use_errors + 1] = {
|
||||||
|
kind = "reguse_name_mismatch",
|
||||||
|
schema_name = reg_use_schema_name,
|
||||||
|
func_ident = func_ident,
|
||||||
|
source_line = line_of(pos),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return after_paren
|
return after_paren
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1530,6 +1566,171 @@ local function register_typedef_alias(underlying, name, pos, line_of, out)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function parse_reg_use_schema_body(body)
|
||||||
|
local slots = {}
|
||||||
|
local alias_to_slot = {}
|
||||||
|
local slot_names = {}
|
||||||
|
local errors = {}
|
||||||
|
|
||||||
|
local function add_alias(path, slot)
|
||||||
|
if alias_to_slot[path] then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_duplicate_alias", path = path }
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
alias_to_slot[path] = slot
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_slot(name, aliases, readonly)
|
||||||
|
if slot_names[name] then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_duplicate_slot", name = name }
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
slot_names[name] = true
|
||||||
|
local slot = { name = name, aliases = aliases, readonly = readonly == true }
|
||||||
|
slots[#slots + 1] = slot
|
||||||
|
return slot
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_reg_names(text, pos)
|
||||||
|
local names = {}
|
||||||
|
while pos <= #text do
|
||||||
|
pos = duffle.skip_ws_and_cmt(text, pos)
|
||||||
|
local name, name_end = duffle.read_ident(text, pos)
|
||||||
|
if not name then return nil, pos end
|
||||||
|
names[#names + 1] = name
|
||||||
|
pos = duffle.skip_ws_and_cmt(text, name_end)
|
||||||
|
if text:sub(pos, pos) == "," then
|
||||||
|
pos = pos + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if text:sub(pos, pos) == ";" then pos = pos + 1 end
|
||||||
|
return names, pos
|
||||||
|
end
|
||||||
|
|
||||||
|
local pos = 1
|
||||||
|
while pos <= #body do
|
||||||
|
pos = duffle.skip_ws_and_cmt(body, pos)
|
||||||
|
if pos > #body then break end
|
||||||
|
local first, first_end = duffle.read_ident(body, pos)
|
||||||
|
if not first then
|
||||||
|
pos = pos + 1
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
local after = duffle.skip_ws_and_cmt(body, first_end)
|
||||||
|
if first == "const" then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
|
||||||
|
return nil, errors
|
||||||
|
elseif first == "union" then
|
||||||
|
if body:sub(after, after) ~= "{" then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
local inner, after_braces = duffle.read_braces(body, after)
|
||||||
|
if not inner then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
local members = {}
|
||||||
|
local union_readonly = nil
|
||||||
|
local inner_pos = 1
|
||||||
|
while inner_pos <= #inner do
|
||||||
|
inner_pos = duffle.skip_ws_and_cmt(inner, inner_pos)
|
||||||
|
if inner_pos > #inner then break end
|
||||||
|
local m_type, m_type_end = duffle.read_ident(inner, inner_pos)
|
||||||
|
if not m_type then
|
||||||
|
inner_pos = inner_pos + 1
|
||||||
|
goto continue_inner
|
||||||
|
end
|
||||||
|
if m_type == "const" then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
if m_type ~= "Reg" then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
local m_after = duffle.skip_ws_and_cmt(inner, m_type_end)
|
||||||
|
local m_readonly = false
|
||||||
|
local maybe_const, maybe_end = duffle.read_ident(inner, m_after)
|
||||||
|
if maybe_const == "const" then
|
||||||
|
m_readonly = true
|
||||||
|
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
|
||||||
|
end
|
||||||
|
if union_readonly == nil then
|
||||||
|
union_readonly = m_readonly
|
||||||
|
elseif union_readonly ~= m_readonly then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_mixed_const" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
local names, new_inner = parse_reg_names(inner, m_after)
|
||||||
|
if not names or #names == 0 then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
for _, n in ipairs(names) do members[#members + 1] = n end
|
||||||
|
inner_pos = new_inner
|
||||||
|
::continue_inner::
|
||||||
|
end
|
||||||
|
if #members == 0 then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
||||||
|
local inst_name, inst_end = duffle.read_ident(body, after_close)
|
||||||
|
local aliases = {}
|
||||||
|
local slot_name
|
||||||
|
if inst_name then
|
||||||
|
slot_name = inst_name
|
||||||
|
for _, m in ipairs(members) do
|
||||||
|
local path = inst_name .. "." .. m
|
||||||
|
if not add_alias(path, slot_name) then return nil, errors end
|
||||||
|
aliases[#aliases + 1] = path
|
||||||
|
end
|
||||||
|
after_close = inst_end
|
||||||
|
else
|
||||||
|
slot_name = members[1]
|
||||||
|
for _, m in ipairs(members) do
|
||||||
|
if not add_alias(m, slot_name) then return nil, errors end
|
||||||
|
aliases[#aliases + 1] = m
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not add_slot(slot_name, aliases, union_readonly) then return nil, errors end
|
||||||
|
after_close = duffle.skip_ws_and_cmt(body, after_close)
|
||||||
|
if body:sub(after_close, after_close) == ";" then after_close = after_close + 1 end
|
||||||
|
pos = after_close
|
||||||
|
elseif first == "Reg" then
|
||||||
|
local readonly = false
|
||||||
|
local maybe_const, maybe_end = duffle.read_ident(body, after)
|
||||||
|
if maybe_const == "const" then
|
||||||
|
readonly = true
|
||||||
|
after = duffle.skip_ws_and_cmt(body, maybe_end)
|
||||||
|
end
|
||||||
|
local names, new_pos = parse_reg_names(body, after)
|
||||||
|
if not names or #names == 0 then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
for _, n in ipairs(names) do
|
||||||
|
if not add_alias(n, n) then return nil, errors end
|
||||||
|
if not add_slot(n, { n }, readonly) then return nil, errors end
|
||||||
|
end
|
||||||
|
pos = new_pos
|
||||||
|
else
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
if #slots == 0 then
|
||||||
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
|
return nil, errors
|
||||||
|
end
|
||||||
|
return { slots = slots, alias_to_slot = alias_to_slot }, errors
|
||||||
|
end
|
||||||
|
|
||||||
--- Parse: `typedef` declarations.
|
--- Parse: `typedef` declarations.
|
||||||
---
|
---
|
||||||
--- Recognizes four shapes:
|
--- Recognizes four shapes:
|
||||||
@@ -1563,6 +1764,21 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
register_struct_type(body, name, pos, line_of, out)
|
register_struct_type(body, name, pos, line_of, out)
|
||||||
|
if name:sub(1, 7) == "RegUse_" then
|
||||||
|
local schema, schema_errors = parse_reg_use_schema_body(body)
|
||||||
|
if schema then
|
||||||
|
schema.name = name
|
||||||
|
schema.source_file = out._source_file
|
||||||
|
schema.source_line = line_of(pos)
|
||||||
|
out.reg_use_schemas[name] = schema
|
||||||
|
end
|
||||||
|
for _, err in ipairs(schema_errors or {}) do
|
||||||
|
err.schema_name = name
|
||||||
|
err.source_file = out._source_file
|
||||||
|
err.source_line = line_of(pos)
|
||||||
|
out.reg_use_errors[#out.reg_use_errors + 1] = err
|
||||||
|
end
|
||||||
|
end
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
return after_brace
|
return after_brace
|
||||||
|
|
||||||
@@ -1954,6 +2170,8 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
|
|||||||
-- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums.
|
-- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums.
|
||||||
-- See `propagate_type_sizes()` below.
|
-- See `propagate_type_sizes()` below.
|
||||||
type_name_registry = {},
|
type_name_registry = {},
|
||||||
|
reg_use_schemas = {},
|
||||||
|
reg_use_errors = {},
|
||||||
-- Shared `R_*_Code -> integer code` registry
|
-- Shared `R_*_Code -> integer code` registry
|
||||||
-- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver).
|
-- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver).
|
||||||
-- Stripped from `src.scan` before return.
|
-- Stripped from `src.scan` before return.
|
||||||
@@ -2186,13 +2404,15 @@ local function merge_corpus_registries(corpus)
|
|||||||
corpus.atom_auto_regs = corpus.atom_auto_regs or {}
|
corpus.atom_auto_regs = corpus.atom_auto_regs or {}
|
||||||
corpus.phase_auto_regs = corpus.phase_auto_regs or {}
|
corpus.phase_auto_regs = corpus.phase_auto_regs or {}
|
||||||
corpus.collisions = corpus.collisions or {}
|
corpus.collisions = corpus.collisions or {}
|
||||||
|
corpus.reg_use_schemas = corpus.reg_use_schemas or {}
|
||||||
|
corpus.reg_use_errors = corpus.reg_use_errors or {}
|
||||||
|
|
||||||
-- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge).
|
-- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge).
|
||||||
-- This is safe because M.run is the only writer to these tables within a single orchestrator invocation.
|
-- This is safe because M.run is the only writer to these tables within a single orchestrator invocation.
|
||||||
for _, key in ipairs({
|
for _, key in ipairs({
|
||||||
"register_alias_registry", "type_name_registry", "binds_by_name",
|
"register_alias_registry", "type_name_registry", "binds_by_name",
|
||||||
"atoms_by_name", "atom_views", "atom_ctxs", "atom_phases",
|
"atoms_by_name", "atom_views", "atom_ctxs", "atom_phases",
|
||||||
"atom_infos", "collisions",
|
"atom_infos", "collisions", "reg_use_schemas", "reg_use_errors",
|
||||||
}) do
|
}) do
|
||||||
corpus[key] = {}
|
corpus[key] = {}
|
||||||
end
|
end
|
||||||
@@ -2285,6 +2505,15 @@ local function merge_corpus_registries(corpus)
|
|||||||
for _, info in ipairs(scan.atom_infos or {}) do
|
for _, info in ipairs(scan.atom_infos or {}) do
|
||||||
corpus.atom_infos[#corpus.atom_infos + 1] = info
|
corpus.atom_infos[#corpus.atom_infos + 1] = info
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for name, schema in pairs(scan.reg_use_schemas or {}) do
|
||||||
|
if corpus.reg_use_schemas[name] == nil then
|
||||||
|
corpus.reg_use_schemas[name] = schema
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, err in ipairs(scan.reg_use_errors or {}) do
|
||||||
|
corpus.reg_use_errors[#corpus.reg_use_errors + 1] = err
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -451,6 +451,14 @@ local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function gpr_identity(event, pos)
|
||||||
|
local keys = event and event.gpr_keys
|
||||||
|
if keys and keys[pos] then return keys[pos] end
|
||||||
|
local arg = event and event.args and event.args[pos]
|
||||||
|
if type(arg) == "string" and arg:sub(1, 2) == "R_" then return arg end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
-- True iff `consumer_event` reads the GPR operand at any position the destination register occupies.
|
-- True iff `consumer_event` reads the GPR operand at any position the destination register occupies.
|
||||||
-- read_pos lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match.
|
-- read_pos lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match.
|
||||||
local function is_gpr_consumer_of(consumer_event, destination)
|
local function is_gpr_consumer_of(consumer_event, destination)
|
||||||
@@ -458,9 +466,8 @@ local function is_gpr_consumer_of(consumer_event, destination)
|
|||||||
local read_pos = duffle.OPERAND_READ_POSITIONS or {}
|
local read_pos = duffle.OPERAND_READ_POSITIONS or {}
|
||||||
local positions = read_pos[consumer_token]
|
local positions = read_pos[consumer_token]
|
||||||
if not positions then return false end
|
if not positions then return false end
|
||||||
local args = consumer_event.args or {}
|
|
||||||
for _, pos in ipairs(positions) do
|
for _, pos in ipairs(positions) do
|
||||||
if args[pos] == destination then return true end
|
if gpr_identity(consumer_event, pos) == destination then return true end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -552,6 +559,11 @@ local function is_gpr_operand(operand)
|
|||||||
return type(operand) == "string" and operand:sub(1, 2) == "R_"
|
return type(operand) == "string" and operand:sub(1, 2) == "R_"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function is_tracked_gpr(operand)
|
||||||
|
return is_gpr_operand(operand)
|
||||||
|
or (type(operand) == "string" and operand:sub(1, 7) == "reguse:")
|
||||||
|
end
|
||||||
|
|
||||||
local function constant_for_operand(gpr_values, operand)
|
local function constant_for_operand(gpr_values, operand)
|
||||||
if operand == "R_0" then return 0 end
|
if operand == "R_0" then return 0 end
|
||||||
local slot = is_gpr_operand(operand) and gpr_values[operand] or nil
|
local slot = is_gpr_operand(operand) and gpr_values[operand] or nil
|
||||||
@@ -560,13 +572,13 @@ local function constant_for_operand(gpr_values, operand)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function invalidate_gpr(gpr_values, operand)
|
local function invalidate_gpr(gpr_values, operand)
|
||||||
if is_gpr_operand(operand) and operand ~= "R_0" then
|
if is_tracked_gpr(operand) and operand ~= "R_0" then
|
||||||
gpr_values[operand] = { kind = "unknown" }
|
gpr_values[operand] = { kind = "unknown" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function store_gpr_constant(gpr_values, operand, value)
|
local function store_gpr_constant(gpr_values, operand, value)
|
||||||
if not is_gpr_operand(operand) or operand == "R_0" then return end
|
if not is_tracked_gpr(operand) or operand == "R_0" then return end
|
||||||
if value == nil then gpr_values[operand] = { kind = "unknown" }
|
if value == nil then gpr_values[operand] = { kind = "unknown" }
|
||||||
else gpr_values[operand] = { kind = "constant", value = wrap_u4(value) }
|
else gpr_values[operand] = { kind = "constant", value = wrap_u4(value) }
|
||||||
end
|
end
|
||||||
@@ -629,26 +641,31 @@ end
|
|||||||
-- Encoders without an explicit effect row conservatively invalidate every R_-prefixed operand.
|
-- Encoders without an explicit effect row conservatively invalidate every R_-prefixed operand.
|
||||||
-- Recognized value rules are evaluated before their destination is invalidated.
|
-- Recognized value rules are evaluated before their destination is invalidated.
|
||||||
-- A failed/unknown evaluation writes `{kind = "unknown"}` instead.
|
-- A failed/unknown evaluation writes `{kind = "unknown"}` instead.
|
||||||
local function apply_gpr_effects(ev_ident, ev_args, forward_state)
|
local function apply_gpr_effects(ev, forward_state)
|
||||||
|
local ev_ident = ev.encoder or ev.ident
|
||||||
|
local ev_args = ev.args or {}
|
||||||
local gpr_values = forward_state.gpr_values
|
local gpr_values = forward_state.gpr_values
|
||||||
local effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
|
local effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
|
||||||
local row = effects[ev_ident]
|
local row = effects[ev_ident]
|
||||||
if row == nil then
|
if row == nil then
|
||||||
for _, operand in ipairs(ev_args or {}) do
|
for pos, operand in ipairs(ev_args) do
|
||||||
invalidate_gpr(gpr_values, operand)
|
local key = gpr_identity(ev, pos) or operand
|
||||||
|
if type(key) == "string" and (key:sub(1, 2) == "R_" or key:sub(1, 7) == "reguse:") then
|
||||||
|
if key ~= "R_0" then gpr_values[key] = { kind = "unknown" } end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local value_rule = (duffle.GPR_VALUE_RULES or {})[ev_ident]
|
local value_rule = (duffle.GPR_VALUE_RULES or {})[ev_ident]
|
||||||
local value = value_rule and evaluate_gpr_value_rule(value_rule, ev_args or {}, gpr_values) or nil
|
local value = value_rule and evaluate_gpr_value_rule(value_rule, ev_args, gpr_values) or nil
|
||||||
for _, position in ipairs(row.writes or {}) do
|
for _, position in ipairs(row.writes or {}) do
|
||||||
local destination = ev_args and ev_args[position]
|
local destination = gpr_identity(ev, position)
|
||||||
if is_gpr_operand(destination) then
|
if destination then
|
||||||
if value_rule and position == value_rule.dest and value ~= nil then
|
if value_rule and position == value_rule.dest and value ~= nil then
|
||||||
store_gpr_constant(gpr_values, destination, value)
|
store_gpr_constant(gpr_values, destination, value)
|
||||||
else
|
else
|
||||||
invalidate_gpr(gpr_values, destination)
|
if destination ~= "R_0" then gpr_values[destination] = { kind = "unknown" } end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -953,7 +970,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ── 2. Apply GPR value effects. ──
|
-- ── 2. Apply GPR value effects. ──
|
||||||
apply_gpr_effects(ev_ident, ev_args, forward)
|
apply_gpr_effects(ev, forward)
|
||||||
|
|
||||||
-- ── 3. Stage producers created by this event. ──
|
-- ── 3. Stage producers created by this event. ──
|
||||||
local rows = rows_by_token[ev_ident]
|
local rows = rows_by_token[ev_ident]
|
||||||
@@ -962,7 +979,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
-- `stage = false` rows document a direction but do not create a later command-input producer (SWC2 and ordinary MTC0).
|
-- `stage = false` rows document a direction but do not create a later command-input producer (SWC2 and ordinary MTC0).
|
||||||
if row.stage ~= false then
|
if row.stage ~= false then
|
||||||
local dest_arg = row.writes and row.writes.arg
|
local dest_arg = row.writes and row.writes.arg
|
||||||
local destination = dest_arg and ev_args[dest_arg] or nil
|
local destination = dest_arg and (gpr_identity(ev, dest_arg) or ev_args[dest_arg]) or nil
|
||||||
if destination then
|
if destination then
|
||||||
-- Apply the destination_match filter when present.
|
-- Apply the destination_match filter when present.
|
||||||
if row.destination_match and row.destination_match ~= destination then
|
if row.destination_match and row.destination_match ~= destination then
|
||||||
@@ -1352,7 +1369,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
for _, row in ipairs(relations_table) do
|
for _, row in ipairs(relations_table) do
|
||||||
if row.token == ev_ident and row.stage ~= false then
|
if row.token == ev_ident and row.stage ~= false then
|
||||||
local dest_arg = row.writes and row.writes.arg
|
local dest_arg = row.writes and row.writes.arg
|
||||||
local destination = dest_arg and ev_args[dest_arg] or nil
|
local destination = dest_arg and (gpr_identity(ev, dest_arg) or ev_args[dest_arg]) or nil
|
||||||
if destination and (not row.destination_match or row.destination_match == destination) then
|
if destination and (not row.destination_match or row.destination_match == destination) then
|
||||||
local required = row.visibility and row.visibility.required
|
local required = row.visibility and row.visibility.required
|
||||||
if required == nil and not (row.visibility and row.visibility.kind == "unknown_consumer") then
|
if required == nil and not (row.visibility and row.visibility.kind == "unknown_consumer") then
|
||||||
@@ -1517,11 +1534,12 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
|
|||||||
-- Use `net_reads` to ignore RMW positions (write shadows read within the same instruction).
|
-- Use `net_reads` to ignore RMW positions (write shadows read within the same instruction).
|
||||||
if not is_load then
|
if not is_load then
|
||||||
for _, pos in ipairs(net_reads(event_ident, args)) do
|
for _, pos in ipairs(net_reads(event_ident, args)) do
|
||||||
local reg = args[pos]
|
local reg = gpr_identity(event, pos)
|
||||||
if type(reg) == "string" and reg:sub(1, 2) == "R_" then
|
if reg then
|
||||||
local until_idx = volatile_until[reg]
|
local until_idx = volatile_until[reg]
|
||||||
if until_idx and event_idx <= until_idx then
|
if until_idx and event_idx <= until_idx then
|
||||||
local ev_line = line_for_word_event(event)
|
local ev_line = line_for_word_event(event)
|
||||||
|
local authored = args[pos] or reg
|
||||||
findings[#findings + 1] = {
|
findings[#findings + 1] = {
|
||||||
atom = atom.name,
|
atom = atom.name,
|
||||||
line = ev_line,
|
line = ev_line,
|
||||||
@@ -1530,7 +1548,7 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
|
|||||||
msg = string.format("%s at line %d reads %s at word %d, but a prior load's "
|
msg = string.format("%s at line %d reads %s at word %d, but a prior load's "
|
||||||
.. "delay slot is not over until word %d; insert a `nop` between the "
|
.. "delay slot is not over until word %d; insert a `nop` between the "
|
||||||
.. "load and this instruction.",
|
.. "load and this instruction.",
|
||||||
atom.name, ev_line, reg, event_idx, until_idx),
|
atom.name, ev_line, authored, event_idx, until_idx),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1541,8 +1559,8 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
|
|||||||
local effect = gpr_effects[event_ident]
|
local effect = gpr_effects[event_ident]
|
||||||
if effect and effect.writes then
|
if effect and effect.writes then
|
||||||
for _, pos in ipairs(effect.writes) do
|
for _, pos in ipairs(effect.writes) do
|
||||||
local reg = args[pos]
|
local reg = gpr_identity(event, pos)
|
||||||
if type(reg) == "string" and reg:sub(1, 2) == "R_" then
|
if reg then
|
||||||
if is_load then
|
if is_load then
|
||||||
-- Load: destination volatile for exactly 1 slot (the delay slot).
|
-- Load: destination volatile for exactly 1 slot (the delay slot).
|
||||||
volatile_until[reg] = event_idx + 1
|
volatile_until[reg] = event_idx + 1
|
||||||
|
|||||||
Reference in New Issue
Block a user