mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-24 00:17:51 +00:00
adding comments for scan progress
This commit is contained in:
@@ -316,6 +316,7 @@ local function parse_pragma_operator(source, ident_pos, after_ident)
|
||||
return nil, open_paren + 1
|
||||
end
|
||||
local str, str_end = read_parens(source, open_paren)
|
||||
-- scan: _Pragma(<string>)
|
||||
str = trim(str)
|
||||
if str:sub(1, 1) ~= '"' or str:sub(-1) ~= '"' then
|
||||
return nil, str_end
|
||||
@@ -350,6 +351,7 @@ local function parse_pragma_directive(source, ident_pos, after_ident)
|
||||
eol = eol + 1
|
||||
end
|
||||
local line_text = trim(source:sub(rest_start, eol - 1))
|
||||
-- scan: #pragma <mac_name> tape_atom words=<N>
|
||||
local tokens = split_ws(line_text)
|
||||
local entry
|
||||
if #tokens >= 3 and tokens[2] == "tape_atom" and tokens[3]:sub(1, WORDS_KEY_PREFIX_LEN) == WORDS_KEY_PREFIX then
|
||||
@@ -388,24 +390,27 @@ local function find_macro_word_annotations(source)
|
||||
if source:byte(pos) == 35 then -- '#'
|
||||
pos = skip_preprocessor_line(source, pos)
|
||||
else
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == PRAGMA_OPERATOR then
|
||||
local entry, new_pos = parse_pragma_operator(source, pos, after_ident)
|
||||
if entry then
|
||||
local line_of = duffle.LineIndex(source)
|
||||
entry.line = line_of(pos)
|
||||
out[#out + 1] = entry
|
||||
end
|
||||
pos = new_pos
|
||||
elseif ident == PRAGMA_IDENT then
|
||||
local entry, new_pos = parse_pragma_directive(source, pos, after_ident)
|
||||
if entry then out[#out + 1] = entry end
|
||||
pos = new_pos
|
||||
else
|
||||
pos = after_ident
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == PRAGMA_OPERATOR then
|
||||
-- scan: _Pragma(...)
|
||||
local entry, new_pos = parse_pragma_operator(source, pos, after_ident)
|
||||
if entry then
|
||||
local line_of = duffle.LineIndex(source)
|
||||
entry.line = line_of(pos)
|
||||
out[#out + 1] = entry
|
||||
end
|
||||
pos = new_pos
|
||||
elseif ident == PRAGMA_IDENT then
|
||||
-- scan: #pragma <mac_name> tape_atom words=<N>
|
||||
local entry, new_pos = parse_pragma_directive(source, pos, after_ident)
|
||||
if entry then out[#out + 1] = entry end
|
||||
pos = new_pos
|
||||
else
|
||||
pos = after_ident
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
@@ -458,6 +463,7 @@ end
|
||||
local function parse_typedef_binds(source, ident_pos, after_typedef, line_of)
|
||||
local after_type = skip_ws_and_cmt(source, after_typedef)
|
||||
local type_ident, after_type_ident = read_ident(source, after_type)
|
||||
-- scan: typedef <type_ident>
|
||||
if type_ident ~= STRUCT_TYPE then
|
||||
return nil, after_type_ident or (after_type + 1)
|
||||
end
|
||||
@@ -468,12 +474,15 @@ local function parse_typedef_binds(source, ident_pos, after_typedef, line_of)
|
||||
end
|
||||
|
||||
local inner, after_paren = read_parens(source, open_paren)
|
||||
-- scan: typedef Struct_(<name>)
|
||||
local name = trim(inner)
|
||||
|
||||
local brace = scan_to_char(source, "{", after_paren)
|
||||
-- scan: typedef Struct_(<name>) {
|
||||
if not brace then return nil, open_paren + 1 end
|
||||
|
||||
local body, after_brace = read_braces(source, brace)
|
||||
-- scan: typedef Struct_(<name>) { <fields> }
|
||||
local fields, bytes = parse_binds_body(body)
|
||||
|
||||
-- Only emit Binds_* structs (other Struct_ typedefs are ignored).
|
||||
@@ -504,16 +513,18 @@ local function find_binds_structs(source)
|
||||
if source:byte(pos) == 35 then -- '#'
|
||||
pos = skip_preprocessor_line(source, pos)
|
||||
else
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == "typedef" then
|
||||
local binds_struct, new_pos = parse_typedef_binds(source, pos, after_ident, line_of)
|
||||
if binds_struct then out[#out + 1] = binds_struct end
|
||||
pos = new_pos
|
||||
else
|
||||
pos = after_ident
|
||||
end
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == "typedef" then
|
||||
-- scan: typedef Struct_(<name>) { <fields> }
|
||||
local binds_struct, new_pos = parse_typedef_binds(source, pos, after_ident, line_of)
|
||||
if binds_struct then out[#out + 1] = binds_struct end
|
||||
pos = new_pos
|
||||
else
|
||||
pos = after_ident
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
@@ -550,6 +561,7 @@ local function find_atom_names(source)
|
||||
if pos > str_len then break end
|
||||
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident ~= ATOM_DECL then
|
||||
@@ -560,13 +572,16 @@ local function find_atom_names(source)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = read_parens(source, open_paren)
|
||||
-- scan: MipsAtom_(<name>)
|
||||
local name, _ = read_alnum_ident(inner, 1)
|
||||
if name and name ~= "" then
|
||||
out[#out + 1] = { line = line_of(pos), name = name }
|
||||
end
|
||||
local brace = scan_to_char(source, "{", after_paren)
|
||||
-- scan: MipsAtom_(<name>) {
|
||||
if brace then
|
||||
local _, after_brace = read_braces(source, brace)
|
||||
-- scan: MipsAtom_(<name>) { <body> }
|
||||
pos = after_brace
|
||||
else
|
||||
pos = open_paren + 1
|
||||
@@ -630,12 +645,14 @@ end
|
||||
local function parse_atom_info_call(source, atom_name, after_mipsatom_paren, line_of)
|
||||
local lookahead = skip_ws_and_cmt(source, after_mipsatom_paren)
|
||||
local look_ident, look_after = read_ident(source, lookahead)
|
||||
-- scan: MipsAtom_(<name>) <look_ident>
|
||||
if look_ident ~= ATOM_INFO then return nil, after_mipsatom_paren end
|
||||
|
||||
local info_open = skip_ws_and_cmt(source, look_after)
|
||||
if source:byte(info_open) ~= BYTE_OPEN_PAREN then return nil, info_open + 1 end
|
||||
|
||||
local info_inner, info_after = read_parens(source, info_open)
|
||||
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
||||
local args = parse_atom_annot_args(info_inner)
|
||||
local entry = new_annot_entry(line_of(lookahead), ATOM_INFO, atom_name, "info")
|
||||
ANNOT_ARG_HANDLERS.info(entry, args)
|
||||
@@ -661,6 +678,7 @@ local function find_atom_annotations(source)
|
||||
pos = skip_preprocessor_line(source, pos)
|
||||
else
|
||||
local ident, after_ident = read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == ATOM_DECL then
|
||||
@@ -669,16 +687,20 @@ local function find_atom_annotations(source)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = read_parens(source, open_paren)
|
||||
-- scan: MipsAtom_(<name>)
|
||||
local name, _ = read_alnum_ident(inner, 1)
|
||||
|
||||
local entry, new_pos = parse_atom_info_call(source, name, after_paren, line_of)
|
||||
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
||||
if entry then annots[#annots + 1] = entry end
|
||||
pos = new_pos
|
||||
|
||||
-- Skip past the body { ... } if present.
|
||||
local brace = scan_to_char(source, "{", pos)
|
||||
-- scan: MipsAtom_(<name>) atom_info(...) {
|
||||
if brace then
|
||||
local _, after_brace = read_braces(source, brace)
|
||||
-- scan: MipsAtom_(<name>) atom_info(...) { <body> }
|
||||
pos = after_brace
|
||||
end
|
||||
end
|
||||
|
||||
@@ -173,7 +173,9 @@ local function find_function_args_for(source, name, before_pos)
|
||||
end
|
||||
|
||||
local open_paren = last_idx + #name -- position of "("
|
||||
-- scan: MipsAtom ac_X(
|
||||
local inner = duffle.read_parens(source, open_paren)
|
||||
-- scan: MipsAtom ac_X(<args>)
|
||||
if not inner then return nil end
|
||||
return inner
|
||||
end
|
||||
@@ -408,8 +410,10 @@ end
|
||||
--- @return Component|nil, integer -- the component + new source position
|
||||
local function make_bare_component(source, name, ident_pos, after_paren, line_of, args, comment)
|
||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||
-- scan: <ident>(<name>) {
|
||||
if not brace then return nil, after_paren + 1 end
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
-- scan: <ident>(<name>) { <body> }
|
||||
return {
|
||||
line = line_of(ident_pos),
|
||||
name = name:sub(AC_PREFIX_LEN + 1), -- strip "ac_" prefix
|
||||
@@ -449,6 +453,7 @@ local function find_component_atoms(source)
|
||||
if pos > src_len then break end
|
||||
|
||||
local ident, after_ident = duffle.read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
local is_comp = ident == ATOM_COMP or ident == ATOM_COMP_PROC
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
@@ -460,7 +465,9 @@ local function find_component_atoms(source)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
-- scan: <ident>(<args>)
|
||||
local name, body = parse_atomcomp_inner(inner)
|
||||
-- scan: <ident>(<name>) OR <ident>(<name>, { <body> })
|
||||
if not name or name:sub(1, AC_PREFIX_LEN) ~= AC_PREFIX then
|
||||
pos = open_paren + 1
|
||||
else
|
||||
@@ -468,11 +475,14 @@ local function find_component_atoms(source)
|
||||
local comment = preceding_comment_block(source, pos)
|
||||
if body == nil then
|
||||
-- Bare form: body comes from the brace block after the parens.
|
||||
-- scan: <ident>(<name>) {
|
||||
local comp, new_pos = make_bare_component(source, name, pos, after_paren, line_of, args, comment)
|
||||
-- scan: <ident>(<name>) { <body> }
|
||||
if comp then out[#out + 1] = comp end
|
||||
pos = new_pos
|
||||
else
|
||||
-- Function form: body was inside the parens.
|
||||
-- scan: <ident>(<name>, { <body> })
|
||||
out[#out + 1] = make_proc_component(name, body, pos, line_of, args, comment)
|
||||
pos = after_paren
|
||||
end
|
||||
|
||||
@@ -147,6 +147,7 @@ local function extract_ident_args(token, after_ident)
|
||||
local arg_start = duffle.skip_ws_and_cmt(token, after_ident)
|
||||
if token:sub(arg_start, arg_start) ~= "(" then return {}, nil end
|
||||
local inner, after_paren = duffle.read_parens(token, arg_start)
|
||||
-- scan: <marker>(<args>)
|
||||
|
||||
local args = {}
|
||||
local pos = 1
|
||||
@@ -236,7 +237,9 @@ local function find_marker_call_end(tok)
|
||||
pos = (nx > pos) and nx or (pos + 1)
|
||||
else
|
||||
local ident, after_ident = duffle.read_ident(tok, pos)
|
||||
-- scan: <ident>
|
||||
if ident == LABEL_MARKER or ident == OFFSET_MARKER then
|
||||
-- scan: atom_label(<name>) OR atom_offset(<tag>, <target>)
|
||||
local open_paren = duffle.skip_ws_and_cmt(tok, after_ident)
|
||||
if tok:sub(open_paren, open_paren) == "(" then
|
||||
local _, end_paren = duffle.read_parens(tok, open_paren)
|
||||
@@ -276,6 +279,7 @@ local function try_wrapped_atom(source_text, after_pos)
|
||||
local paren_pos = duffle.skip_ws_and_cmt(source_text, after_pos)
|
||||
if source_text:sub(paren_pos, paren_pos) ~= "(" then return nil end
|
||||
local inner, after_paren = duffle.read_parens(source_text, paren_pos)
|
||||
-- scan: MipsAtom_(<name>)
|
||||
|
||||
local name_start = 1
|
||||
while name_start <= #inner and duffle.is_space(inner:sub(name_start, name_start)) do
|
||||
@@ -289,8 +293,10 @@ local function try_wrapped_atom(source_text, after_pos)
|
||||
if name == "" then return nil end
|
||||
|
||||
local brace_pos = duffle.scan_to_char(source_text, "{", after_paren)
|
||||
-- scan: MipsAtom_(<name>) {
|
||||
if not brace_pos then return nil end
|
||||
local body, after_brace = duffle.read_braces(source_text, brace_pos)
|
||||
-- scan: MipsAtom_(<name>) { <body> }
|
||||
return { name = name, body = body, after_brace = after_brace }
|
||||
end
|
||||
|
||||
@@ -301,13 +307,17 @@ end
|
||||
local function try_raw_atom(source_text, after_pos)
|
||||
local next_pos = duffle.skip_ws_and_cmt(source_text, after_pos)
|
||||
local next_ident, next_after = duffle.read_ident(source_text, next_pos)
|
||||
-- scan: MipsCode <next_ident>
|
||||
if not next_ident then return nil end
|
||||
if not starts_with(next_ident, CODE_RAW_PREFIX) then return nil end
|
||||
if #next_ident <= CODE_RAW_PREFIX_LEN then return nil end
|
||||
local atom_name = next_ident:sub(CODE_RAW_PREFIX_LEN + 1)
|
||||
-- scan: MipsCode code_<name>
|
||||
local brace_pos = duffle.scan_to_char(source_text, "{", next_after)
|
||||
-- scan: MipsCode code_<name> {
|
||||
if not brace_pos then return nil end
|
||||
local body, after_brace = duffle.read_braces(source_text, brace_pos)
|
||||
-- scan: MipsCode code_<name> { <body> }
|
||||
return { name = atom_name, body = body, after_brace = after_brace }
|
||||
end
|
||||
|
||||
@@ -324,9 +334,11 @@ local function find_atoms(source_text)
|
||||
pos = skip_qualifiers(source_text, pos); if pos > src_len then break end
|
||||
|
||||
local ident, after = duffle.read_ident(source_text, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == ATOM_PREFIX then
|
||||
-- scan: MipsAtom_(<name>) { <body> }
|
||||
local atom = try_wrapped_atom(source_text, after)
|
||||
if atom then
|
||||
atoms[#atoms + 1] = { name = atom.name, body = atom.body }
|
||||
@@ -335,6 +347,7 @@ local function find_atoms(source_text)
|
||||
pos = pos + 1
|
||||
end
|
||||
elseif ident == CODE_DECL then
|
||||
-- scan: MipsCode code_<name> { <body> }
|
||||
local atom = try_raw_atom(source_text, after)
|
||||
if atom then
|
||||
atoms[#atoms + 1] = { name = atom.name, body = atom.body }
|
||||
|
||||
@@ -157,6 +157,7 @@ local function find_atom_bodies(source_text)
|
||||
pos = eol_pos + 1
|
||||
else
|
||||
local ident, ident_end = duffle.read_ident(source_text, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == "MipsAtom_"
|
||||
@@ -175,6 +176,7 @@ local function find_atom_bodies(source_text)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = duffle.read_parens(source_text, open_paren)
|
||||
-- scan: <ident>(<args>)
|
||||
|
||||
if kind == "comp_proc" then
|
||||
-- MipsAtomComp_Proc_(sym, { body })
|
||||
@@ -216,6 +218,7 @@ local function find_atom_bodies(source_text)
|
||||
-- unmatched; bail
|
||||
pos = open_paren + 1
|
||||
else
|
||||
-- scan: <ident>(<name>, { <body> })
|
||||
-- First ident in `inner` is the comp name.
|
||||
local name_match = inner:match("^%s*([%w_]+)")
|
||||
local name = name_match or "?"
|
||||
@@ -245,12 +248,15 @@ local function find_atom_bodies(source_text)
|
||||
local name_end = name_start
|
||||
while name_end <= #inner and inner:sub(name_end, name_end):match("[%w_]") do name_end = name_end + 1 end
|
||||
local name = inner:sub(name_start, name_end - 1)
|
||||
-- scan: <ident>(<name>)
|
||||
if name == "" then
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local brace = duffle.scan_to_char(source_text, "{", after_paren)
|
||||
-- scan: <ident>(<name>) {
|
||||
if brace then
|
||||
local body, after_brace = duffle.read_braces(source_text, brace)
|
||||
-- scan: <ident>(<name>) { <body> }
|
||||
local body_off = brace + 1
|
||||
out[#out + 1] = {
|
||||
line = line_of(pos),
|
||||
@@ -363,15 +369,15 @@ local function tokenize_body(body)
|
||||
end
|
||||
-- Extract token [rel .. scan-1]
|
||||
local tok = duffle.trim(body:sub(rel, scan - 1))
|
||||
if tok ~= "" then
|
||||
if tok ~= "" then
|
||||
out[#out + 1] = { tok = tok, rel = rel }
|
||||
end
|
||||
-- Move past the separator
|
||||
if scan <= len then
|
||||
scan = scan + 1
|
||||
scan = scan + 1
|
||||
-- Also skip whitespace before next token
|
||||
local w = duffle.skip_ws_and_cmt(body, scan)
|
||||
if w > scan then scan = w end
|
||||
if w > scan then scan = w end
|
||||
end
|
||||
rel = scan
|
||||
end
|
||||
@@ -404,17 +410,16 @@ local function check_gte_pipeline_fill(atoms, findings, line_of)
|
||||
-- "previous GTE state" but don't themselves count as pipeline
|
||||
-- fill.
|
||||
for _, a in ipairs(atoms) do
|
||||
local tokens = tokenize_body(a.body)
|
||||
local tokens = tokenize_body(a.body)
|
||||
local line_in_body = build_body_line_index(a.body)
|
||||
local tn = #tokens
|
||||
local ti = 1
|
||||
while ti <= tn do
|
||||
local tok = tokens[ti].tok
|
||||
local cmdw_full = tok:match("^(gte_cmdw_[%w_]+)%s*[,%)]")
|
||||
or tok:match("^(gte_cmdw_[%w_]+)%s*$")
|
||||
local cmdw_full = tok:match("^(gte_cmdw_[%w_]+)%s*[,%)]") or tok:match("^(gte_cmdw_[%w_]+)%s*$")
|
||||
if cmdw_full then
|
||||
local variant = cmdw_full:match("^gte_cmdw_(.+)$")
|
||||
local need = duffle.GTE_PIPELINE_LATENCY[cmdw_full]
|
||||
local need = duffle.GTE_PIPELINE_LATENCY[cmdw_full]
|
||||
if need == nil then
|
||||
-- alias or new gte_cmdw_<X> not yet in latency table
|
||||
local line = a.line + line_in_body[tokens[ti].rel]
|
||||
@@ -432,12 +437,12 @@ local function check_gte_pipeline_fill(atoms, findings, line_of)
|
||||
-- Count consecutive nops immediately BEFORE the cmdw
|
||||
-- token. We walk tokens[ti - n] backwards, accumulating
|
||||
-- nop_word_count, stopping at the first non-nop.
|
||||
local have = 0
|
||||
local have = 0
|
||||
local where_ti = ti - 1
|
||||
while where_ti >= 1 do
|
||||
local n = nop_word_count(tokens[where_ti].tok)
|
||||
if n == 0 then break end
|
||||
have = have + n
|
||||
have = have + n
|
||||
where_ti = where_ti - 1
|
||||
end
|
||||
if have < need then
|
||||
@@ -492,14 +497,14 @@ local function check_mac_yield_uniformity(atoms, findings)
|
||||
local tokens = tokenize_body(a.body)
|
||||
local line_in_body = build_body_line_index(a.body)
|
||||
|
||||
local count = 0
|
||||
local count = 0
|
||||
local last_idx = 0
|
||||
for tok_idx, t in ipairs(tokens) do
|
||||
local tok = t.tok
|
||||
-- Match `mac_yield(...)` or just `mac_yield`. The bareword
|
||||
-- variant is rare in modern style but tolerated.
|
||||
if tok:match("^mac_yield%s*%(") or tok == "mac_yield" then
|
||||
count = count + 1
|
||||
count = count + 1
|
||||
last_idx = tok_idx
|
||||
end
|
||||
end
|
||||
@@ -536,7 +541,7 @@ local function check_mac_yield_uniformity(atoms, findings)
|
||||
local post_non_nop = false
|
||||
for search_idx = last_idx + 1, #tokens do
|
||||
local t = tokens[search_idx].tok
|
||||
if t ~= "" and t ~= "nop" and t ~= "nop2"
|
||||
if t ~= "" and t ~= "nop" and t ~= "nop2"
|
||||
and not t:match("%,%s*nop%)%s*$") then
|
||||
post_non_nop = true
|
||||
break
|
||||
@@ -597,11 +602,13 @@ local function find_binds_structs(source_text)
|
||||
pos = eol_pos + 1
|
||||
else
|
||||
local ident, ident_end = duffle.read_ident(source_text, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == "typedef" then
|
||||
local after_typedef = duffle.skip_ws_and_cmt(source_text, ident_end)
|
||||
local id2, id2_end = duffle.read_ident(source_text, after_typedef)
|
||||
local id2, id2_end = duffle.read_ident(source_text, after_typedef)
|
||||
-- scan: typedef <id2>
|
||||
if id2 ~= "Struct_" then
|
||||
pos = id2_end or (after_typedef + 1)
|
||||
else
|
||||
@@ -610,13 +617,16 @@ local function find_binds_structs(source_text)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = duffle.read_parens(source_text, open_paren)
|
||||
-- scan: typedef Struct_(<name>)
|
||||
local name = duffle.trim(inner)
|
||||
local brace = duffle.scan_to_char(source_text, "{", after_paren)
|
||||
-- scan: typedef Struct_(<name>) {
|
||||
if not brace then
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local body, after_brace = duffle.read_braces(source_text, brace)
|
||||
local fields = {}
|
||||
-- scan: typedef Struct_(<name>) { <fields> }
|
||||
local fields = {}
|
||||
local byte_off = 0
|
||||
local body_pos = 1
|
||||
while body_pos <= #body do
|
||||
@@ -668,6 +678,7 @@ local function find_atom_info(source_text)
|
||||
pos = eol_pos + 1
|
||||
else
|
||||
local ident, ident_end = duffle.read_ident(source_text, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then
|
||||
pos = pos + 1
|
||||
elseif ident == "MipsAtom_" then
|
||||
@@ -676,6 +687,7 @@ local function find_atom_info(source_text)
|
||||
pos = open_paren + 1
|
||||
else
|
||||
local inner, after_paren = duffle.read_parens(source_text, open_paren)
|
||||
-- scan: MipsAtom_(<name>)
|
||||
local name_start = 1
|
||||
while name_start <= #inner and inner:sub(name_start, name_start):match("[%s]") do name_start = name_start + 1 end
|
||||
local name_end = name_start
|
||||
@@ -683,10 +695,12 @@ local function find_atom_info(source_text)
|
||||
local atom_name = inner:sub(name_start, name_end - 1)
|
||||
local lookahead = duffle.skip_ws_and_cmt(source_text, after_paren)
|
||||
local look_ident, look_end = duffle.read_ident(source_text, lookahead)
|
||||
-- scan: MipsAtom_(<name>) <look_ident>
|
||||
if look_ident == "atom_info" then
|
||||
local info_open = duffle.skip_ws_and_cmt(source_text, look_end)
|
||||
if source_text:sub(info_open, info_open) == "(" then
|
||||
local info_inner, info_after = duffle.read_parens(source_text, info_open)
|
||||
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
||||
local binds, reads, writes = nil, nil, nil
|
||||
local sub_pos = 1
|
||||
while sub_pos <= #info_inner do
|
||||
@@ -698,6 +712,7 @@ local function find_atom_info(source_text)
|
||||
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end)
|
||||
if info_inner:sub(sub_open, sub_open) == "(" then
|
||||
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
||||
-- scan: atom_bind(<Binds_X>)
|
||||
binds = duffle.trim(sub_inner)
|
||||
sub_pos = sub_after2
|
||||
else
|
||||
@@ -708,6 +723,7 @@ local function find_atom_info(source_text)
|
||||
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end)
|
||||
if info_inner:sub(sub_open, sub_open) == "(" then
|
||||
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
||||
-- scan: atom_reads(<regs>) OR atom_writes(<regs>)
|
||||
local regs = {}
|
||||
local sub_inner_pos = 1
|
||||
while sub_inner_pos <= #sub_inner do
|
||||
@@ -778,54 +794,56 @@ local function check_abi_handoff(atoms, atom_infos, binds_index, findings)
|
||||
local info = info_by_atom[a.name]
|
||||
if info and info.binds then
|
||||
local binds_name = info.binds
|
||||
local binds = binds_index[binds_name]
|
||||
local binds = binds_index[binds_name]
|
||||
if not binds then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "abi_handoff", kind = "error",
|
||||
msg = string.format("%s at line %d has `atom_bind(%s)` but no `typedef Struct_(%s)` declaration found in source",
|
||||
msg = string.format("%s at line %d has `atom_bind(%s)` but no `typedef Struct_(%s)` declaration found in source",
|
||||
a.name, a.line, binds_name, binds_name),
|
||||
}
|
||||
else
|
||||
local tokens = tokenize_body(a.body)
|
||||
local line_in_body = build_body_line_index(a.body)
|
||||
local tokens = tokenize_body(a.body)
|
||||
local line_in_body = build_body_line_index(a.body)
|
||||
local expected_field_seq = {}
|
||||
for _, f in ipairs(binds.fields) do expected_field_seq[#expected_field_seq + 1] = f.name end
|
||||
local found_field_seq = {}
|
||||
local found_field_set = {}
|
||||
local found_advance = false
|
||||
local bind_re = "O_%(" .. binds_name .. ",%s*([%w_]+)%s*%)"
|
||||
local found_advance = false
|
||||
local bind_re = "O_%(" .. binds_name .. ",%s*([%w_]+)%s*%)"
|
||||
for _, t in ipairs(tokens) do
|
||||
local tok = t.tok
|
||||
if tok:match("^load_word%s*%(") then
|
||||
if tok:find("R_TapePtr", 1, true) and tok:find("O_(" .. binds_name .. ",", 1, true) then
|
||||
local field = tok:match(bind_re)
|
||||
-- scan: load_word(R_*, R_TapePtr, O_(<Binds_X>, <field>))
|
||||
if field then
|
||||
found_field_seq[#found_field_seq + 1] = field
|
||||
found_field_set[field] = true
|
||||
found_field_set[field] = true
|
||||
else
|
||||
local body_line = a.line + line_in_body[t.rel]
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = body_line,
|
||||
atom = a.name, line = body_line,
|
||||
check = "abi_handoff", kind = "error",
|
||||
msg = string.format("%s at line %d has load_word(R_TapePtr, O_(%s, <non-ident>)); expected O_(%s, <field>)",
|
||||
msg = string.format("%s at line %d has load_word(R_TapePtr, O_(%s, <non-ident>)); expected O_(%s, <field>)",
|
||||
a.name, body_line, binds_name, binds_name),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if tok:find("R_TapePtr", 1, true)
|
||||
and tok:find("S_(" .. binds_name .. ")", 1, true) then
|
||||
found_advance = true
|
||||
end
|
||||
if tok:find("R_TapePtr", 1, true)
|
||||
and tok:find("S_(" .. binds_name .. ")", 1, true) then
|
||||
-- scan: add_ui_self(R_TapePtr, S_(<Binds_X>))
|
||||
found_advance = true
|
||||
end
|
||||
end
|
||||
|
||||
for _, fname in ipairs(expected_field_seq) do
|
||||
if not found_field_set[fname] then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "abi_handoff", kind = "error",
|
||||
msg = string.format("%s at line %d binds %s but never loads field `%s` from R_TapePtr (expected O_(%s, %s))",
|
||||
msg = string.format("%s at line %d binds %s but never loads field `%s` from R_TapePtr (expected O_(%s, %s))",
|
||||
a.name, a.line, binds_name, fname, binds_name, fname),
|
||||
}
|
||||
end
|
||||
@@ -835,11 +853,11 @@ local function check_abi_handoff(atoms, atom_infos, binds_index, findings)
|
||||
for field_idx = 1, #expected_field_seq do
|
||||
if found_field_seq[field_idx] ~= expected_field_seq[field_idx] then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "abi_handoff", kind = "error",
|
||||
msg = string.format("%s at line %d loads fields in wrong order: got [%s], expected [%s]",
|
||||
msg = string.format("%s at line %d loads fields in wrong order: got [%s], expected [%s]",
|
||||
a.name, a.line,
|
||||
table.concat(found_field_seq, ", "),
|
||||
table.concat(found_field_seq, ", "),
|
||||
table.concat(expected_field_seq, ", ")),
|
||||
}
|
||||
break
|
||||
@@ -849,9 +867,9 @@ local function check_abi_handoff(atoms, atom_infos, binds_index, findings)
|
||||
|
||||
if not found_advance then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "abi_handoff", kind = "error",
|
||||
msg = string.format("%s at line %d binds %s but never advances R_TapePtr by S_(%s) (= %d bytes / %d words)",
|
||||
msg = string.format("%s at line %d binds %s but never advances R_TapePtr by S_(%s) (= %d bytes / %d words)",
|
||||
a.name, a.line, binds_name, binds_name, binds.bytes, binds.bytes / 4),
|
||||
}
|
||||
end
|
||||
@@ -893,8 +911,7 @@ local function check_gpu_portstore_shape(atoms, findings)
|
||||
local tok = t.tok
|
||||
-- Match `mac_format_<shape>_color(...)` and strip `_color`
|
||||
-- to get the bare shape suffix (f3 / g4 / etc).
|
||||
local shape = tok:match("^mac_format_([%w_]+)_color%s*%(")
|
||||
or tok:match("^mac_format_([%w_]+)_color%s*$")
|
||||
local shape = tok:match("^mac_format_([%w_]+)_color%s*%(") or tok:match("^mac_format_([%w_]+)_color%s*$")
|
||||
if shape and duffle.GP0_CMD_BY_SHAPE[shape] then
|
||||
if not cmd_byte then
|
||||
cmd_byte = duffle.GP0_CMD_BY_SHAPE[shape]
|
||||
@@ -903,17 +920,17 @@ local function check_gpu_portstore_shape(atoms, findings)
|
||||
saw_format = true
|
||||
local contrib_key = "mac_format_" .. shape .. "_color"
|
||||
local n = duffle.GP0_MACRO_CONTRIB[contrib_key]
|
||||
if n then contrib = contrib + n end
|
||||
if n then contrib = contrib + n end
|
||||
end
|
||||
local gte_store = tok:match("^mac_gte_store_[%w_]+")
|
||||
if gte_store then
|
||||
local n = duffle.GP0_MACRO_CONTRIB[gte_store]
|
||||
if n then contrib = contrib + n end
|
||||
if n then contrib = contrib + n end
|
||||
end
|
||||
local ot_tag = tok:match("^mac_insert_ot_tag_([%w_]+)")
|
||||
if ot_tag then
|
||||
local n = duffle.GP0_MACRO_CONTRIB["mac_insert_ot_tag_" .. ot_tag]
|
||||
if n then contrib = contrib + n end
|
||||
if n then contrib = contrib + n end
|
||||
end
|
||||
if tok:match("^store_word%s*%(") and tok:find("R_PrimCursor", 1, true) then
|
||||
saw_prim_write = true
|
||||
@@ -923,9 +940,9 @@ local function check_gpu_portstore_shape(atoms, findings)
|
||||
if not cmd_byte then
|
||||
if saw_prim_write and not saw_format then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "gpu_portstore_shape", kind = "warning",
|
||||
msg = string.format("%s at line %d writes to R_PrimCursor via raw store_word(...)"
|
||||
msg = string.format("%s at line %d writes to R_PrimCursor via raw store_word(...)"
|
||||
.. " but uses no `mac_format_*_color`; the cmd byte + word count cannot be auto-validated."
|
||||
.. " Consider migrating to `mac_format_X_color` + `mac_gte_store_X_post_*` + `mac_insert_ot_tag_X`.",
|
||||
a.name, a.line),
|
||||
@@ -935,9 +952,9 @@ local function check_gpu_portstore_shape(atoms, findings)
|
||||
local expected = duffle.GP0_CMD_SIZE[cmd_byte]
|
||||
if contrib ~= expected then
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = cmd_line or a.line,
|
||||
atom = a.name, line = cmd_line or a.line,
|
||||
check = "gpu_portstore_shape", kind = "error",
|
||||
msg = string.format("%s at line %d emits GP0 0x%02X with %d prim word(s); expected %d (cmd 0x%02X total = %d)",
|
||||
msg = string.format("%s at line %d emits GP0 0x%02X with %d prim word(s); expected %d (cmd 0x%02X total = %d)",
|
||||
a.name, cmd_line or a.line, cmd_byte, contrib, expected, cmd_byte, expected),
|
||||
}
|
||||
end
|
||||
@@ -964,7 +981,7 @@ local function token_cycles(tok)
|
||||
local ident = tok:match("^([%w_]+)")
|
||||
if not ident then return duffle.UNKNOWN_INSTRUCTION_CYCLES, "?", true end
|
||||
local cost = duffle.INSTRUCTION_LATENCY[ident]
|
||||
if cost == nil then
|
||||
if cost == nil then
|
||||
return duffle.UNKNOWN_INSTRUCTION_CYCLES, ident, true
|
||||
end
|
||||
return cost, ident, false
|
||||
@@ -999,7 +1016,7 @@ local function find_branch_targets(tokens)
|
||||
-- branch_<cond>(rs, atom_offset(F, label)) or
|
||||
-- branch_<cond>(rs, rt, atom_offset(F, label))
|
||||
-- atom_offset's arg list is (flag, name); we want the name.
|
||||
local label = t.tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)")
|
||||
local label = t.tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)")
|
||||
targets[tok_idx] = label or false -- `false` = known branch, unknown target
|
||||
end
|
||||
end
|
||||
@@ -1026,13 +1043,13 @@ end
|
||||
--- value; included for backward-compat; double-counts
|
||||
--- the BD-slot nop relative to cycles_min/max)
|
||||
local function analyze_atom_paths(atom)
|
||||
local tokens = tokenize_body(atom.body)
|
||||
local tokens = tokenize_body(atom.body)
|
||||
local labels = find_atom_labels(tokens)
|
||||
local branches = find_branch_targets(tokens)
|
||||
|
||||
-- Pre-compute per-token cycle costs and identify terminators.
|
||||
local n = #tokens
|
||||
local costs = {}
|
||||
local n = #tokens
|
||||
local costs = {}
|
||||
local unknown_set = {}
|
||||
for tok_idx, t in ipairs(tokens) do
|
||||
local c, _, unknown = token_cycles(t.tok)
|
||||
@@ -1046,7 +1063,7 @@ local function analyze_atom_paths(atom)
|
||||
-- The yield transfers control; we don't count its cost (the next
|
||||
-- atom's prologue absorbs it).
|
||||
local function is_terminator(tok_idx)
|
||||
local tok = tokens[tok_idx].tok
|
||||
local tok = tokens[tok_idx].tok
|
||||
return tok == "mac_yield" or tok:match("^mac_yield%s*%(")
|
||||
end
|
||||
|
||||
@@ -1065,7 +1082,7 @@ local function analyze_atom_paths(atom)
|
||||
-- nil value" — distinguishing them requires the key check.
|
||||
local function is_branch(tok_idx)
|
||||
local v = branches[tok_idx]
|
||||
if v == nil then return false end
|
||||
if v == nil then return false end
|
||||
-- v is non-nil: either a string (atom_offset target) or false
|
||||
-- (literal offset, no target). Both indicate a branch.
|
||||
return true
|
||||
@@ -1106,11 +1123,11 @@ local function analyze_atom_paths(atom)
|
||||
-- scoped to the current path (to detect loops), and a count of paths.
|
||||
-- Cap recursion at MAX_PATHS to prevent runaway exploration on
|
||||
-- pathological bodies.
|
||||
local MAX_PATHS = 64
|
||||
local MAX_PATHS = 64
|
||||
local cycles_min = math.huge
|
||||
local cycles_max = -1
|
||||
local path_count = 0
|
||||
local has_loops = false
|
||||
local has_loops = false
|
||||
local function dfs(tok_idx, acc, visited)
|
||||
if path_count >= MAX_PATHS then return end
|
||||
if _G._DEBUG_DFS then
|
||||
@@ -1217,9 +1234,9 @@ local function check_per_atom_cycle_budget(atoms, findings)
|
||||
if not unknown_seen[name] then
|
||||
unknown_seen[name] = a.line
|
||||
findings[#findings + 1] = {
|
||||
atom = a.name, line = a.line,
|
||||
atom = a.name, line = a.line,
|
||||
check = "per_atom_cycle_budget", kind = "warning",
|
||||
msg = string.format("%s at line %d uses macro `%s` which is not in duffle.INSTRUCTION_LATENCY; "
|
||||
msg = string.format("%s at line %d uses macro `%s` which is not in duffle.INSTRUCTION_LATENCY; "
|
||||
.. "cycle count will be +%d per call (best-case). Add an entry to duffle.INSTRUCTION_LATENCY.",
|
||||
a.name, a.line, name, duffle.UNKNOWN_INSTRUCTION_CYCLES),
|
||||
}
|
||||
@@ -1259,14 +1276,14 @@ local function validate(ctx, src)
|
||||
local cycles_by_atom = {}
|
||||
for _, a in ipairs(atoms) do
|
||||
local p = analyze_atom_paths(a)
|
||||
a.paths = p
|
||||
a.cycles = p.cycles_max -- default for any code that reads .cycles
|
||||
a.cycles_min = p.cycles_min
|
||||
a.cycles_max = p.cycles_max
|
||||
a.cycles_full = p.cycles_full
|
||||
a.branch_count = p.branches
|
||||
a.paths = p
|
||||
a.cycles = p.cycles_max -- default for any code that reads .cycles
|
||||
a.cycles_min = p.cycles_min
|
||||
a.cycles_max = p.cycles_max
|
||||
a.cycles_full = p.cycles_full
|
||||
a.branch_count = p.branches
|
||||
a.unknown_macros = p.unknown_macros
|
||||
a.has_loops = p.has_loops
|
||||
a.has_loops = p.has_loops
|
||||
cycles_by_atom[a.name] = p
|
||||
end
|
||||
|
||||
@@ -1279,10 +1296,8 @@ local function validate(ctx, src)
|
||||
-- either severity (errors for missing nops; warnings for unknown
|
||||
-- cmdw macros not in the latency table). Bin by `kind`, not by
|
||||
-- check name.
|
||||
if f.kind == "error" then
|
||||
errors[#errors + 1] = { line = f.line, msg = f.msg }
|
||||
else
|
||||
warnings[#warnings + 1] = { line = f.line, msg = f.msg }
|
||||
if f.kind == "error" then errors [#errors + 1] = { line = f.line, msg = f.msg }
|
||||
else warnings[#warnings + 1] = { line = f.line, msg = f.msg }
|
||||
end
|
||||
end
|
||||
-- Per-source "scanned:" summary line. Includes the source basename
|
||||
@@ -1351,7 +1366,7 @@ local function emit_static_analysis_txt(ctx, src, result)
|
||||
local n_atoms, n_bare, n_proc = 0, 0, 0
|
||||
for _, a in ipairs(result.atoms) do
|
||||
n_atoms = n_atoms + 1
|
||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
||||
elseif a.kind == "comp_proc" then n_proc = n_proc + 1
|
||||
end
|
||||
end
|
||||
@@ -1410,9 +1425,8 @@ local function emit_static_analysis_txt(ctx, src, result)
|
||||
return out_path
|
||||
end
|
||||
|
||||
-- (Old per-source emit function above; kept for backward compat but no
|
||||
-- longer called from M.run. Replaced by `emit_module_static_analysis_txt`
|
||||
-- which aggregates by directory.)
|
||||
-- (Old per-source emit function above; kept for backward compat but no longer called from M.run.
|
||||
-- Replaced by `emit_module_static_analysis_txt` which aggregates by directory.)
|
||||
|
||||
--- Per-directory emit. Aggregates atoms + findings across every source
|
||||
--- in `dir_sources` and writes a single report to
|
||||
@@ -1421,7 +1435,7 @@ end
|
||||
local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, findings, errors, warnings, info)
|
||||
-- Module basename = last component of `dir` ("code/duffle" -> "duffle").
|
||||
local dir_basename = dir:match("([^/\\]+)$") or dir
|
||||
local out_path = ctx.out_root .. "/" .. dir_basename .. ".static_analysis.txt"
|
||||
local out_path = ctx.out_root .. "/" .. dir_basename .. ".static_analysis.txt"
|
||||
if ctx.dry_run then return out_path end
|
||||
duffle.ensure_dir(ctx.out_root)
|
||||
|
||||
@@ -1441,7 +1455,7 @@ local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, fin
|
||||
local n_atoms, n_bare, n_proc = 0, 0, 0
|
||||
for _, a in ipairs(atoms) do
|
||||
n_atoms = n_atoms + 1
|
||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
||||
elseif a.kind == "comp_proc" then n_proc = n_proc + 1
|
||||
end
|
||||
end
|
||||
@@ -1512,10 +1526,10 @@ local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, fin
|
||||
for _, a in ipairs(atoms) do sorted[#sorted + 1] = a end
|
||||
table.sort(sorted, function(x, y) return (x.cycles_max or 0) > (y.cycles_max or 0) end)
|
||||
for _, a in ipairs(sorted) do
|
||||
local p = a.paths or {}
|
||||
local br_count = p.branches or 0
|
||||
local path_count = p.paths or 0
|
||||
local loops_tag = p.has_loops and " [loop!]" or ""
|
||||
local p = a.paths or {}
|
||||
local br_count = p.branches or 0
|
||||
local path_count = p.paths or 0
|
||||
local loops_tag = p.has_loops and " [loop!]" or ""
|
||||
local unknown_tag = ""
|
||||
if a.unknown_macros and #a.unknown_macros > 0 then
|
||||
unknown_tag = string.format(" [unknown: %s]",
|
||||
@@ -1555,7 +1569,7 @@ local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, fin
|
||||
goto continue
|
||||
end
|
||||
local atom_count = #src_atoms
|
||||
local mn, mx = math.huge, -1
|
||||
local mn, mx = math.huge, -1
|
||||
for _, a in ipairs(src_atoms) do
|
||||
local p = a.paths or {}
|
||||
if (p.cycles_min or 0) < mn then mn = p.cycles_min or 0 end
|
||||
@@ -1575,7 +1589,7 @@ local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, fin
|
||||
end
|
||||
|
||||
-- Module-level findings summary (across all sources).
|
||||
local total_errs = #errors
|
||||
local total_errs = #errors
|
||||
local total_warns = #warnings
|
||||
add("")
|
||||
add(string.format("Module findings: %d error(s), %d warning(s)", total_errs, total_warns))
|
||||
|
||||
Reference in New Issue
Block a user