mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
build_noramlized_v3s4 mostly reviewed.
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
||||
#define mac_yield(...) \
|
||||
load_word(R_AtomJmp, R_TapePtr, 0) \
|
||||
LdSlot_ \
|
||||
, add_ui_self( R_TapePtr, S_(MipsCode)) \
|
||||
, jump_reg( R_AtomJmp) \
|
||||
, BdSlot_ nop
|
||||
@@ -217,7 +218,7 @@ WORD_COUNT(mac_gte_ld_ir123_v3s4, 3)
|
||||
, mac_gte_mv_from_mac123_v3s4(a) \
|
||||
GteDelay_ /* Read MAC1/2/3 → a.xyz (overwrites source-A's load targets) */ \
|
||||
, mac_shift_aright_v3s4_self(a, 12) /* Right-shift MAC by 12 (S12.20 → S12.0 OuterProduct12) */
|
||||
WORD_COUNT(mac_gte_op_cross_v3s4, 16)
|
||||
WORD_COUNT(mac_gte_op_cross_v3s4, 13)
|
||||
|
||||
/* atom_dbg_skip */
|
||||
#define mac_gte_store_f3(r_primitive_cursor) \
|
||||
@@ -291,7 +292,7 @@ WORD_COUNT(mac_gte_sqr_v3s4, 5)
|
||||
, shift_aright_var(r_dx, r_dx, r_shift) \
|
||||
, shift_aright_var(r_dy, r_dy, r_shift) \
|
||||
, shift_aright_var(r_dz, r_dz, r_shift)
|
||||
WORD_COUNT(mac_gte_gpf_scale, 12)
|
||||
WORD_COUNT(mac_gte_gpf_scale, 13)
|
||||
|
||||
#define mac_trans_mt3s3s4(r_mtx, r_off, r_t0, r_t1, r_t2) \
|
||||
load_word( r_t0, r_off, O_(V3_S4,x)) \
|
||||
|
||||
@@ -33,7 +33,7 @@ enum {
|
||||
atom_offset_example_atom_proc_skip = _atom_offset_example_atom_proc_skip,
|
||||
};
|
||||
|
||||
// --- atom: build_normalize_v3s4 (65 words) ---
|
||||
// --- atom: build_normalize_v3s4 (63 words) ---
|
||||
|
||||
#define _atom_offset_aligned_done_srav_path 3
|
||||
#define _atom_offset_srav_path_aligned_done 4
|
||||
|
||||
+41
-58
@@ -272,82 +272,65 @@ typedef Struct_(Binds_NormalizeV3S4) {
|
||||
U2 dst_offset; /* offset of dst V3_S4 within the BIOS scratchpad */
|
||||
};
|
||||
typedef Struct_(RegUse_build_normalize_v3s4) {
|
||||
Reg src_ptr;
|
||||
Reg dst_ptr;
|
||||
Reg src_x;
|
||||
Reg src_z;
|
||||
Reg recip_est;
|
||||
Reg norm;
|
||||
Reg shift;
|
||||
union {
|
||||
Reg v_sqr_aligned, dst_offset;
|
||||
} t3;
|
||||
union { Reg mac2; } t4;
|
||||
union { Reg btarget, shift_count, sqrtbl_index, src_offset; } t5;
|
||||
union { Reg_(V3_S4) res, src; };
|
||||
union { Reg t0, src_ptr, mac2; };
|
||||
union { Reg t1, dst_ptr; };
|
||||
union { Reg t2, dst_offset, mac1, v_sqr_aligned; };
|
||||
union { Reg t3, src_offset, btarget, shift_count, sqrtbl_index; };
|
||||
union { Reg t4, mac3, v_sqr_sum, half_shift_tmp, inv_len; };
|
||||
union { Reg t5, lzcr, half_shift; };
|
||||
};
|
||||
/* ─── Full normalize (all 4 stages inline) ───
|
||||
* Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). */
|
||||
internal MipsAtom* build_normalize_v3s4(AtomArena_R aa, RegUse_build_normalize_v3s4 r)
|
||||
MipsAtom_Proc_(aa, {
|
||||
load_half(r.t5.src_offset, R_TapePtr, O_(Binds_NormalizeV3S4, src_offset)),
|
||||
load_half(r.t3.dst_offset, R_TapePtr, O_(Binds_NormalizeV3S4, dst_offset)),
|
||||
LdSlot_ add_u(r.src_ptr, R_ScratchBase, r.t5.src_offset),
|
||||
LdSlot_ add_u(r.dst_ptr, R_ScratchBase, r.t3.dst_offset),
|
||||
load_half(r.src_offset, R_TapePtr, O_(Binds_NormalizeV3S4, src_offset)),
|
||||
load_half(r.dst_offset, R_TapePtr, O_(Binds_NormalizeV3S4, dst_offset)),
|
||||
LdSlot_ add_u(r.src_ptr, R_ScratchBase, r.src_offset),
|
||||
LdSlot_ add_u(r.dst_ptr, R_ScratchBase, r.dst_offset),
|
||||
LdSlot_ add_ui_self(R_TapePtr, S_(Binds_NormalizeV3S4)),
|
||||
|
||||
/* Load src.x/y/z from r_src_ptr (caller-determined address) into r_src_x / r_recip_est / r_src_z.
|
||||
* r_src_x holds src.x throughout stages 1-2; r_recip_est (which is src.y during this phase) is reused as MAC2 in stage 4. */
|
||||
mac_load_word_v3(r.src_x, r.recip_est, r.src_z, r.src_ptr, 0),
|
||||
mac_load_v3s4(r.src, r.src_ptr, 0),
|
||||
|
||||
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
|
||||
LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.src_z, LdSlot_ nop),
|
||||
LdSlot_ mac_gte_sqr_v3s4(r.src.x, r.src.y, r.src.z, LdSlot_ nop),
|
||||
|
||||
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */
|
||||
mac_gte_mv_from_data_r_mac123(r.t3.v_sqr_aligned, r.t4.mac2, r.norm), LdSlot_ nop,
|
||||
add_u_self( r.norm, r.t3.v_sqr_aligned),
|
||||
add_u_self( r.norm, r.t4.mac2),
|
||||
gte_mv_to_data_r( r.norm, C2_LZCS), GteDelay_ nop2,
|
||||
gte_mv_from_data_r(r.shift, C2_LZCR), GteDelay_ nop,
|
||||
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. src_ptr is dead; reuse as mac2. */
|
||||
mac_gte_mv_from_data_r_mac123(r.mac1, r.mac2, r.mac3), LdSlot_ nop,
|
||||
add_u_self( r.v_sqr_sum, r.mac1),
|
||||
add_u_self( r.v_sqr_sum, r.mac2),
|
||||
gte_mv_to_data_r( r.v_sqr_sum, C2_LZCS), GteDelay_ nop2,
|
||||
gte_mv_from_data_r(r.lzcr, C2_LZCR), GteDelay_ nop,
|
||||
|
||||
/* Stage 3: round LZCR to even, compute half-shift, align |v|² to bit 24.
|
||||
* r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2.
|
||||
* After the component: r_shift = even(LZCR), r_norm = half-shift, r_t3.v_sqr_aligned = |v|². */
|
||||
mac_lzcr_round_even_half_shift(r.shift, r.norm, r.t3.v_sqr_aligned),
|
||||
add_si( r.t5.btarget, r.shift, -24),
|
||||
branch_lt_zero(r.t5.btarget, atom_offset(aligned_done, srav_path)), BdSlot_ nop, /* bltz → srav_path (LZCR < 24 path) */
|
||||
/* Stage 3: even(LZCR), half-shift, align |v|² to bit 24. */
|
||||
mac_lzcr_round_even_half_shift(r.lzcr, r.v_sqr_sum, r.v_sqr_aligned),
|
||||
add_si( r.btarget, r.lzcr, -24),
|
||||
branch_lt_zero(r.btarget, atom_offset(aligned_done, srav_path)), BdSlot_ nop, /* bltz → srav_path (LZCR < 24 path) */
|
||||
jump_rel(atom_offset(srav_path, aligned_done)), /* b → aligned_done (LZCR >= 24 path) */
|
||||
BdSlot_ shift_lleft_var(r.t3.v_sqr_aligned, r.t3.v_sqr_aligned, r.t5.btarget),
|
||||
BdSlot_ shift_lleft_var(r.v_sqr_aligned, r.v_sqr_aligned, r.btarget),
|
||||
atom_label(srav_path)
|
||||
li_s( r.t5.shift_count, 24),
|
||||
sub_s(r.t5.shift_count, r.t5.shift_count, r.shift),
|
||||
shift_aright_var(r.t3.v_sqr_aligned, r.t3.v_sqr_aligned, r.t5.shift_count),
|
||||
li_s( r.shift_count, 24),
|
||||
sub_s(r.shift_count, r.shift_count, r.lzcr),
|
||||
shift_aright_var(r.v_sqr_aligned, r.v_sqr_aligned, r.shift_count),
|
||||
atom_label(aligned_done)
|
||||
// Save the shift count to r_shift before the next 5 instructions overwrite r_norm (the sqrtbl lookup loads 1/|v| into r_norm, which becomes IR0 in stage 4).
|
||||
or_u(r.shift, r.norm, 0), /* r_shift ← shift count (preserved through stage 4) */
|
||||
add_si( r.t3.v_sqr_aligned, r.t3.v_sqr_aligned, -64), /* r_t3.v_sqr_aligned holds |v|² aligned (top bit at bit 7). */
|
||||
shift_lleft(r.t3.v_sqr_aligned, r.t3.v_sqr_aligned, 1),
|
||||
mac_load_word_imm(r.t5.sqrtbl_index, & gte_normalize_sqr_tbl), add_u_self(r.t5.sqrtbl_index, r.t3.v_sqr_aligned),
|
||||
load_half(r.norm, r.t5.sqrtbl_index, 0), /* r_norm = sqrtbl[aligned-64] = 1/|v| (IR0 in stage 4) */
|
||||
or_u(r.half_shift, r.half_shift_tmp, 0),
|
||||
add_si( r.v_sqr_aligned, r.v_sqr_aligned, -64),
|
||||
shift_lleft(r.v_sqr_aligned, r.v_sqr_aligned, 1),
|
||||
mac_load_word_imm(r.sqrtbl_index, & gte_normalize_sqr_tbl), add_u_self(r.sqrtbl_index, r.v_sqr_aligned),
|
||||
load_half(r.inv_len, r.sqrtbl_index, 0),
|
||||
LdSlot_ nop,
|
||||
|
||||
/* r.src_z holds src.z from the initial load (r.src_z is a dedicated slot,
|
||||
* never clobbered between the load at body start and the stage-4 GPF use below).
|
||||
* Stage 4: GPF + srav finalize (r_shift = shift count, r_norm = 1/|v|). */
|
||||
mac_gte_general_purpose_interopolation(
|
||||
r.norm,
|
||||
r.src_x, /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */
|
||||
r.recip_est,
|
||||
r.src_z, /* IR3 = src.z (reloaded) */
|
||||
r.t4.mac2, r.recip_est, r.src_z,
|
||||
GteDelay_ nop,
|
||||
GteDelay_ nop
|
||||
mac_gte_general_purpose_interopolation(r.inv_len,
|
||||
r.src.x, r.src.y, r.src.z,
|
||||
r.res.x, r.res.y, r.res.z,
|
||||
GteDelay_ load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_ // ac_yield: word 1
|
||||
GteDelay_ add_ui_self( R_TapePtr, S_(MipsCode)) // ac_yield: word 2
|
||||
),
|
||||
/* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */
|
||||
mac_shift_aright_var_v3_self(r.t4.mac2, r.recip_est, r.src_z, r.shift),
|
||||
/* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */
|
||||
mac_store_word_v3(r.t4.mac2, r.recip_est, r.src_z, r.dst_ptr, 0),
|
||||
mac_shift_aright_var_v3s4_self(r.res, r.half_shift),
|
||||
mac_store_v3s4(r.res, r.dst_ptr, 0),
|
||||
|
||||
mac_yield()
|
||||
jump_reg(R_AtomJmp), BdSlot_ nop // ac_yield: word 3-4
|
||||
// mac_yield()
|
||||
})
|
||||
|
||||
/* ─── GTE OP cross product (a × b → out) ───
|
||||
|
||||
@@ -263,7 +263,7 @@ FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_
|
||||
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
||||
|
||||
atom_dbg_skip MipsAtomComp_(ac_yield) {
|
||||
load_word(R_AtomJmp, R_TapePtr, 0),
|
||||
load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_
|
||||
add_ui_self( R_TapePtr, S_(MipsCode)),
|
||||
jump_reg( R_AtomJmp), BdSlot_ nop,
|
||||
};
|
||||
|
||||
@@ -151,14 +151,10 @@ internal void compile_init_atoms(void) {
|
||||
|
||||
smem.normalize_v3s4 = build_normalize_v3s4(& ab,
|
||||
RegUse_(build_normalize_v3s4) {
|
||||
.src_ptr = ralloc(),
|
||||
.dst_ptr = ralloc(),
|
||||
.recip_est = ralloc(),
|
||||
.norm = ralloc(),
|
||||
.shift = ralloc(),
|
||||
.src_x = ralloc(),
|
||||
.src_z = ralloc(),
|
||||
// .shift_count = ralloc(), /* dedicated slot for stage-3 → stage-4 shift count */
|
||||
.res = ralloc_v3(),
|
||||
.t0 = ralloc(),
|
||||
.t1 = ralloc(),
|
||||
.t2 = ralloc(),
|
||||
.t3 = ralloc(),
|
||||
.t4 = ralloc(),
|
||||
.t5 = ralloc(),
|
||||
|
||||
@@ -550,7 +550,14 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
||||
local tok_line = line_of(body_off + bt.rel) or 0
|
||||
if M.DELAY_MARKERS[ident] then
|
||||
emit_marker("delay", ident, nil, tok_line)
|
||||
local rest = M.trim(tok:sub(after or (#tok + 1)))
|
||||
local rest = tok:sub(after or (#tok + 1))
|
||||
while true do
|
||||
rest = M.trim(rest)
|
||||
if rest:sub(1, 2) ~= "/*" then break end
|
||||
local close = rest:find("*/", 3, true)
|
||||
if not close then rest = ""; break end
|
||||
rest = rest:sub(close + 2)
|
||||
end
|
||||
if rest ~= "" then
|
||||
process_token({ tok = rest, rel = bt.rel })
|
||||
end
|
||||
|
||||
@@ -329,6 +329,24 @@ local function strip_mac_prefix(ident)
|
||||
return ident
|
||||
end
|
||||
|
||||
--- Strip a leading delay marker (`LdSlot_` / `BdSlot_` / `GteDelay_` / `DmaSlot_`)
|
||||
--- plus following whitespace and block comments. Returns the remainder, or ""
|
||||
--- when the token is only the marker.
|
||||
--- `BdSlot_ nop` becomes `nop`. Bare `LdSlot_` becomes "".
|
||||
--- @param tok string
|
||||
--- @return string
|
||||
local function strip_leading_delay_marker(tok)
|
||||
local ident = duffle.read_ident(tok, 1)
|
||||
if not ident or not duffle.DELAY_MARKERS[ident] then return tok end
|
||||
local rest = tok:sub(#ident + 1):match("^%s*(.*)$") or ""
|
||||
while rest:sub(1, 2) == "/*" do
|
||||
local close = rest:find("*/", 3, true)
|
||||
if not close then return "" end
|
||||
rest = rest:sub(close + 2):match("^%s*(.*)$") or ""
|
||||
end
|
||||
return rest
|
||||
end
|
||||
|
||||
--- (internal) Recursive word-count lookup. `cache` is the memoization table shared across all components
|
||||
--- in a single source's `count_all_components` pass; the in-progress -1 sentinel detects cycles (A -> B -> A).
|
||||
--- @param name string -- the component name (without `mac_`)
|
||||
@@ -347,7 +365,18 @@ local function word_count_rec(name, comp_by_name, wc, cache)
|
||||
for _, t in ipairs(tokens) do
|
||||
local trimmed = t.tok
|
||||
if trimmed ~= "" then
|
||||
local lookup = strip_mac_prefix(duffle.read_ident(trimmed, 1))
|
||||
local work = trimmed
|
||||
while true do
|
||||
local marker = duffle.read_ident(work, 1)
|
||||
if marker and duffle.DELAY_MARKERS[marker] then
|
||||
work = strip_leading_delay_marker(work)
|
||||
if work == "" then break end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if work ~= "" then
|
||||
local lookup = strip_mac_prefix(duffle.read_ident(work, 1))
|
||||
if lookup == "atom_label" or lookup == "atom_offset" then
|
||||
-- Pure metaprogram anchors; emit zero words.
|
||||
elseif lookup and comp_by_name[lookup] then
|
||||
@@ -362,6 +391,7 @@ local function word_count_rec(name, comp_by_name, wc, cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Not a known component: assume 1 word (regular instruction).
|
||||
n = 1
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
--- Public boundary:
|
||||
--- * `M.run(ctx)` is the only entry point.
|
||||
--- * The pass returns `{outputs = {}, errors = ..., warnings = ...}`.
|
||||
--- Pass kind = `validation` → `PASS_KIND_STOP_ON_ERROR.validation` preserves the existing build-stopping policy.
|
||||
--- Pass kind = `validation`. Findings record on the result; the orchestrator does not exit non-zero.
|
||||
---
|
||||
--- Source-order discipline:
|
||||
--- * `corpus.source_order` sets the source-record order.
|
||||
|
||||
+20
-13
@@ -129,33 +129,39 @@ end
|
||||
--- This preserves backward compatibility for any top-level marker that may exist outside a control-transfer instruction.
|
||||
--- @param labels table<string, integer>
|
||||
--- @param branches table[]
|
||||
--- @param errors table[]
|
||||
--- @return BranchOffset[]
|
||||
local function compute_offsets(labels, branches)
|
||||
local function compute_offsets(labels, branches, errors)
|
||||
local results = {}
|
||||
for _, br in ipairs(branches) do
|
||||
local target = labels[br.target]
|
||||
if not target then
|
||||
error("Branch target '" .. br.target .. "' has no atom_label (at word " .. br.branch_word .. ")")
|
||||
end
|
||||
errors[#errors + 1] = {
|
||||
line = br.line or 0,
|
||||
msg = "Branch target '" .. br.target .. "' has no atom_label (at word " .. br.branch_word .. ")",
|
||||
}
|
||||
else
|
||||
local consuming = br.consuming_encoder
|
||||
local offset
|
||||
if consuming == "jump_reg" or consuming == "call_reg" or consuming == "jump_link" then
|
||||
-- Register-form jumps have no offset field. `atom_offset` cannot be used here.
|
||||
error("atom_offset cannot be used with " .. consuming
|
||||
.. " (register-form jumps have no offset field); at word " .. br.branch_word)
|
||||
end
|
||||
errors[#errors + 1] = {
|
||||
line = br.line or 0,
|
||||
msg = "atom_offset cannot be used with " .. consuming
|
||||
.. " (register-form jumps have no offset field); at word " .. br.branch_word,
|
||||
}
|
||||
else
|
||||
-- All other consuming instructions (including `branch_*`, `jump`, `call_addr`, and nil for top-level markers) use the same relative offset value.
|
||||
-- The MIPS encoding differs per opcode but the duffle `enc_i` macro handles the truncation to the immediate-field width.
|
||||
offset = target - br.branch_word - 1
|
||||
results[#results + 1] = {
|
||||
target = br.target,
|
||||
tag = br.tag,
|
||||
branch_word = br.branch_word,
|
||||
offset = offset,
|
||||
offset = target - br.branch_word - 1,
|
||||
consuming_encoder = br.consuming_encoder,
|
||||
consuming_arg_pos = br.consuming_arg_pos,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
@@ -237,8 +243,9 @@ local M = {}
|
||||
--- @param ctx PassCtx
|
||||
--- @param dir string -- the absolute source directory
|
||||
--- @param sources SourceFile[] -- sources in this directory
|
||||
--- @param errors table[]
|
||||
--- @return string|nil -- the offsets_h path
|
||||
local function process_directory(ctx, dir, sources)
|
||||
local function process_directory(ctx, dir, sources, errors)
|
||||
local atoms_data = {}
|
||||
|
||||
local function append_atom(atom)
|
||||
@@ -248,7 +255,7 @@ local function process_directory(ctx, dir, sources)
|
||||
atoms_data[#atoms_data + 1] = {
|
||||
name = atom.raw_name or atom.name,
|
||||
total_words = #(paths.word_events or {}),
|
||||
offsets = compute_offsets(labels, branches),
|
||||
offsets = compute_offsets(labels, branches, errors),
|
||||
}
|
||||
end
|
||||
|
||||
@@ -286,7 +293,7 @@ function M.run(ctx)
|
||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order)
|
||||
for dir, sources in pairs(sources_by_dir) do
|
||||
local out_path = process_directory(ctx, dir, sources)
|
||||
local out_path = process_directory(ctx, dir, sources, errors)
|
||||
if out_path then
|
||||
outputs[#outputs + 1] = { offsets_h = out_path }
|
||||
end
|
||||
|
||||
+197
-13
@@ -1674,9 +1674,20 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local members = {}
|
||||
local views = {}
|
||||
local union_readonly = nil
|
||||
local inner_pos = 1
|
||||
|
||||
local function note_readonly(flag)
|
||||
if union_readonly == nil then
|
||||
union_readonly = flag
|
||||
elseif union_readonly ~= flag then
|
||||
errors[#errors + 1] = { kind = "reguse_mixed_const" }
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
while inner_pos <= #inner do
|
||||
inner_pos = duffle.skip_ws_and_cmt(inner, inner_pos)
|
||||
if inner_pos > #inner then break end
|
||||
@@ -1689,10 +1700,138 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
|
||||
return nil, errors
|
||||
end
|
||||
if m_type ~= "Reg" then
|
||||
|
||||
if m_type == "Reg_" then
|
||||
local after_ty = duffle.skip_ws_and_cmt(inner, m_type_end)
|
||||
if inner:sub(after_ty, after_ty) ~= "(" then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local type_inner, after_paren = duffle.read_parens(inner, after_ty)
|
||||
if not type_inner then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local typed_fields = fields_for_reg_type(duffle.trim(type_inner), type_registry)
|
||||
after_paren = duffle.skip_ws_and_cmt(inner, after_paren)
|
||||
local inst_names, new_inner = parse_reg_names(inner, after_paren)
|
||||
if not inst_names or #inst_names == 0 then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
if not note_readonly(false) then return nil, errors end
|
||||
if not typed_fields then
|
||||
if require_types then
|
||||
errors[#errors + 1] = { kind = "reguse_unknown_reg_type", type_name = duffle.trim(type_inner) }
|
||||
return nil, errors
|
||||
end
|
||||
pending = true
|
||||
else
|
||||
for _, inst in ipairs(inst_names) do
|
||||
local names = {}
|
||||
for _, field in ipairs(typed_fields) do
|
||||
names[#names + 1] = inst .. "." .. field
|
||||
end
|
||||
views[#views + 1] = { names = names, lanes = true }
|
||||
end
|
||||
end
|
||||
inner_pos = new_inner
|
||||
|
||||
elseif m_type == "struct" then
|
||||
local after_struct = duffle.skip_ws_and_cmt(inner, m_type_end)
|
||||
if inner:sub(after_struct, after_struct) ~= "{" then
|
||||
local _, tag_end = duffle.read_ident(inner, after_struct)
|
||||
if not tag_end then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
after_struct = duffle.skip_ws_and_cmt(inner, tag_end)
|
||||
end
|
||||
if inner:sub(after_struct, after_struct) ~= "{" then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local struct_inner, after_struct_braces = duffle.read_braces(inner, after_struct)
|
||||
if not struct_inner then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local lane_names = {}
|
||||
local s_pos = 1
|
||||
while s_pos <= #struct_inner do
|
||||
s_pos = duffle.skip_ws_and_cmt(struct_inner, s_pos)
|
||||
if s_pos > #struct_inner then break end
|
||||
local s_ty, s_ty_end = duffle.read_ident(struct_inner, s_pos)
|
||||
if not s_ty then
|
||||
s_pos = s_pos + 1
|
||||
goto continue_struct
|
||||
end
|
||||
if s_ty == "Reg_" then
|
||||
local after_ty = duffle.skip_ws_and_cmt(struct_inner, s_ty_end)
|
||||
if struct_inner:sub(after_ty, after_ty) ~= "(" then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local type_inner, after_paren = duffle.read_parens(struct_inner, after_ty)
|
||||
if not type_inner then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
local typed_fields = fields_for_reg_type(duffle.trim(type_inner), type_registry)
|
||||
after_paren = duffle.skip_ws_and_cmt(struct_inner, after_paren)
|
||||
local inst_names, new_s = parse_reg_names(struct_inner, after_paren)
|
||||
if not inst_names or #inst_names == 0 then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
if not note_readonly(false) then return nil, errors end
|
||||
if not typed_fields then
|
||||
if require_types then
|
||||
errors[#errors + 1] = { kind = "reguse_unknown_reg_type", type_name = duffle.trim(type_inner) }
|
||||
return nil, errors
|
||||
end
|
||||
pending = true
|
||||
else
|
||||
for _, inst in ipairs(inst_names) do
|
||||
for _, field in ipairs(typed_fields) do
|
||||
lane_names[#lane_names + 1] = inst .. "." .. field
|
||||
end
|
||||
end
|
||||
end
|
||||
s_pos = new_s
|
||||
elseif s_ty == "Reg" then
|
||||
local s_after = duffle.skip_ws_and_cmt(struct_inner, s_ty_end)
|
||||
local s_readonly = false
|
||||
local maybe_const, maybe_end = duffle.read_ident(struct_inner, s_after)
|
||||
if maybe_const == "const" then
|
||||
s_readonly = true
|
||||
s_after = duffle.skip_ws_and_cmt(struct_inner, maybe_end)
|
||||
end
|
||||
if not note_readonly(s_readonly) then return nil, errors end
|
||||
local names, new_s = parse_reg_names(struct_inner, s_after)
|
||||
if not names or #names == 0 then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
for _, n in ipairs(names) do lane_names[#lane_names + 1] = n end
|
||||
s_pos = new_s
|
||||
else
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
::continue_struct::
|
||||
end
|
||||
if #lane_names == 0 and not pending then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
if #lane_names > 0 then
|
||||
views[#views + 1] = { names = lane_names, lanes = true }
|
||||
end
|
||||
inner_pos = duffle.skip_ws_and_cmt(inner, after_struct_braces)
|
||||
if inner:sub(inner_pos, inner_pos) == ";" then inner_pos = inner_pos + 1 end
|
||||
|
||||
elseif m_type == "Reg" then
|
||||
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)
|
||||
@@ -1700,27 +1839,68 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||
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
|
||||
if not note_readonly(m_readonly) then 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
|
||||
views[#views + 1] = { names = names, lanes = false }
|
||||
inner_pos = new_inner
|
||||
::continue_inner::
|
||||
end
|
||||
if #members == 0 then
|
||||
else
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
::continue_inner::
|
||||
end
|
||||
|
||||
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
||||
local inst_name, inst_end = duffle.read_ident(body, after_close)
|
||||
|
||||
if #views == 0 then
|
||||
if not pending then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
else
|
||||
local has_lanes = false
|
||||
for _, v in ipairs(views) do
|
||||
if v.lanes then has_lanes = true end
|
||||
end
|
||||
|
||||
if has_lanes then
|
||||
local width = nil
|
||||
for _, v in ipairs(views) do
|
||||
if not v.lanes then
|
||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||
return nil, errors
|
||||
end
|
||||
if width == nil then
|
||||
width = #v.names
|
||||
elseif #v.names ~= width then
|
||||
errors[#errors + 1] = { kind = "reguse_union_width" }
|
||||
return nil, errors
|
||||
end
|
||||
end
|
||||
if width then
|
||||
for i = 1, width do
|
||||
local slot_name = views[1].names[i]
|
||||
if inst_name then slot_name = inst_name .. "." .. slot_name end
|
||||
local aliases = {}
|
||||
for _, v in ipairs(views) do
|
||||
local n = v.names[i]
|
||||
if inst_name then n = inst_name .. "." .. n end
|
||||
if not add_alias(n, slot_name) then return nil, errors end
|
||||
aliases[#aliases + 1] = n
|
||||
end
|
||||
if not add_slot(slot_name, aliases, union_readonly) then return nil, errors end
|
||||
end
|
||||
end
|
||||
else
|
||||
local members = {}
|
||||
for _, v in ipairs(views) do
|
||||
for _, n in ipairs(v.names) do members[#members + 1] = n end
|
||||
end
|
||||
local aliases = {}
|
||||
local slot_name
|
||||
if inst_name then
|
||||
@@ -1730,7 +1910,6 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||
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
|
||||
@@ -1739,6 +1918,10 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
||||
end
|
||||
end
|
||||
if not add_slot(slot_name, aliases, union_readonly) then return nil, errors end
|
||||
end
|
||||
end
|
||||
|
||||
if inst_name then after_close = inst_end 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
|
||||
@@ -2801,6 +2984,7 @@ local SCHEMA_BODY_ERROR = {
|
||||
reguse_duplicate_slot = true,
|
||||
reguse_const_reg_spelling = true,
|
||||
reguse_mixed_const = true,
|
||||
reguse_union_width = true,
|
||||
}
|
||||
|
||||
-- Re-parse every RegUse_* body against the merged type_name_registry.
|
||||
|
||||
@@ -879,6 +879,14 @@ local function analyze_hardware_relations(atom)
|
||||
end
|
||||
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
|
||||
required = 0
|
||||
end
|
||||
local unknown_visibility = relation.visibility and relation.visibility.kind == "unknown_consumer"
|
||||
if unknown_visibility then
|
||||
hazards[#hazards + 1] = {
|
||||
@@ -906,7 +914,7 @@ local function analyze_hardware_relations(atom)
|
||||
, (relation.evidence and relation.evidence.confidence or "unknown")
|
||||
),
|
||||
}
|
||||
elseif prod.required ~= nil and gap < prod.required then
|
||||
elseif required ~= nil and gap < required then
|
||||
local payload = {
|
||||
check = "transfer_hazards",
|
||||
kind = prod.violation_kind or "error",
|
||||
@@ -923,7 +931,7 @@ local function analyze_hardware_relations(atom)
|
||||
consumer_word = ev_word,
|
||||
consumer_token = ev_ident,
|
||||
gap = gap,
|
||||
required = prod.required,
|
||||
required = required,
|
||||
evidence_confidence = relation.evidence and relation.evidence.confidence or "unknown",
|
||||
evidence_source = relation.evidence and relation.evidence.source or "",
|
||||
msg = string.format("%s at line %d: %s relation %s (producer %s at word %d, %s:%d) violated: consumer at word %d (gap=%d, required=%d) [%s]"
|
||||
@@ -941,7 +949,7 @@ local function analyze_hardware_relations(atom)
|
||||
hazards[#hazards + 1] = payload
|
||||
end
|
||||
local satisfied = nil
|
||||
if not unknown_visibility then satisfied = gap >= prod.required end
|
||||
if not unknown_visibility then satisfied = gap >= required end
|
||||
-- Record the relation touch on `paths.relations` even when the gap is satisfied.
|
||||
-- Unknown relations are informational, not numeric pass/fail measurements.
|
||||
relations[#relations + 1] = {
|
||||
@@ -951,7 +959,7 @@ local function analyze_hardware_relations(atom)
|
||||
consumer_word = ev_word,
|
||||
destination = prod.destination,
|
||||
gap = gap,
|
||||
required = prod.required,
|
||||
required = required,
|
||||
satisfied = satisfied,
|
||||
}
|
||||
table.remove(pending, pending_idx)
|
||||
|
||||
+9
-13
@@ -142,8 +142,7 @@ local PASSES = {
|
||||
},
|
||||
["static-analysis"] = {
|
||||
module = "passes.static_analysis",
|
||||
-- "diagnostic" — every `error`/`warning` finding is written to the report file;
|
||||
-- The orchestrator does NOT exit non-zero on these findings (see PASS_KIND_STOP_ON_ERROR).
|
||||
-- "diagnostic" — every `error`/`warning` finding is written to the report file.
|
||||
-- Report severity is independent from process exit policy.
|
||||
kind = "diagnostic",
|
||||
deps = {"scan-source", "word-counts", "components", "emission-model"},
|
||||
@@ -206,16 +205,13 @@ local function request_roots_for_group(args, group_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Pass-kind taxonomy: Which kinds stop the build on errors?
|
||||
--
|
||||
-- Pass-kind taxonomy: findings always print. No pass kind stops the build.
|
||||
-- Report severity is independent from process exit policy.
|
||||
-- A "diagnostic" pass still writes every `error`/`warning` finding into its report file,
|
||||
-- but `report_validation_errors` returns early for non-stopping kinds, so nothing is printed to stderr and the orchestrator does not exit non-zero.
|
||||
-- Adding a new pass kind requires listing it here explicitly; An unknown kind must not silently fall back to "true".
|
||||
-- Adding a new pass kind requires listing it here explicitly; an unknown kind must not silently fall back to "true".
|
||||
local PASS_KIND_STOP_ON_ERROR = {
|
||||
["shared"] = false,
|
||||
["header-output"] = true,
|
||||
["validation"] = true,
|
||||
["header-output"] = false,
|
||||
["validation"] = false,
|
||||
["diagnostic"] = false,
|
||||
["report"] = false,
|
||||
}
|
||||
@@ -693,19 +689,19 @@ end
|
||||
-- Main Orchestrator
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- (internal) If the pass's kind is in PASS_KIND_STOP_ON_ERROR and it reported errors, write each error to stderr.
|
||||
--- Returns true if any validation errors were reported.
|
||||
--- (internal) Write every pass error to stderr.
|
||||
--- Returns true only when the pass kind still stops the build.
|
||||
--- @param pass_name string
|
||||
--- @param pass PassDescriptor
|
||||
--- @param result PassResult
|
||||
--- @return boolean
|
||||
local function report_validation_errors(pass_name, pass, result)
|
||||
local has_errors = result.errors and #result.errors > 0
|
||||
if not (has_errors and PASS_KIND_STOP_ON_ERROR[pass.kind]) then return false end
|
||||
if not has_errors then return false end
|
||||
for _, e in ipairs(result.errors) do
|
||||
io.stderr:write(string.format("[%s] line %d: %s\n", pass_name, e.line or 0, e.msg or ""))
|
||||
end
|
||||
return true
|
||||
return PASS_KIND_STOP_ON_ERROR[pass.kind] == true
|
||||
end
|
||||
|
||||
--- (internal) Run each pass in `order` in topological sequence.
|
||||
|
||||
Reference in New Issue
Block a user