diff --git a/scripts/duffle_emit.lua b/scripts/duffle_emit.lua index 2048ab3..82e4ef9 100644 --- a/scripts/duffle_emit.lua +++ b/scripts/duffle_emit.lua @@ -268,6 +268,13 @@ local function _project_emission_inner(root_body_entry, ctx_table) if not reg_use_schema then gpr_keys = nil end + local isa = M.instr(encoder) + local isa_kind = isa and isa.kind or "unknown" + local nop_words = (encoder == "nop" and 1) or (encoder == "nop2" and 2) or 0 + local is_yield = (encoder == "mac_yield" or encoder == "mac_yield_tail") + local gp0_shape = type(encoder) == "string" + and encoder:match("^mac_format_([%w_]+)_color$") + or nil items[#items + 1] = { kind = "word", encoder = encoder, @@ -280,6 +287,11 @@ local function _project_emission_inner(root_body_entry, ctx_table) invocation_ids = inv_ids, outermost_invocation_id = outermost, gpr_keys = gpr_keys, + ident = encoder, + isa_kind = isa_kind, + nop_words = nop_words, + is_yield = is_yield, + gp0_shape = gp0_shape, } word_events[#word_events + 1] = { i = word_idx, @@ -293,6 +305,11 @@ local function _project_emission_inner(root_body_entry, ctx_table) outermost_invocation_id = outermost, word_count = 1, gpr_keys = gpr_keys, + ident = encoder, + kind = isa_kind, + nop_words = nop_words, + is_yield = is_yield, + gp0_shape = gp0_shape, } word_idx = word_idx + 1 end diff --git a/scripts/duffle_isa.lua b/scripts/duffle_isa.lua index 96f8dfd..640cb4f 100644 --- a/scripts/duffle_isa.lua +++ b/scripts/duffle_isa.lua @@ -536,6 +536,7 @@ M.HARDWARE_RELATIONS = { { id = "mtc2_gpr_visibility", semantic = "MTC2", + consumer = "cop2_input", token = "gte_mv_to_data_r", direction = "gpr_to_cop2_data", reads = { domain = "gpr", arg = 1 }, @@ -554,6 +555,7 @@ M.HARDWARE_RELATIONS = { { id = "mtc2_irgb_visibility", semantic = "MTC2", + consumer = "cop2_input", token = "gte_mv_to_data_r", direction = "gpr_to_cop2_data", reads = { domain = "gpr", arg = 1 }, @@ -572,6 +574,7 @@ M.HARDWARE_RELATIONS = { { id = "ctc2_gpr_visibility", semantic = "CTC2", + consumer = "cop2_input", token = "gte_mv_to_ctrl_r", direction = "gpr_to_cop2_control", reads = { domain = "gpr", arg = 1 }, @@ -588,6 +591,7 @@ M.HARDWARE_RELATIONS = { { id = "mfc2_gpr_visibility", semantic = "MFC2", + consumer = "gpr_read", token = "gte_mv_from_data_r", direction = "cop2_data_to_gpr", reads = { domain = "cop2.data", arg = 2 }, @@ -603,6 +607,7 @@ M.HARDWARE_RELATIONS = { { id = "cfc2_gpr_visibility", semantic = "CFC2", + consumer = "gpr_read", token = "gte_mv_from_ctrl_r", direction = "cop2_control_to_gpr", reads = { domain = "cop2.ctrl", arg = 2 }, @@ -621,6 +626,7 @@ M.HARDWARE_RELATIONS = { { id = "mfc0_gpr_visibility", semantic = "MFC0", + consumer = "gpr_read", token = "sys_mov_from_cop0", direction = "cop0_control_to_gpr", reads = { domain = "cop0.ctrl", arg = 2 }, @@ -643,6 +649,7 @@ M.HARDWARE_RELATIONS = { { id = "lwc2_to_gte_command", semantic = "LWC2_to_GTE", + consumer = "cop2_input", token = "gte_lw", direction = "memory_to_cop2_data", reads = { domain = "memory", arg = 2 }, @@ -658,6 +665,7 @@ M.HARDWARE_RELATIONS = { { id = "lwc2_to_other_consumer", semantic = "LWC2_to_other", + consumer = "cop2_input", token = "gte_lw", direction = "memory_to_cop2_data", reads = { domain = "memory", arg = 2 }, @@ -675,6 +683,7 @@ M.HARDWARE_RELATIONS = { { id = "swc2_memory_write", semantic = "SWC2", + consumer = "gpr_read", token = "gte_sw", direction = "cop2_data_to_memory", reads = { domain = "cop2.data", arg = 1 }, @@ -692,6 +701,7 @@ M.HARDWARE_RELATIONS = { { id = "mtc0_cu2_visibility", semantic = "MTC0", + consumer = "gpr_read", token = "sys_mov_to_cop0", direction = "gpr_to_cop0_status", reads = { domain = "gpr", arg = 1 }, diff --git a/scripts/passes/components.lua b/scripts/passes/components.lua index 4897cb6..7730138 100644 --- a/scripts/passes/components.lua +++ b/scripts/passes/components.lua @@ -942,7 +942,7 @@ function M.run(ctx) aggregated_components[#aggregated_components + 1] = c end if #per_source > 0 then - metadata_per_source[src] = compute_components_metadata(per_source, duffle.INSTRUCTION_LATENCY) + metadata_per_source[src] = compute_components_metadata(per_source, {}) end end if #aggregated_components > 0 then diff --git a/scripts/passes/dwarf_injection.lua b/scripts/passes/dwarf_injection.lua index a0171e2..14d2dad 100644 --- a/scripts/passes/dwarf_injection.lua +++ b/scripts/passes/dwarf_injection.lua @@ -104,6 +104,67 @@ local ABBREV_BIND_VAR_LOCLIST = 0x6D -- 109: DW_TAG_variable no children + DW_ -- (a pointer_type that carries DW_AT_byte_size + DW_AT_type), so gdb misparses our 4-byte ref4 as (byte_size, type[0..2]) and lands the cursor mid-attribute. local ABBREV_TYPED_VIEW_POINTER = 0x6E -- 110: DW_TAG_pointer_type no children + DW_AT_type = ref4 (typed-view / U4 / void chain) +-- One row per DIE kind build_inserted_children emits. +-- attrs list form + the value key filled from the atom / registry / local table. +local DIE_SCHEMA = { + base_type = { + abbrev = ABBREV_BASE_TYPE, + attrs = { + { form = "string", key = "name" }, + { form = "data1", key = "byte_size" }, + { form = "data1", key = "encoding" }, + }, + }, + abstract_subprogram = { + abbrev = ABBREV_ABSTRACT_SUBPROGRAM, + attrs = { + { form = "string", key = "name" }, + { form = "data1", key = "inline" }, + { form = "data1", key = "external" }, + { form = "udata", key = "decl_file" }, + { form = "udata", key = "decl_line" }, + }, + }, + subprogram = { + abbrev = ABBREV_SUBPROGRAM, + attrs = { + { form = "string", key = "name" }, + { form = "addr", key = "low_pc" }, + { form = "addr", key = "high_pc" }, + { form = "string", key = "linkage_name" }, + }, + }, + variable = { + abbrev = ABBREV_VARIABLE, + attrs = { + { form = "string", key = "name" }, + { form = "ref4", key = "type" }, + { form = "exprloc", key = "location" }, + }, + }, + structure_type = { + abbrev = ABBREV_STRUCT_TYPE, + attrs = { + { form = "string", key = "name" }, + { form = "data1", key = "byte_size" }, + }, + }, + member = { + abbrev = ABBREV_MEMBER, + attrs = { + { form = "string", key = "name" }, + { form = "data2", key = "data_member_location" }, + { form = "ref4", key = "type" }, + }, + }, + pointer_type = { + abbrev = ABBREV_TYPED_VIEW_POINTER, + attrs = { + { form = "ref4", key = "type" }, + }, + }, +} + -- DWARF5 §7.7.3 loclist opcodes. local DW_LLE_end_of_list = 0x00 local DW_LLE_start_length = 0x08 @@ -1581,6 +1642,26 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta S.bytes[#S.bytes + 1] = s S.next_offset = S.next_offset + #s end + local function emit_die(schema_name, values) + local row = DIE_SCHEMA[schema_name] + emit(uleb128(row.abbrev)) + for _, attr in ipairs(row.attrs) do + local v = values[attr.key] + if attr.form == "string" then + emit(v .. "\0") + elseif attr.form == "data1" then + emit(string.char(v)) + elseif attr.form == "udata" then + emit(uleb128(v)) + elseif attr.form == "addr" or attr.form == "ref4" then + emit(elf_dwarf.write_u32_le(v)) + elseif attr.form == "data2" then + emit(elf_dwarf.write_u16_le(v)) + elseif attr.form == "exprloc" then + emit(v) + end + end + end local function ref4_of(section_offset) if section_offset == nil then return 0 end return section_offset - main_cu_offset @@ -1589,10 +1670,11 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta -- 1) Emit the base_type DIE first (member ref4s reference it). local base_type_section_offset = S.next_offset - emit(uleb128(ABBREV_BASE_TYPE)) - emit("unsigned int\0") -- DW_FORM_string (DW_AT_name) - emit(string.char(4)) -- DW_FORM_data1 (DW_AT_byte_size) - emit(string.char(DW_ATE_unsigned)) -- DW_FORM_data1 (DW_AT_encoding) + emit_die("base_type", { + name = "unsigned int", + byte_size = 4, + encoding = DW_ATE_unsigned, + }) -- The function body below reads S.next_offset directly via the `next_offset` function; -- this keeps offsets synchronized with emitted data. local function next_offset() return S.next_offset end @@ -1847,13 +1929,13 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta for _, comp_name in ipairs(sorted_comp_names) do local def = component_defs[comp_name] abstract_offsets[comp_name] = next_offset() - emit(uleb128(ABBREV_ABSTRACT_SUBPROGRAM)) - emit("mac_" .. comp_name .. "\0") -- DW_FORM_string (DW_AT_name) - emit(string.char(DW_INL_inlined)) -- DW_FORM_data1 (DW_AT_inline) - emit(string.char(0x01)) -- DW_FORM_data1 (DW_AT_external=1) - -- decl_file + decl_line resolve the abstract origin back to its definition site even when no inlined_subroutine instance maps to it. - emit(uleb128(resolve_provenance_file_index(def.def_file))) -- DW_FORM_udata (DW_AT_decl_file) - emit(uleb128(def.def_line)) -- DW_FORM_udata (DW_AT_decl_line) + emit_die("abstract_subprogram", { + name = "mac_" .. comp_name, + inline = DW_INL_inlined, + external = 0x01, + decl_file = resolve_provenance_file_index(def.def_file), + decl_line = def.def_line, + }) end -- 4) Emit per-atom DW_TAG_subprograms (children of main CU). @@ -1861,11 +1943,12 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta -- The gcc global `[]` is a DW_TAG_variable without children; our subprogram has the wave-context var children. -- gdb's symbol resolution picks our subprogram (it has low_pc/high_pc + children) over the gcc global for function-context lookups. for _, atom in ipairs(atom_table) do - emit(uleb128(ABBREV_SUBPROGRAM)) - emit(atom.name .. "\0") -- DW_FORM_string (DW_AT_name) - emit(elf_dwarf.write_u32_le(atom.addr)) - emit(elf_dwarf.write_u32_le(atom.addr + atom.size_bytes)) - emit(atom.name .. "\0") -- DW_FORM_string (DW_AT_linkage_name; same as DW_AT_name for non-mangled C) + emit_die("subprogram", { + name = atom.name, + low_pc = atom.addr, + high_pc = atom.addr + atom.size_bytes, + linkage_name = atom.name, + }) -- Per debug-visible R_ alias (filtered to GPR 0..31 in `by_alias`): DW_TAG_variable. -- Precedence chain (per atom, per RR_): diff --git a/scripts/passes/scan_source.lua b/scripts/passes/scan_source.lua index cf5ad57..682bdf2 100644 --- a/scripts/passes/scan_source.lua +++ b/scripts/passes/scan_source.lua @@ -2390,131 +2390,6 @@ local DECL_PARSERS = { -- Only the bare `atom_dbg_skip` marker reaches `parse_dbg_skip_marker`. -- Unknown identifiers follow the same unrelated-token path as every other unsupported source token. -local function tape_skip_ident(ident) - return ident and (DECL_FORMS[ident] or ident == "Struct_" or ident == "Enum_") -end - -local function collect_addrs_assigns_REMOVED(text) - local addrs = {} - local pos = 1 - local n = #text - while pos <= n do - pos = duffle.skip_ws_and_cmt(text, pos) - if pos > n then break end - local ident, ident_end = duffle.read_ident(text, pos) - if ident == "addrs" then - local after = duffle.skip_ws_and_cmt(text, ident_end) - if text:sub(after, after) == "[" then - local inner, after_br = duffle.read_brackets(text, after) - local idx = inner and tonumber(duffle.trim(inner)) - after_br = duffle.skip_ws_and_cmt(text, after_br or after) - if idx and text:sub(after_br, after_br) == "=" then - local rhs = duffle.skip_ws_and_cmt(text, after_br + 1) - local rhs_ident = duffle.read_ident(text, rhs) - if rhs_ident then addrs[idx] = rhs_ident end - pos = rhs - else - pos = after_br or (after + 1) - end - else - pos = ident_end - end - elseif ident then - pos = ident_end - else - pos = pos + 1 - end - end - return addrs -end - -local function collect_tb_emits(body, addrs) - local names = {} - local pos = 1 - local n = #body - while pos <= n do - pos = duffle.skip_ws_and_cmt(body, pos) - if pos > n then break end - local ident, ident_end = duffle.read_ident(body, pos) - if ident == "tb_emit_" or ident == "tb_emit" then - local after = duffle.skip_ws_and_cmt(body, ident_end) - if body:sub(after, after) == "(" then - local inner, after_p = duffle.read_parens(body, after) - local name - if ident == "tb_emit_" then - name = duffle.trim(inner or ""):match("^([%w_]+)") - else - local args = duffle.split_top_level_commas(inner or "") - local last = duffle.trim(args[#args] or "") - local idx = last:match("^addrs%s*%[%s*(%d+)%s*%]$") - if idx then - name = addrs[tonumber(idx)] - else - name = last:match("([%w_]+)$") - end - end - if name then names[#names + 1] = name end - pos = after_p or (after + 1) - else - pos = ident_end - end - elseif ident then - pos = ident_end - else - pos = pos + 1 - end - end - return names -end - --- Linear appearance order of tb_emit / tb_emit_ in each C function body. --- Commented-out emits are skipped by skip_ws_and_cmt. No C if/loop CFG. -local function scan_tape_chains(source) - local addrs = collect_addrs_assigns(source) - local chains = {} - local pos = 1 - local n = #source - while pos <= n do - pos = duffle.skip_ws_and_cmt(source, pos) - if pos > n then break end - local ident, ident_end = duffle.read_ident(source, pos) - if tape_skip_ident(ident) then - local after = duffle.skip_ws_and_cmt(source, ident_end) - if source:sub(after, after) == "(" then - local _, after_p = duffle.read_parens(source, after) - after = duffle.skip_ws_and_cmt(source, after_p or after) - end - if source:sub(after, after) == "{" then - local _, after_b = duffle.read_braces(source, after) - pos = after_b or (after + 1) - else - pos = after - end - elseif ident then - local after = duffle.skip_ws_and_cmt(source, ident_end) - if source:sub(after, after) == "(" then - local _, after_p = duffle.read_parens(source, after) - after = duffle.skip_ws_and_cmt(source, after_p or after) - if source:sub(after, after) == "{" then - local body, after_b = duffle.read_braces(source, after) - local names = collect_tb_emits(body or "", addrs) - if #names > 0 then - chains[#chains + 1] = names - end - pos = after_b or (after + 1) - else - pos = after - end - else - pos = ident_end - end - else - pos = pos + 1 - end - end - return chains -end - -- ════════════════════════════════════════════════════════════════════════════ -- The single source walker -- ════════════════════════════════════════════════════════════════════════════ diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index 4e478f2..1bddd12 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -245,7 +245,7 @@ local BRANCH_PATTERN = "^branch_[%w_]+%s*%(" -- This keeps `consuming_encoder` canonical for any downstream tooling that consults the metadata field. local JUMP_REL_PATTERN = "^jump_rel%s*%(" -local function classify_tokens(tokens) +local function tok_class_view(tokens) local n = #tokens local tc = {} local nop_run = 0 -- running count of consecutive nop words (forward pass) @@ -811,6 +811,7 @@ local function analyze_hardware_relations(atom) pending = {}, cu2_state = "unobserved", cu2_transition = nil, + c2_ctrl_writes = {}, _analysis_complete = false, } -- The architectural zero register is always a known U4 zero and cannot be invalidated by an emitted writer. @@ -847,44 +848,62 @@ local function analyze_hardware_relations(atom) -- Both operations are part of this one event walk. stage_cu2_transition(ev_ident, ev_args, ev_word, ev_line, ev_source, forward) consume_cu2_transition(atom, ev, ev_word, forward) + if ev_ident == "gte_mv_to_ctrl_r" then + forward.c2_ctrl_writes = forward.c2_ctrl_writes or {} + local alias = ev_args[2] + if type(alias) ~= "string" or not alias:match("^gte_cr_") then + alias = tostring(ev.call_text or ""):match("gte_cr_[%w_]+") + or tostring(ev.root_call_text or ""):match("gte_cr_[%w_]+") + end + local src = ev_args[1] + if type(src) == "string" then src = src:match("[%w_]+") end + if alias then + forward.c2_ctrl_writes[#forward.c2_ctrl_writes + 1] = { + alias = alias, + src = src, + line = ev_line or atom.line, + } + end + end -- ── 1. Inspect pending relations against the event as CONSUMER. ── -- Walk pending in REVERSE so `table.remove` doesn't shift indexes still to be inspected. + local CONSUMER = { + cop2_input = function(ev, prod) + local relation = prod.relation + if relation.id == "lwc2_to_gte_command" then + return is_gte_command(ev) and is_cop2_consumer_of(ev, prod.destination, relation) + end + if relation.id == "lwc2_to_other_consumer" then + return (not is_gte_command(ev)) and is_cop2_consumer_of(ev, prod.destination, relation) + end + return is_cop2_consumer_of(ev, prod.destination, relation) + end, + gpr_read = function(ev, prod) + return is_gpr_consumer_of(ev, prod.destination) + end, + overwrite_same_dest = function(ev, prod) + local ident = ev.encoder or ev.ident + local args = ev.args or {} + return (ident == "gte_mv_to_data_r" or ident == "gte_mv_to_ctrl_r") + and args[2] == prod.destination + end, + } for pending_idx = #pending, 1, -1 do local prod = pending[pending_idx] local relation = prod.relation - local semantic = relation.semantic - local is_match = false - if semantic == "MTC2" or semantic == "CTC2" or semantic == "LWC2_to_GTE" or semantic == "LWC2_to_other" then - -- Consumer is a GTE command whose input set contains the producer's COP2 destination (or a fan-out target). - -- LWC2_to_GTE — GTE-command consumer: gap = 0 OK (the pipeline latches the LWC2 result). - -- LWC2_to_other — non-GTE consumer: standard load delay applies. - if relation.id == "lwc2_to_gte_command" then - is_match = is_gte_command(ev) and is_cop2_consumer_of(ev, prod.destination, relation) - elseif relation.id == "lwc2_to_other_consumer" then - is_match = (not is_gte_command(ev)) and is_cop2_consumer_of(ev, prod.destination, relation) - else - is_match = is_cop2_consumer_of(ev, prod.destination, relation) - end - elseif semantic == "MFC2" or semantic == "CFC2" or semantic == "MFC0" then - -- Consumer is any encoder that reads the producer's GPR destination as an operand. - is_match = is_gpr_consumer_of(ev, prod.destination) - elseif semantic == "command_latch" then - -- Consumer is a subsequent MTC2/CTC2 overwrite of the same C2 destination. - -- The semantic is the post-command latch direction (command -> register); - -- This is intentionally separate from the preceding MTC2 -> command relation. - is_match = (ev_ident == "gte_mv_to_data_r" or ev_ident == "gte_mv_to_ctrl_r") - and ev_args[1] ~= nil - and ev_args[2] == prod.destination - end + local match_fn = CONSUMER[relation.consumer] + local is_match = match_fn and match_fn(ev, prod) if is_match then local gap = ev_word - prod.word - 1 local required = prod.required -- A following COP2 command waits at the transfer boundary. -- Software nops are not required for that consumer class. if is_gte_command(ev) - and (semantic == "MTC2" or semantic == "CTC2") - and relation.id ~= "mtc2_irgb_visibility" then + and relation.consumer == "cop2_input" + and relation.id ~= "mtc2_irgb_visibility" + and relation.id ~= "lwc2_to_gte_command" + and relation.id ~= "lwc2_to_other_consumer" then required = 0 end local unknown_visibility = relation.visibility and relation.visibility.kind == "unknown_consumer" @@ -1049,6 +1068,7 @@ local function analyze_hardware_relations(atom) relation = { id = "command_latch_input", semantic = "command_latch", + consumer = "overwrite_same_dest", direction = "gte_command_to_cop2_register", token = canonical, evidence = { @@ -1383,8 +1403,9 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings) if latch.register and latch.required then pending_snapshot[#pending_snapshot + 1] = { relation = { - id = "command_latch_input", + id = "command_latch_input", semantic = "command_latch", + consumer = "overwrite_same_dest", }, destination = latch.register, word = ev_word, @@ -2098,7 +2119,7 @@ end --- plus `mac_*` idents whose bare name is missing from `pipe_ctx.components_by_name` (i.e. no `MipsAtomComp_` for it). local function analyze_atom_paths(atom, pipe_ctx) local tokens = atom.paths.tokens or duffle.tokenize_body(atom.body) - local tc = atom.paths.tok_class or classify_tokens(tokens) + local tc = atom.paths.tok_class or tok_class_view(tokens) local n = #tokens -- Build label + branch maps from the pre-computed classification (no re-scan). @@ -2554,6 +2575,8 @@ local function ctrl_alias_from_text(text) end local function ctrl_writes_in_atom(atom) + local fs = atom.paths and atom.paths.forward_state + if fs and fs.c2_ctrl_writes then return fs.c2_ctrl_writes end local out = {} for _, ev in ipairs((atom.paths and atom.paths.word_events) or {}) do if (ev.encoder or "") == "gte_mv_to_ctrl_r" then @@ -2572,18 +2595,6 @@ local function ctrl_writes_in_atom(atom) end end end - if #out == 0 then - for _, t in ipairs((atom.paths and atom.paths.tokens) or {}) do - local tok = t.tok or "" - if tok:match("^gte_mv_to_ctrl_r") then - local alias = ctrl_alias_from_text(tok) - local src = tok:match("%(%s*([%w_]+)") - if alias then - out[#out + 1] = { alias = alias, src = src, line = atom.line } - end - end - end - end return out end @@ -2994,16 +3005,16 @@ local function arg_as_gpr(arg) return arg:match("^R_[%w_]+$") end -local function collect_gpr_traffic(tokens) +local function collect_gpr_traffic(atom) local reads, writes = {}, {} - for _, t in ipairs(tokens or {}) do - local tok = t.tok or t - local ident = (tok or ""):match("^([%w_]+)") or "" + local events = (atom and atom.paths and atom.paths.word_events) or {} + for _, ev in ipairs(events) do + local ident = ev.encoder or ev.ident or "" if ident:sub(1, 4) ~= "mac_" and not (duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[ident]) and ident ~= "nop" and ident ~= "atom_label" and ident ~= "atom_offset" then - local args = token_arg_list(tok) + local args = ev.args or {} local fx = duffle.instr(ident) if fx then for _, pos in ipairs(fx.reads or {}) do @@ -3057,7 +3068,7 @@ local function check_atom_calls_inferred_traffic(atom, pipe_ctx, findings) if #(info.reads or {}) == 0 and #(info.writes or {}) == 0 then return end local tokens = (atom.paths and atom.paths.tokens) or atom.body_tokens - local reads, writes = collect_gpr_traffic(tokens) + local reads, writes = collect_gpr_traffic(atom) for _, t in ipairs(tokens or {}) do local ident = ((t.tok or t) or ""):match("^([%w_]+)") or "" if ident:sub(1, 4) == "mac_" then @@ -3280,7 +3291,7 @@ local function validate(ctx, src, corpus_pipe_ctx) -- `paths.tokens` / `paths.line_in_body` / `paths.items` / `paths.word_events` are populated by `passes/emission_model.lua`. -- Supply tokens when no emission projection is present. if a.paths.tokens == nil then a.paths.tokens = a.body_tokens end - a.paths.tok_class = classify_tokens(a.paths.tokens) + a.paths.tok_class = tok_class_view(a.paths.tokens) -- analyze_atom_paths fills the *cycles / branches / has_loops / unknown_macros* fields of a.paths. analyze_atom_paths(a, pipe_ctx)