mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-09-08 09:19:06 +00:00
Compare commits
4
Commits
86fe189b4e
...
1cbddc6708
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cbddc6708 | ||
|
|
b345ccd60e | ||
|
|
bbda5efaea | ||
|
|
290bb0e07a |
@@ -37,6 +37,7 @@
|
|||||||
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
||||||
#define mac_yield(...) \
|
#define mac_yield(...) \
|
||||||
load_word(R_AtomJmp, R_TapePtr, 0) \
|
load_word(R_AtomJmp, R_TapePtr, 0) \
|
||||||
|
LdSlot_ \
|
||||||
, add_ui_self( R_TapePtr, S_(MipsCode)) \
|
, add_ui_self( R_TapePtr, S_(MipsCode)) \
|
||||||
, jump_reg( R_AtomJmp) \
|
, jump_reg( R_AtomJmp) \
|
||||||
, BdSlot_ nop
|
, BdSlot_ nop
|
||||||
@@ -217,7 +218,7 @@ WORD_COUNT(mac_gte_ld_ir123_v3s4, 3)
|
|||||||
, mac_gte_mv_from_mac123_v3s4(a) \
|
, mac_gte_mv_from_mac123_v3s4(a) \
|
||||||
GteDelay_ /* Read MAC1/2/3 → a.xyz (overwrites source-A's load targets) */ \
|
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) */
|
, 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 */
|
/* atom_dbg_skip */
|
||||||
#define mac_gte_store_f3(r_primitive_cursor) \
|
#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_dx, r_dx, r_shift) \
|
||||||
, shift_aright_var(r_dy, r_dy, r_shift) \
|
, shift_aright_var(r_dy, r_dy, r_shift) \
|
||||||
, shift_aright_var(r_dz, r_dz, 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) \
|
#define mac_trans_mt3s3s4(r_mtx, r_off, r_t0, r_t1, r_t2) \
|
||||||
load_word( r_t0, r_off, O_(V3_S4,x)) \
|
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_offset_example_atom_proc_skip = _atom_offset_example_atom_proc_skip,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- atom: build_normalize_v3s4 (67 words) ---
|
// --- atom: build_normalize_v3s4 (63 words) ---
|
||||||
|
|
||||||
#define _atom_offset_aligned_done_srav_path 3
|
#define _atom_offset_aligned_done_srav_path 3
|
||||||
#define _atom_offset_srav_path_aligned_done 4
|
#define _atom_offset_srav_path_aligned_done 4
|
||||||
|
|||||||
+42
-65
@@ -272,88 +272,65 @@ typedef Struct_(Binds_NormalizeV3S4) {
|
|||||||
U2 dst_offset; /* offset of dst V3_S4 within the BIOS scratchpad */
|
U2 dst_offset; /* offset of dst V3_S4 within the BIOS scratchpad */
|
||||||
};
|
};
|
||||||
typedef Struct_(RegUse_build_normalize_v3s4) {
|
typedef Struct_(RegUse_build_normalize_v3s4) {
|
||||||
Reg scratch; /* scratchpad base; loaded via load_word_imm below. */
|
union { Reg_(V3_S4) res, src; };
|
||||||
Reg src_ptr;
|
union { Reg t0, src_ptr, mac2; };
|
||||||
Reg dst_ptr;
|
union { Reg t1, dst_ptr; };
|
||||||
Reg recip_est; /* |v|² sum + shift-input + sqrtbl[index] */
|
union { Reg t2, dst_offset, mac1, v_sqr_aligned; };
|
||||||
Reg norm; Reg shift;
|
union { Reg t3, src_offset, btarget, shift_count, sqrtbl_index; };
|
||||||
Reg src_x;
|
union { Reg t4, mac3, v_sqr_sum, half_shift_tmp, inv_len; };
|
||||||
union { Reg mac1_scratch, dst_offset; } t3;
|
union { Reg t5, lzcr, half_shift; };
|
||||||
union { Reg mac2_scratch; } t4;
|
|
||||||
union { Reg btarget, shift_count, lookup_addr, src_z, src_offset; } t5;
|
|
||||||
};
|
};
|
||||||
/* ─── Full normalize (all 4 stages inline) ───
|
/* ─── Full normalize (all 4 stages inline) ───
|
||||||
* Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). */
|
* 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)
|
internal MipsAtom* build_normalize_v3s4(AtomArena_R aa, RegUse_build_normalize_v3s4 r)
|
||||||
MipsAtom_Proc_(aa, {
|
MipsAtom_Proc_(aa, {
|
||||||
/* Load scratch base via immediate (always Scratchpad_Loc = 0x1F800000 — the BIOS
|
load_half(r.src_offset, R_TapePtr, O_(Binds_NormalizeV3S4, src_offset)),
|
||||||
* scratchpad, aliased by every consumer's ResolveLookAtScratch struct). */
|
load_half(r.dst_offset, R_TapePtr, O_(Binds_NormalizeV3S4, dst_offset)),
|
||||||
mac_load_word_imm(r.scratch, Scratchpad_Loc),
|
LdSlot_ add_u(r.src_ptr, R_ScratchBase, r.src_offset),
|
||||||
/* Tape pop: src_offset, dst_offset = 4 bytes (packed into 1 U4: low16=src, high16=dst).
|
LdSlot_ add_u(r.dst_ptr, R_ScratchBase, r.dst_offset),
|
||||||
* Loads back-to-back fill each other's load-delay slots; the subsequent add_u
|
|
||||||
* (2 cycles after the matching load) sees a valid value. */
|
|
||||||
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.scratch, r.t5.src_offset),
|
|
||||||
LdSlot_ add_u(r.dst_ptr, r.scratch, r.t3.dst_offset),
|
|
||||||
LdSlot_ add_ui_self(R_TapePtr, S_(Binds_NormalizeV3S4)),
|
LdSlot_ add_ui_self(R_TapePtr, S_(Binds_NormalizeV3S4)),
|
||||||
|
|
||||||
/* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp.
|
mac_load_v3s4(r.src, r.src_ptr, 0),
|
||||||
* r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below).
|
|
||||||
* t5.src_offset/dst_offset are dead by here; t5 is reused for src.z in the mac_load_word_v3 below. */
|
|
||||||
mac_load_word_v3(r.src_x, r.recip_est, r.t5.src_z, r.src_ptr, 0),
|
|
||||||
|
|
||||||
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
|
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
|
||||||
LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.t5.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. */
|
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. src_ptr is dead; reuse as mac2. */
|
||||||
mac_gte_mv_from_data_r_mac123(r.t3.mac1_scratch, r.t4.mac2_scratch, r.norm), LdSlot_ nop,
|
mac_gte_mv_from_data_r_mac123(r.mac1, r.mac2, r.mac3), LdSlot_ nop,
|
||||||
add_u_self( r.norm, r.t3.mac1_scratch),
|
add_u_self( r.v_sqr_sum, r.mac1),
|
||||||
add_u_self( r.norm, r.t4.mac2_scratch),
|
add_u_self( r.v_sqr_sum, r.mac2),
|
||||||
gte_mv_to_data_r( r.norm, C2_LZCS), GteDelay_ nop2,
|
gte_mv_to_data_r( r.v_sqr_sum, C2_LZCS), GteDelay_ nop2,
|
||||||
gte_mv_from_data_r(r.shift, C2_LZCR), GteDelay_ nop,
|
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.
|
/* Stage 3: even(LZCR), half-shift, align |v|² to bit 24. */
|
||||||
* r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2.
|
mac_lzcr_round_even_half_shift(r.lzcr, r.v_sqr_sum, r.v_sqr_aligned),
|
||||||
* After the component: r_shift = even(LZCR), r_norm = half-shift, r_mac1_scratch = |v|². */
|
add_si( r.btarget, r.lzcr, -24),
|
||||||
mac_lzcr_round_even_half_shift(r.shift, r.norm, r.t3.mac1_scratch),
|
branch_lt_zero(r.btarget, atom_offset(aligned_done, srav_path)), BdSlot_ nop, /* bltz → srav_path (LZCR < 24 path) */
|
||||||
/* r_branch_tmp = LZCR - 24 (overwrites r_branch_tmp; src.z no longer needed after SQR) */
|
|
||||||
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) */
|
|
||||||
jump_rel(atom_offset(srav_path, aligned_done)), /* b → aligned_done (LZCR >= 24 path) */
|
jump_rel(atom_offset(srav_path, aligned_done)), /* b → aligned_done (LZCR >= 24 path) */
|
||||||
BdSlot_ shift_lleft_var(r.t3.mac1_scratch, r.t3.mac1_scratch, r.t5.btarget), /* src=sum (r_mac1_scratch), dst=same */
|
BdSlot_ shift_lleft_var(r.v_sqr_aligned, r.v_sqr_aligned, r.btarget),
|
||||||
atom_label(srav_path)
|
atom_label(srav_path)
|
||||||
li_s( r.t5.shift_count, 24),
|
li_s( r.shift_count, 24),
|
||||||
sub_s(r.t5.shift_count, r.t5.shift_count, r.shift),
|
sub_s(r.shift_count, r.shift_count, r.lzcr),
|
||||||
shift_aright_var(r.t3.mac1_scratch, r.t3.mac1_scratch, r.t5.shift_count), /* src=sum (r_mac1_scratch), dst=same */
|
shift_aright_var(r.v_sqr_aligned, r.v_sqr_aligned, r.shift_count),
|
||||||
atom_label(aligned_done)
|
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.half_shift, r.half_shift_tmp, 0),
|
||||||
or_u(r.shift, r.norm, 0), /* r_shift ← shift count (preserved through stage 4) */
|
add_si( r.v_sqr_aligned, r.v_sqr_aligned, -64),
|
||||||
/* r_mac1_scratch holds |v|² aligned (top bit at bit 7). */
|
shift_lleft(r.v_sqr_aligned, r.v_sqr_aligned, 1),
|
||||||
add_si( r.t3.mac1_scratch, r.t3.mac1_scratch, -64),
|
mac_load_word_imm(r.sqrtbl_index, & gte_normalize_sqr_tbl), add_u_self(r.sqrtbl_index, r.v_sqr_aligned),
|
||||||
shift_lleft(r.t3.mac1_scratch, r.t3.mac1_scratch, 1),
|
load_half(r.inv_len, r.sqrtbl_index, 0),
|
||||||
mac_load_word_imm(r.t5.lookup_addr, & gte_normalize_sqr_tbl), add_u_self(r.t5.lookup_addr, r.t3.mac1_scratch),
|
LdSlot_ nop,
|
||||||
load_half(r.norm, r.t5.lookup_addr, 0), /* r_norm = sqrtbl[aligned-64] = 1/|v| (IR0 in stage 4) */
|
|
||||||
|
|
||||||
/* r_branch_tmp held the sqrtbl base+index, NOT src.z. Reload src.z from scratch now that r_branch_tmp is free. */
|
mac_gte_general_purpose_interopolation(r.inv_len,
|
||||||
LdSlot_ load_word(r.t5.src_z, r.src_ptr, O_(V3_S4,z)), /* r_branch_tmp = src.z (for IR3 in stage 4) */
|
r.src.x, r.src.y, r.src.z,
|
||||||
|
r.res.x, r.res.y, r.res.z,
|
||||||
/* Stage 4: GPF + srav finalize (r_shift = shift count, r_norm = 1/|v|). */
|
GteDelay_ load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_ // ac_yield: word 1
|
||||||
LdSlot_ mac_gte_general_purpose_interopolation(
|
GteDelay_ add_ui_self( R_TapePtr, S_(MipsCode)) // ac_yield: word 2
|
||||||
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.t5.src_z, /* IR3 = src.z (reloaded) */
|
|
||||||
r.t4.mac2_scratch, r.recip_est, r.t5.src_z,
|
|
||||||
GteDelay_ nop,
|
|
||||||
GteDelay_ nop
|
|
||||||
),
|
),
|
||||||
/* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */
|
mac_shift_aright_var_v3s4_self(r.res, r.half_shift),
|
||||||
mac_shift_aright_var_v3_self(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.shift),
|
mac_store_v3s4(r.res, r.dst_ptr, 0),
|
||||||
/* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */
|
|
||||||
mac_store_word_v3(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, 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) ───
|
/* ─── 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).
|
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
||||||
|
|
||||||
atom_dbg_skip MipsAtomComp_(ac_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)),
|
add_ui_self( R_TapePtr, S_(MipsCode)),
|
||||||
jump_reg( R_AtomJmp), BdSlot_ nop,
|
jump_reg( R_AtomJmp), BdSlot_ nop,
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-5
@@ -55,9 +55,15 @@ FI_ Slice_MipsCode ac_shift_aright_var_v3s4_self(AtomBuilder_R ab, Reg_(V3_S4) d
|
|||||||
* 3. $t0 = bios_table_addr ; t0 = &BIOS A-function table
|
* 3. $t0 = bios_table_addr ; t0 = &BIOS A-function table
|
||||||
* 4. jalr $t0, $ra ; call BIOS(flushcache)
|
* 4. jalr $t0, $ra ; call BIOS(flushcache)
|
||||||
* nop ; branch delay slot
|
* nop ; branch delay slot
|
||||||
* 5. lw $ra, 4($sp); jr $ra ; restore & return
|
* 5. lw $ra, 4($sp)
|
||||||
* 6. sp += 8
|
* 6. sp += 8 ; load-delay
|
||||||
|
* 7. jr $ra
|
||||||
|
* nop ; BD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Note: Can't do this without having a way to do C-Runtime frame call from Tape ABI.
|
||||||
|
// Don't support this without adjusting scratchpad to save tape frame in some way.
|
||||||
internal MipsAtom_(mips_flush_icache) {
|
internal MipsAtom_(mips_flush_icache) {
|
||||||
add_ui(R_SP, R_SP, -MipsStackAlignment), // sp -= 8
|
add_ui(R_SP, R_SP, -MipsStackAlignment), // sp -= 8
|
||||||
store_word(R_RA, R_SP, S_(U4)), // sw $ra, 4($sp)
|
store_word(R_RA, R_SP, S_(U4)), // sw $ra, 4($sp)
|
||||||
@@ -65,9 +71,10 @@ internal MipsAtom_(mips_flush_icache) {
|
|||||||
add_ui(R_T0, R_0, bios_table_addr), // addiu $t0, $0, 0xA0
|
add_ui(R_T0, R_0, bios_table_addr), // addiu $t0, $0, 0xA0
|
||||||
jump_link(R_T0, R_RA), nop, // jalr $t0, $ra, BD slot
|
jump_link(R_T0, R_RA), nop, // jalr $t0, $ra, BD slot
|
||||||
load_word(R_RA, R_SP, S_(U4)), // lw $ra, 4($sp)
|
load_word(R_RA, R_SP, S_(U4)), // lw $ra, 4($sp)
|
||||||
jump_reg(R_RA), // jr $ra
|
add_ui(R_SP, R_SP, MipsStackAlignment), // sp += 8 (load-delay)
|
||||||
add_ui(R_SP, R_SP, MipsStackAlignment), // sp += 8 (BD)
|
jump_reg(R_RA), nop, // jr $ra, BD slot
|
||||||
mac_yield(),
|
// mac_yield(),
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma endregion Baked Atoms
|
#pragma endregion Baked Atoms
|
||||||
|
|||||||
+2
-1
@@ -586,6 +586,7 @@ enum { _BitOffsets = 0
|
|||||||
, jump_link(rtmp_0, rret_addr) \
|
, jump_link(rtmp_0, rret_addr) \
|
||||||
, nop \
|
, nop \
|
||||||
, load_word(rret_addr, rstack_ptr, 4) \
|
, load_word(rret_addr, rstack_ptr, 4) \
|
||||||
, jump_reg(rret_addr) \
|
|
||||||
, add_ui(rstack_ptr, rstack_ptr, MipsStackAlignment) \
|
, add_ui(rstack_ptr, rstack_ptr, MipsStackAlignment) \
|
||||||
|
, jump_reg(rret_addr) \
|
||||||
|
, nop \
|
||||||
) asm_clobber: clbr_volatile_gprs )
|
) asm_clobber: clbr_volatile_gprs )
|
||||||
|
|||||||
@@ -151,14 +151,10 @@ internal void compile_init_atoms(void) {
|
|||||||
|
|
||||||
smem.normalize_v3s4 = build_normalize_v3s4(& ab,
|
smem.normalize_v3s4 = build_normalize_v3s4(& ab,
|
||||||
RegUse_(build_normalize_v3s4) {
|
RegUse_(build_normalize_v3s4) {
|
||||||
.scratch = ralloc(),
|
.res = ralloc_v3(),
|
||||||
.src_ptr = ralloc(),
|
.t0 = ralloc(),
|
||||||
.dst_ptr = ralloc(),
|
.t1 = ralloc(),
|
||||||
.recip_est = ralloc(),
|
.t2 = ralloc(),
|
||||||
.norm = ralloc(),
|
|
||||||
.shift = ralloc(),
|
|
||||||
.src_x = ralloc(),
|
|
||||||
// .shift_count = ralloc(), /* dedicated slot for stage-3 → stage-4 shift count */
|
|
||||||
.t3 = ralloc(),
|
.t3 = ralloc(),
|
||||||
.t4 = ralloc(),
|
.t4 = ralloc(),
|
||||||
.t5 = ralloc(),
|
.t5 = ralloc(),
|
||||||
|
|||||||
+25
-1
@@ -268,6 +268,13 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
if not reg_use_schema then
|
if not reg_use_schema then
|
||||||
gpr_keys = nil
|
gpr_keys = nil
|
||||||
end
|
end
|
||||||
|
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] = {
|
items[#items + 1] = {
|
||||||
kind = "word",
|
kind = "word",
|
||||||
encoder = encoder,
|
encoder = encoder,
|
||||||
@@ -280,6 +287,11 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
invocation_ids = inv_ids,
|
invocation_ids = inv_ids,
|
||||||
outermost_invocation_id = outermost,
|
outermost_invocation_id = outermost,
|
||||||
gpr_keys = gpr_keys,
|
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] = {
|
word_events[#word_events + 1] = {
|
||||||
i = word_idx,
|
i = word_idx,
|
||||||
@@ -293,6 +305,11 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
outermost_invocation_id = outermost,
|
outermost_invocation_id = outermost,
|
||||||
word_count = 1,
|
word_count = 1,
|
||||||
gpr_keys = gpr_keys,
|
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
|
word_idx = word_idx + 1
|
||||||
end
|
end
|
||||||
@@ -550,7 +567,14 @@ local function _project_emission_inner(root_body_entry, ctx_table)
|
|||||||
local tok_line = line_of(body_off + bt.rel) or 0
|
local tok_line = line_of(body_off + bt.rel) or 0
|
||||||
if M.DELAY_MARKERS[ident] then
|
if M.DELAY_MARKERS[ident] then
|
||||||
emit_marker("delay", ident, nil, tok_line)
|
emit_marker("delay", ident, nil, tok_line)
|
||||||
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
|
if rest ~= "" then
|
||||||
process_token({ tok = rest, rel = bt.rel })
|
process_token({ tok = rest, rel = bt.rel })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -536,6 +536,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "mtc2_gpr_visibility",
|
id = "mtc2_gpr_visibility",
|
||||||
semantic = "MTC2",
|
semantic = "MTC2",
|
||||||
|
consumer = "cop2_input",
|
||||||
token = "gte_mv_to_data_r",
|
token = "gte_mv_to_data_r",
|
||||||
direction = "gpr_to_cop2_data",
|
direction = "gpr_to_cop2_data",
|
||||||
reads = { domain = "gpr", arg = 1 },
|
reads = { domain = "gpr", arg = 1 },
|
||||||
@@ -554,6 +555,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "mtc2_irgb_visibility",
|
id = "mtc2_irgb_visibility",
|
||||||
semantic = "MTC2",
|
semantic = "MTC2",
|
||||||
|
consumer = "cop2_input",
|
||||||
token = "gte_mv_to_data_r",
|
token = "gte_mv_to_data_r",
|
||||||
direction = "gpr_to_cop2_data",
|
direction = "gpr_to_cop2_data",
|
||||||
reads = { domain = "gpr", arg = 1 },
|
reads = { domain = "gpr", arg = 1 },
|
||||||
@@ -572,6 +574,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "ctc2_gpr_visibility",
|
id = "ctc2_gpr_visibility",
|
||||||
semantic = "CTC2",
|
semantic = "CTC2",
|
||||||
|
consumer = "cop2_input",
|
||||||
token = "gte_mv_to_ctrl_r",
|
token = "gte_mv_to_ctrl_r",
|
||||||
direction = "gpr_to_cop2_control",
|
direction = "gpr_to_cop2_control",
|
||||||
reads = { domain = "gpr", arg = 1 },
|
reads = { domain = "gpr", arg = 1 },
|
||||||
@@ -588,6 +591,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "mfc2_gpr_visibility",
|
id = "mfc2_gpr_visibility",
|
||||||
semantic = "MFC2",
|
semantic = "MFC2",
|
||||||
|
consumer = "gpr_read",
|
||||||
token = "gte_mv_from_data_r",
|
token = "gte_mv_from_data_r",
|
||||||
direction = "cop2_data_to_gpr",
|
direction = "cop2_data_to_gpr",
|
||||||
reads = { domain = "cop2.data", arg = 2 },
|
reads = { domain = "cop2.data", arg = 2 },
|
||||||
@@ -603,6 +607,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "cfc2_gpr_visibility",
|
id = "cfc2_gpr_visibility",
|
||||||
semantic = "CFC2",
|
semantic = "CFC2",
|
||||||
|
consumer = "gpr_read",
|
||||||
token = "gte_mv_from_ctrl_r",
|
token = "gte_mv_from_ctrl_r",
|
||||||
direction = "cop2_control_to_gpr",
|
direction = "cop2_control_to_gpr",
|
||||||
reads = { domain = "cop2.ctrl", arg = 2 },
|
reads = { domain = "cop2.ctrl", arg = 2 },
|
||||||
@@ -621,6 +626,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "mfc0_gpr_visibility",
|
id = "mfc0_gpr_visibility",
|
||||||
semantic = "MFC0",
|
semantic = "MFC0",
|
||||||
|
consumer = "gpr_read",
|
||||||
token = "sys_mov_from_cop0",
|
token = "sys_mov_from_cop0",
|
||||||
direction = "cop0_control_to_gpr",
|
direction = "cop0_control_to_gpr",
|
||||||
reads = { domain = "cop0.ctrl", arg = 2 },
|
reads = { domain = "cop0.ctrl", arg = 2 },
|
||||||
@@ -643,6 +649,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "lwc2_to_gte_command",
|
id = "lwc2_to_gte_command",
|
||||||
semantic = "LWC2_to_GTE",
|
semantic = "LWC2_to_GTE",
|
||||||
|
consumer = "cop2_input",
|
||||||
token = "gte_lw",
|
token = "gte_lw",
|
||||||
direction = "memory_to_cop2_data",
|
direction = "memory_to_cop2_data",
|
||||||
reads = { domain = "memory", arg = 2 },
|
reads = { domain = "memory", arg = 2 },
|
||||||
@@ -658,6 +665,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "lwc2_to_other_consumer",
|
id = "lwc2_to_other_consumer",
|
||||||
semantic = "LWC2_to_other",
|
semantic = "LWC2_to_other",
|
||||||
|
consumer = "cop2_input",
|
||||||
token = "gte_lw",
|
token = "gte_lw",
|
||||||
direction = "memory_to_cop2_data",
|
direction = "memory_to_cop2_data",
|
||||||
reads = { domain = "memory", arg = 2 },
|
reads = { domain = "memory", arg = 2 },
|
||||||
@@ -675,6 +683,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "swc2_memory_write",
|
id = "swc2_memory_write",
|
||||||
semantic = "SWC2",
|
semantic = "SWC2",
|
||||||
|
consumer = "gpr_read",
|
||||||
token = "gte_sw",
|
token = "gte_sw",
|
||||||
direction = "cop2_data_to_memory",
|
direction = "cop2_data_to_memory",
|
||||||
reads = { domain = "cop2.data", arg = 1 },
|
reads = { domain = "cop2.data", arg = 1 },
|
||||||
@@ -692,6 +701,7 @@ M.HARDWARE_RELATIONS = {
|
|||||||
{
|
{
|
||||||
id = "mtc0_cu2_visibility",
|
id = "mtc0_cu2_visibility",
|
||||||
semantic = "MTC0",
|
semantic = "MTC0",
|
||||||
|
consumer = "gpr_read",
|
||||||
token = "sys_mov_to_cop0",
|
token = "sys_mov_to_cop0",
|
||||||
direction = "gpr_to_cop0_status",
|
direction = "gpr_to_cop0_status",
|
||||||
reads = { domain = "gpr", arg = 1 },
|
reads = { domain = "gpr", arg = 1 },
|
||||||
|
|||||||
@@ -329,6 +329,24 @@ local function strip_mac_prefix(ident)
|
|||||||
return ident
|
return ident
|
||||||
end
|
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
|
--- (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).
|
--- 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_`)
|
--- @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
|
for _, t in ipairs(tokens) do
|
||||||
local trimmed = t.tok
|
local trimmed = t.tok
|
||||||
if trimmed ~= "" then
|
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
|
if lookup == "atom_label" or lookup == "atom_offset" then
|
||||||
-- Pure metaprogram anchors; emit zero words.
|
-- Pure metaprogram anchors; emit zero words.
|
||||||
elseif lookup and comp_by_name[lookup] then
|
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
|
||||||
end
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- Not a known component: assume 1 word (regular instruction).
|
-- Not a known component: assume 1 word (regular instruction).
|
||||||
n = 1
|
n = 1
|
||||||
@@ -912,7 +942,7 @@ function M.run(ctx)
|
|||||||
aggregated_components[#aggregated_components + 1] = c
|
aggregated_components[#aggregated_components + 1] = c
|
||||||
end
|
end
|
||||||
if #per_source > 0 then
|
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
|
||||||
end
|
end
|
||||||
if #aggregated_components > 0 then
|
if #aggregated_components > 0 then
|
||||||
|
|||||||
@@ -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.
|
-- (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)
|
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.
|
-- DWARF5 §7.7.3 loclist opcodes.
|
||||||
local DW_LLE_end_of_list = 0x00
|
local DW_LLE_end_of_list = 0x00
|
||||||
local DW_LLE_start_length = 0x08
|
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.bytes[#S.bytes + 1] = s
|
||||||
S.next_offset = S.next_offset + #s
|
S.next_offset = S.next_offset + #s
|
||||||
end
|
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)
|
local function ref4_of(section_offset)
|
||||||
if section_offset == nil then return 0 end
|
if section_offset == nil then return 0 end
|
||||||
return section_offset - main_cu_offset
|
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).
|
-- 1) Emit the base_type DIE first (member ref4s reference it).
|
||||||
local base_type_section_offset = S.next_offset
|
local base_type_section_offset = S.next_offset
|
||||||
emit(uleb128(ABBREV_BASE_TYPE))
|
emit_die("base_type", {
|
||||||
emit("unsigned int\0") -- DW_FORM_string (DW_AT_name)
|
name = "unsigned int",
|
||||||
emit(string.char(4)) -- DW_FORM_data1 (DW_AT_byte_size)
|
byte_size = 4,
|
||||||
emit(string.char(DW_ATE_unsigned)) -- DW_FORM_data1 (DW_AT_encoding)
|
encoding = DW_ATE_unsigned,
|
||||||
|
})
|
||||||
-- The function body below reads S.next_offset directly via the `next_offset` function;
|
-- The function body below reads S.next_offset directly via the `next_offset` function;
|
||||||
-- this keeps offsets synchronized with emitted data.
|
-- this keeps offsets synchronized with emitted data.
|
||||||
local function next_offset() return S.next_offset end
|
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
|
for _, comp_name in ipairs(sorted_comp_names) do
|
||||||
local def = component_defs[comp_name]
|
local def = component_defs[comp_name]
|
||||||
abstract_offsets[comp_name] = next_offset()
|
abstract_offsets[comp_name] = next_offset()
|
||||||
emit(uleb128(ABBREV_ABSTRACT_SUBPROGRAM))
|
emit_die("abstract_subprogram", {
|
||||||
emit("mac_" .. comp_name .. "\0") -- DW_FORM_string (DW_AT_name)
|
name = "mac_" .. comp_name,
|
||||||
emit(string.char(DW_INL_inlined)) -- DW_FORM_data1 (DW_AT_inline)
|
inline = DW_INL_inlined,
|
||||||
emit(string.char(0x01)) -- DW_FORM_data1 (DW_AT_external=1)
|
external = 0x01,
|
||||||
-- decl_file + decl_line resolve the abstract origin back to its definition site even when no inlined_subroutine instance maps to it.
|
decl_file = resolve_provenance_file_index(def.def_file),
|
||||||
emit(uleb128(resolve_provenance_file_index(def.def_file))) -- DW_FORM_udata (DW_AT_decl_file)
|
decl_line = def.def_line,
|
||||||
emit(uleb128(def.def_line)) -- DW_FORM_udata (DW_AT_decl_line)
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 4) Emit per-atom DW_TAG_subprograms (children of main CU).
|
-- 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 `<name>[]` is a DW_TAG_variable without children; our subprogram has the wave-context var children.
|
-- The gcc global `<name>[]` 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.
|
-- 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
|
for _, atom in ipairs(atom_table) do
|
||||||
emit(uleb128(ABBREV_SUBPROGRAM))
|
emit_die("subprogram", {
|
||||||
emit(atom.name .. "\0") -- DW_FORM_string (DW_AT_name)
|
name = atom.name,
|
||||||
emit(elf_dwarf.write_u32_le(atom.addr))
|
low_pc = atom.addr,
|
||||||
emit(elf_dwarf.write_u32_le(atom.addr + atom.size_bytes))
|
high_pc = 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)
|
linkage_name = atom.name,
|
||||||
|
})
|
||||||
|
|
||||||
-- Per debug-visible R_ alias (filtered to GPR 0..31 in `by_alias`): DW_TAG_variable.
|
-- Per debug-visible R_ alias (filtered to GPR 0..31 in `by_alias`): DW_TAG_variable.
|
||||||
-- Precedence chain (per atom, per RR_<R_Name>):
|
-- Precedence chain (per atom, per RR_<R_Name>):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
--- Public boundary:
|
--- Public boundary:
|
||||||
--- * `M.run(ctx)` is the only entry point.
|
--- * `M.run(ctx)` is the only entry point.
|
||||||
--- * The pass returns `{outputs = {}, errors = ..., warnings = ...}`.
|
--- * 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:
|
--- Source-order discipline:
|
||||||
--- * `corpus.source_order` sets the source-record order.
|
--- * `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.
|
--- This preserves backward compatibility for any top-level marker that may exist outside a control-transfer instruction.
|
||||||
--- @param labels table<string, integer>
|
--- @param labels table<string, integer>
|
||||||
--- @param branches table[]
|
--- @param branches table[]
|
||||||
|
--- @param errors table[]
|
||||||
--- @return BranchOffset[]
|
--- @return BranchOffset[]
|
||||||
local function compute_offsets(labels, branches)
|
local function compute_offsets(labels, branches, errors)
|
||||||
local results = {}
|
local results = {}
|
||||||
for _, br in ipairs(branches) do
|
for _, br in ipairs(branches) do
|
||||||
local target = labels[br.target]
|
local target = labels[br.target]
|
||||||
if not target then
|
if not target then
|
||||||
error("Branch target '" .. br.target .. "' has no atom_label (at word " .. br.branch_word .. ")")
|
errors[#errors + 1] = {
|
||||||
end
|
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 consuming = br.consuming_encoder
|
||||||
local offset
|
|
||||||
if consuming == "jump_reg" or consuming == "call_reg" or consuming == "jump_link" then
|
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.
|
errors[#errors + 1] = {
|
||||||
error("atom_offset cannot be used with " .. consuming
|
line = br.line or 0,
|
||||||
.. " (register-form jumps have no offset field); at word " .. br.branch_word)
|
msg = "atom_offset cannot be used with " .. consuming
|
||||||
end
|
.. " (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.
|
-- 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.
|
-- 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] = {
|
results[#results + 1] = {
|
||||||
target = br.target,
|
target = br.target,
|
||||||
tag = br.tag,
|
tag = br.tag,
|
||||||
branch_word = br.branch_word,
|
branch_word = br.branch_word,
|
||||||
offset = offset,
|
offset = target - br.branch_word - 1,
|
||||||
consuming_encoder = br.consuming_encoder,
|
consuming_encoder = br.consuming_encoder,
|
||||||
consuming_arg_pos = br.consuming_arg_pos,
|
consuming_arg_pos = br.consuming_arg_pos,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
return results
|
return results
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -237,8 +243,9 @@ local M = {}
|
|||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @param dir string -- the absolute source directory
|
--- @param dir string -- the absolute source directory
|
||||||
--- @param sources SourceFile[] -- sources in this directory
|
--- @param sources SourceFile[] -- sources in this directory
|
||||||
|
--- @param errors table[]
|
||||||
--- @return string|nil -- the offsets_h path
|
--- @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 atoms_data = {}
|
||||||
|
|
||||||
local function append_atom(atom)
|
local function append_atom(atom)
|
||||||
@@ -248,7 +255,7 @@ local function process_directory(ctx, dir, sources)
|
|||||||
atoms_data[#atoms_data + 1] = {
|
atoms_data[#atoms_data + 1] = {
|
||||||
name = atom.raw_name or atom.name,
|
name = atom.raw_name or atom.name,
|
||||||
total_words = #(paths.word_events or {}),
|
total_words = #(paths.word_events or {}),
|
||||||
offsets = compute_offsets(labels, branches),
|
offsets = compute_offsets(labels, branches, errors),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -286,7 +293,7 @@ function M.run(ctx)
|
|||||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
||||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order)
|
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
|
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
|
if out_path then
|
||||||
outputs[#outputs + 1] = { offsets_h = out_path }
|
outputs[#outputs + 1] = { offsets_h = out_path }
|
||||||
end
|
end
|
||||||
|
|||||||
+197
-138
@@ -1674,9 +1674,20 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
local members = {}
|
local views = {}
|
||||||
local union_readonly = nil
|
local union_readonly = nil
|
||||||
local inner_pos = 1
|
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
|
while inner_pos <= #inner do
|
||||||
inner_pos = duffle.skip_ws_and_cmt(inner, inner_pos)
|
inner_pos = duffle.skip_ws_and_cmt(inner, inner_pos)
|
||||||
if inner_pos > #inner then break end
|
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" }
|
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
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" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
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_after = duffle.skip_ws_and_cmt(inner, m_type_end)
|
||||||
local m_readonly = false
|
local m_readonly = false
|
||||||
local maybe_const, maybe_end = duffle.read_ident(inner, m_after)
|
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_readonly = true
|
||||||
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
|
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
|
||||||
end
|
end
|
||||||
if union_readonly == nil then
|
if not note_readonly(m_readonly) then return nil, errors end
|
||||||
union_readonly = m_readonly
|
|
||||||
elseif union_readonly ~= m_readonly then
|
|
||||||
errors[#errors + 1] = { kind = "reguse_mixed_const" }
|
|
||||||
return nil, errors
|
|
||||||
end
|
|
||||||
local names, new_inner = parse_reg_names(inner, m_after)
|
local names, new_inner = parse_reg_names(inner, m_after)
|
||||||
if not names or #names == 0 then
|
if not names or #names == 0 then
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
for _, n in ipairs(names) do members[#members + 1] = n end
|
views[#views + 1] = { names = names, lanes = false }
|
||||||
inner_pos = new_inner
|
inner_pos = new_inner
|
||||||
::continue_inner::
|
else
|
||||||
end
|
|
||||||
if #members == 0 then
|
|
||||||
errors[#errors + 1] = { kind = "reguse_malformed" }
|
errors[#errors + 1] = { kind = "reguse_malformed" }
|
||||||
return nil, errors
|
return nil, errors
|
||||||
end
|
end
|
||||||
|
::continue_inner::
|
||||||
|
end
|
||||||
|
|
||||||
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
|
||||||
local inst_name, inst_end = duffle.read_ident(body, after_close)
|
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 aliases = {}
|
||||||
local slot_name
|
local slot_name
|
||||||
if inst_name then
|
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
|
if not add_alias(path, slot_name) then return nil, errors end
|
||||||
aliases[#aliases + 1] = path
|
aliases[#aliases + 1] = path
|
||||||
end
|
end
|
||||||
after_close = inst_end
|
|
||||||
else
|
else
|
||||||
slot_name = members[1]
|
slot_name = members[1]
|
||||||
for _, m in ipairs(members) do
|
for _, m in ipairs(members) do
|
||||||
@@ -1739,6 +1918,10 @@ parse_reg_use_schema_body = function(body, type_registry, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not add_slot(slot_name, aliases, union_readonly) then return nil, errors 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)
|
after_close = duffle.skip_ws_and_cmt(body, after_close)
|
||||||
if body:sub(after_close, after_close) == ";" then after_close = after_close + 1 end
|
if body:sub(after_close, after_close) == ";" then after_close = after_close + 1 end
|
||||||
pos = after_close
|
pos = after_close
|
||||||
@@ -2207,131 +2390,6 @@ local DECL_PARSERS = {
|
|||||||
-- Only the bare `atom_dbg_skip` marker reaches `parse_dbg_skip_marker`.
|
-- 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.
|
-- 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
|
-- The single source walker
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -2801,6 +2859,7 @@ local SCHEMA_BODY_ERROR = {
|
|||||||
reguse_duplicate_slot = true,
|
reguse_duplicate_slot = true,
|
||||||
reguse_const_reg_spelling = true,
|
reguse_const_reg_spelling = true,
|
||||||
reguse_mixed_const = true,
|
reguse_mixed_const = true,
|
||||||
|
reguse_union_width = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Re-parse every RegUse_* body against the merged type_name_registry.
|
-- Re-parse every RegUse_* body against the merged type_name_registry.
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ local BRANCH_PATTERN = "^branch_[%w_]+%s*%("
|
|||||||
-- This keeps `consuming_encoder` canonical for any downstream tooling that consults the metadata field.
|
-- This keeps `consuming_encoder` canonical for any downstream tooling that consults the metadata field.
|
||||||
local JUMP_REL_PATTERN = "^jump_rel%s*%("
|
local JUMP_REL_PATTERN = "^jump_rel%s*%("
|
||||||
|
|
||||||
local function classify_tokens(tokens)
|
local function tok_class_view(tokens)
|
||||||
local n = #tokens
|
local n = #tokens
|
||||||
local tc = {}
|
local tc = {}
|
||||||
local nop_run = 0 -- running count of consecutive nop words (forward pass)
|
local nop_run = 0 -- running count of consecutive nop words (forward pass)
|
||||||
@@ -811,6 +811,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
pending = {},
|
pending = {},
|
||||||
cu2_state = "unobserved",
|
cu2_state = "unobserved",
|
||||||
cu2_transition = nil,
|
cu2_transition = nil,
|
||||||
|
c2_ctrl_writes = {},
|
||||||
_analysis_complete = false,
|
_analysis_complete = false,
|
||||||
}
|
}
|
||||||
-- The architectural zero register is always a known U4 zero and cannot be invalidated by an emitted writer.
|
-- The architectural zero register is always a known U4 zero and cannot be invalidated by an emitted writer.
|
||||||
@@ -847,38 +848,64 @@ local function analyze_hardware_relations(atom)
|
|||||||
-- Both operations are part of this one event walk.
|
-- Both operations are part of this one event walk.
|
||||||
stage_cu2_transition(ev_ident, ev_args, ev_word, ev_line, ev_source, forward)
|
stage_cu2_transition(ev_ident, ev_args, ev_word, ev_line, ev_source, forward)
|
||||||
consume_cu2_transition(atom, ev, ev_word, 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. ──
|
-- ── 1. Inspect pending relations against the event as CONSUMER. ──
|
||||||
-- Walk pending in REVERSE so `table.remove` doesn't shift indexes still to be inspected.
|
-- 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
|
for pending_idx = #pending, 1, -1 do
|
||||||
local prod = pending[pending_idx]
|
local prod = pending[pending_idx]
|
||||||
local relation = prod.relation
|
local relation = prod.relation
|
||||||
local semantic = relation.semantic
|
local match_fn = CONSUMER[relation.consumer]
|
||||||
local is_match = false
|
local is_match = match_fn and match_fn(ev, prod)
|
||||||
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
|
|
||||||
if is_match then
|
if is_match then
|
||||||
local gap = ev_word - prod.word - 1
|
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 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"
|
local unknown_visibility = relation.visibility and relation.visibility.kind == "unknown_consumer"
|
||||||
if unknown_visibility then
|
if unknown_visibility then
|
||||||
hazards[#hazards + 1] = {
|
hazards[#hazards + 1] = {
|
||||||
@@ -906,7 +933,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
, (relation.evidence and relation.evidence.confidence or "unknown")
|
, (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 = {
|
local payload = {
|
||||||
check = "transfer_hazards",
|
check = "transfer_hazards",
|
||||||
kind = prod.violation_kind or "error",
|
kind = prod.violation_kind or "error",
|
||||||
@@ -923,7 +950,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
consumer_word = ev_word,
|
consumer_word = ev_word,
|
||||||
consumer_token = ev_ident,
|
consumer_token = ev_ident,
|
||||||
gap = gap,
|
gap = gap,
|
||||||
required = prod.required,
|
required = required,
|
||||||
evidence_confidence = relation.evidence and relation.evidence.confidence or "unknown",
|
evidence_confidence = relation.evidence and relation.evidence.confidence or "unknown",
|
||||||
evidence_source = relation.evidence and relation.evidence.source or "",
|
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]"
|
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 +968,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
hazards[#hazards + 1] = payload
|
hazards[#hazards + 1] = payload
|
||||||
end
|
end
|
||||||
local satisfied = nil
|
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.
|
-- Record the relation touch on `paths.relations` even when the gap is satisfied.
|
||||||
-- Unknown relations are informational, not numeric pass/fail measurements.
|
-- Unknown relations are informational, not numeric pass/fail measurements.
|
||||||
relations[#relations + 1] = {
|
relations[#relations + 1] = {
|
||||||
@@ -951,7 +978,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
consumer_word = ev_word,
|
consumer_word = ev_word,
|
||||||
destination = prod.destination,
|
destination = prod.destination,
|
||||||
gap = gap,
|
gap = gap,
|
||||||
required = prod.required,
|
required = required,
|
||||||
satisfied = satisfied,
|
satisfied = satisfied,
|
||||||
}
|
}
|
||||||
table.remove(pending, pending_idx)
|
table.remove(pending, pending_idx)
|
||||||
@@ -1041,6 +1068,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
relation = {
|
relation = {
|
||||||
id = "command_latch_input",
|
id = "command_latch_input",
|
||||||
semantic = "command_latch",
|
semantic = "command_latch",
|
||||||
|
consumer = "overwrite_same_dest",
|
||||||
direction = "gte_command_to_cop2_register",
|
direction = "gte_command_to_cop2_register",
|
||||||
token = canonical,
|
token = canonical,
|
||||||
evidence = {
|
evidence = {
|
||||||
@@ -1377,6 +1405,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
relation = {
|
relation = {
|
||||||
id = "command_latch_input",
|
id = "command_latch_input",
|
||||||
semantic = "command_latch",
|
semantic = "command_latch",
|
||||||
|
consumer = "overwrite_same_dest",
|
||||||
},
|
},
|
||||||
destination = latch.register,
|
destination = latch.register,
|
||||||
word = ev_word,
|
word = ev_word,
|
||||||
@@ -2090,7 +2119,7 @@ end
|
|||||||
--- plus `mac_*` idents whose bare name is missing from `pipe_ctx.components_by_name` (i.e. no `MipsAtomComp_` for it).
|
--- 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 function analyze_atom_paths(atom, pipe_ctx)
|
||||||
local tokens = atom.paths.tokens or duffle.tokenize_body(atom.body)
|
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
|
local n = #tokens
|
||||||
|
|
||||||
-- Build label + branch maps from the pre-computed classification (no re-scan).
|
-- Build label + branch maps from the pre-computed classification (no re-scan).
|
||||||
@@ -2546,6 +2575,8 @@ local function ctrl_alias_from_text(text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function ctrl_writes_in_atom(atom)
|
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 = {}
|
local out = {}
|
||||||
for _, ev in ipairs((atom.paths and atom.paths.word_events) or {}) do
|
for _, ev in ipairs((atom.paths and atom.paths.word_events) or {}) do
|
||||||
if (ev.encoder or "") == "gte_mv_to_ctrl_r" then
|
if (ev.encoder or "") == "gte_mv_to_ctrl_r" then
|
||||||
@@ -2564,18 +2595,6 @@ local function ctrl_writes_in_atom(atom)
|
|||||||
end
|
end
|
||||||
end
|
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
|
return out
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2986,16 +3005,16 @@ local function arg_as_gpr(arg)
|
|||||||
return arg:match("^R_[%w_]+$")
|
return arg:match("^R_[%w_]+$")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function collect_gpr_traffic(tokens)
|
local function collect_gpr_traffic(atom)
|
||||||
local reads, writes = {}, {}
|
local reads, writes = {}, {}
|
||||||
for _, t in ipairs(tokens or {}) do
|
local events = (atom and atom.paths and atom.paths.word_events) or {}
|
||||||
local tok = t.tok or t
|
for _, ev in ipairs(events) do
|
||||||
local ident = (tok or ""):match("^([%w_]+)") or ""
|
local ident = ev.encoder or ev.ident or ""
|
||||||
if ident:sub(1, 4) ~= "mac_"
|
if ident:sub(1, 4) ~= "mac_"
|
||||||
and not (duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[ident])
|
and not (duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[ident])
|
||||||
and ident ~= "nop" and ident ~= "atom_label" and ident ~= "atom_offset"
|
and ident ~= "nop" and ident ~= "atom_label" and ident ~= "atom_offset"
|
||||||
then
|
then
|
||||||
local args = token_arg_list(tok)
|
local args = ev.args or {}
|
||||||
local fx = duffle.instr(ident)
|
local fx = duffle.instr(ident)
|
||||||
if fx then
|
if fx then
|
||||||
for _, pos in ipairs(fx.reads or {}) do
|
for _, pos in ipairs(fx.reads or {}) do
|
||||||
@@ -3049,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
|
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 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
|
for _, t in ipairs(tokens or {}) do
|
||||||
local ident = ((t.tok or t) or ""):match("^([%w_]+)") or ""
|
local ident = ((t.tok or t) or ""):match("^([%w_]+)") or ""
|
||||||
if ident:sub(1, 4) == "mac_" then
|
if ident:sub(1, 4) == "mac_" then
|
||||||
@@ -3272,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`.
|
-- `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.
|
-- Supply tokens when no emission projection is present.
|
||||||
if a.paths.tokens == nil then a.paths.tokens = a.body_tokens end
|
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 fills the *cycles / branches / has_loops / unknown_macros* fields of a.paths.
|
||||||
analyze_atom_paths(a, pipe_ctx)
|
analyze_atom_paths(a, pipe_ctx)
|
||||||
|
|||||||
+9
-13
@@ -142,8 +142,7 @@ local PASSES = {
|
|||||||
},
|
},
|
||||||
["static-analysis"] = {
|
["static-analysis"] = {
|
||||||
module = "passes.static_analysis",
|
module = "passes.static_analysis",
|
||||||
-- "diagnostic" — every `error`/`warning` finding is written to the report file;
|
-- "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).
|
|
||||||
-- Report severity is independent from process exit policy.
|
-- Report severity is independent from process exit policy.
|
||||||
kind = "diagnostic",
|
kind = "diagnostic",
|
||||||
deps = {"scan-source", "word-counts", "components", "emission-model"},
|
deps = {"scan-source", "word-counts", "components", "emission-model"},
|
||||||
@@ -206,16 +205,13 @@ local function request_roots_for_group(args, group_name)
|
|||||||
end
|
end
|
||||||
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.
|
-- Report severity is independent from process exit policy.
|
||||||
-- A "diagnostic" pass still writes every `error`/`warning` finding into its report file,
|
-- Adding a new pass kind requires listing it here explicitly; an unknown kind must not silently fall back to "true".
|
||||||
-- 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".
|
|
||||||
local PASS_KIND_STOP_ON_ERROR = {
|
local PASS_KIND_STOP_ON_ERROR = {
|
||||||
["shared"] = false,
|
["shared"] = false,
|
||||||
["header-output"] = true,
|
["header-output"] = false,
|
||||||
["validation"] = true,
|
["validation"] = false,
|
||||||
["diagnostic"] = false,
|
["diagnostic"] = false,
|
||||||
["report"] = false,
|
["report"] = false,
|
||||||
}
|
}
|
||||||
@@ -693,19 +689,19 @@ end
|
|||||||
-- Main Orchestrator
|
-- Main Orchestrator
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- (internal) If the pass's kind is in PASS_KIND_STOP_ON_ERROR and it reported errors, write each error to stderr.
|
--- (internal) Write every pass error to stderr.
|
||||||
--- Returns true if any validation errors were reported.
|
--- Returns true only when the pass kind still stops the build.
|
||||||
--- @param pass_name string
|
--- @param pass_name string
|
||||||
--- @param pass PassDescriptor
|
--- @param pass PassDescriptor
|
||||||
--- @param result PassResult
|
--- @param result PassResult
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
local function report_validation_errors(pass_name, pass, result)
|
local function report_validation_errors(pass_name, pass, result)
|
||||||
local has_errors = result.errors and #result.errors > 0
|
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
|
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 ""))
|
io.stderr:write(string.format("[%s] line %d: %s\n", pass_name, e.line or 0, e.msg or ""))
|
||||||
end
|
end
|
||||||
return true
|
return PASS_KIND_STOP_ON_ERROR[pass.kind] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Run each pass in `order` in topological sequence.
|
--- (internal) Run each pass in `order` in topological sequence.
|
||||||
|
|||||||
Reference in New Issue
Block a user