mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-09 00:58:16 +00:00
Compare commits
4
Commits
352a8c9c25
...
26a63ed908
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26a63ed908 | ||
|
|
c293e35cb4 | ||
|
|
b29fc7dc02 | ||
|
|
ffcd43c1ea |
@@ -36,3 +36,7 @@ charset = utf-8
|
|||||||
[*.{natvis, natstepfilter}]
|
[*.{natvis, natstepfilter}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.lua]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
|
|||||||
@@ -1,50 +1,12 @@
|
|||||||
// Auto-generated by gen_atom_offsets.lua — DO NOT EDIT
|
// Auto-generated by tape_atom_offset_gen.meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
#ifndef LOTTES_TAPE_OFFSETS_H
|
#pragma once
|
||||||
#define LOTTES_TAPE_OFFSETS_H
|
|
||||||
|
|
||||||
#pragma region lottes_tape
|
#pragma region lottes_tape
|
||||||
|
|
||||||
// Override the placeholder atom_offset() to dispatch via token pasting.
|
// Dispatch macro: token-pastes <tag>_<target> to the enum name
|
||||||
#undef atom_offset
|
#undef atom_offset
|
||||||
#define atom_offset(name) atom_offset_##name
|
#define atom_offset(tag, name) atom_offset_##tag##_##name
|
||||||
|
|
||||||
// --- atom: sym (8 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: tape_exit (2 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: yield (4 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: mips_flush_icache (13 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: sync_prim_cursor (6 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: set_gte_world (22 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: rbind_cube_tri (6 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: cube_tri (74 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: rbind_floor_tri (6 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: diag_yield (4 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: diag_color (28 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: diag_gte (34 words) ---
|
|
||||||
|
|
||||||
|
|
||||||
#pragma endregion lottes_tape
|
#pragma endregion lottes_tape
|
||||||
|
|
||||||
#endif // LOTTES_TAPE_OFFSETS_H
|
|
||||||
|
|||||||
+47
-46
@@ -18,20 +18,20 @@ typedef U4 const MipsCode;
|
|||||||
* The C compiler is completely unaware of these bindings.
|
* The C compiler is completely unaware of these bindings.
|
||||||
* ---------------------------------------------------------------------------*/
|
* ---------------------------------------------------------------------------*/
|
||||||
enum {
|
enum {
|
||||||
R_AtomJmp = R_T9,
|
R_AtomJmp = R_T9,
|
||||||
R_TapePtr = R_T8, /* The Instruction Stream Pointer */
|
R_TapePtr = R_T8, /* The Instruction Stream Pointer */
|
||||||
R_PrimCur = R_T7, /* VRAM output cursor (primitive buffer) */
|
R_PrimCursor = R_T7, /* VRAM output cursor (primitive buffer) */
|
||||||
R_FaceCur = R_T4, /* Input data cursor (indices/faces) */
|
R_FaceCursor = R_T4, /* Input data cursor (indices/faces) */
|
||||||
R_InCursor = R_T4, /* Input data cursor (indices/faces) */
|
R_InCursor = R_T4, /* Input data cursor (indices/faces) */
|
||||||
R_VertBase = R_T5, /* Base address of the vertex array */
|
R_VertBase = R_T5, /* Base address of the vertex array */
|
||||||
R_OtBase = R_T6, /* Base address of the Ordering Table */
|
R_OtBase = R_T6, /* Base address of the Ordering Table */
|
||||||
/* Stringification codes for the GCC inline assembler clobber lists */
|
/* Stringification codes for the GCC inline assembler clobber lists */
|
||||||
#define R_TapePtr_Code R_T8_Code
|
#define R_TapePtr_Code R_T8_Code
|
||||||
#define R_PrimCur_Code R_T7_Code
|
#define R_PrimCursor_Code R_T7_Code
|
||||||
#define R_FaceCur_Code R_T4_Code
|
#define R_FaceCursor_Code R_T4_Code
|
||||||
#define R_InCursor_Code R_T4_Code
|
#define R_InCursor_Code R_T4_Code
|
||||||
#define R_VertBase_Code R_T5_Code
|
#define R_VertBase_Code R_T5_Code
|
||||||
#define R_OtBase_Code R_T6_Code
|
#define R_OtBase_Code R_T6_Code
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The 'Exit' Atom */
|
/* The 'Exit' Atom */
|
||||||
@@ -55,14 +55,15 @@ FI_ void tape_run(Slice_U4 tape) { register U4* tp rgcc(R_TapePtr) = tape.ptr; a
|
|||||||
, rlit(R_V0), rlit(R_V1)
|
, rlit(R_V0), rlit(R_V1)
|
||||||
, rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3)
|
, rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3)
|
||||||
/* Tell GCC the tape engine owns and destroys the workspace registers */
|
/* Tell GCC the tape engine owns and destroys the workspace registers */
|
||||||
, rlit(R_PrimCur), rlit(R_FaceCur), rlit(R_VertBase), rlit(R_OtBase)
|
, rlit(R_PrimCursor), rlit(R_FaceCursor), rlit(R_VertBase), rlit(R_OtBase)
|
||||||
, rlit(R_T9)
|
, rlit(R_T9)
|
||||||
, clb_mem_drain
|
, clb_mem_drain
|
||||||
); }
|
); }
|
||||||
|
|
||||||
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
|
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
|
||||||
FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; }
|
FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; }
|
||||||
FI_ TapeBuilder tb_make( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
|
FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
|
||||||
|
FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ mem.ptr, mem.len, 0 }; }
|
||||||
|
|
||||||
#define tb_emit_(tb, atom) tb_emit(tb, tmpl(code,atom))
|
#define tb_emit_(tb, atom) tb_emit(tb, tmpl(code,atom))
|
||||||
FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; }
|
FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; }
|
||||||
@@ -91,9 +92,9 @@ FI_ Slice_U4 tb_slice(TapeBuilder tb) { return (Sli
|
|||||||
|
|
||||||
/* Words: 3; Loads 3 S2 indices from the face array */
|
/* Words: 3; Loads 3 S2 indices from the face array */
|
||||||
#define mac_load_tri_indices(rId_0, rId_1, rId_2) \
|
#define mac_load_tri_indices(rId_0, rId_1, rId_2) \
|
||||||
load_half_u(rId_0, R_FaceCur, 0) \
|
load_half_u(rId_0, R_FaceCursor, 0) \
|
||||||
, load_half_u(rId_1, R_FaceCur, 2) \
|
, load_half_u(rId_1, R_FaceCursor, 2) \
|
||||||
, load_half_u(rId_2, R_FaceCur, 4)
|
, load_half_u(rId_2, R_FaceCursor, 4)
|
||||||
|
|
||||||
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE
|
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE
|
||||||
R_AT = rId_[#] << 3;
|
R_AT = rId_[#] << 3;
|
||||||
@@ -116,8 +117,8 @@ FI_ Slice_U4 tb_slice(TapeBuilder tb) { return (Sli
|
|||||||
, shift_ll( R_AT, R_AT, 8) /* Strip upper 8 bits from old_ot */ \
|
, shift_ll( R_AT, R_AT, 8) /* Strip upper 8 bits from old_ot */ \
|
||||||
, shift_lr( R_AT, R_AT, 8) \
|
, shift_lr( R_AT, R_AT, 8) \
|
||||||
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
||||||
, store_word(R_AT, R_PrimCur, 0) /* prim->tag = old_ot_head */ \
|
, store_word(R_AT, R_PrimCursor, 0) /* prim->tag = old_ot_head */ \
|
||||||
, shift_ll( R_AT, R_PrimCur, 8) /* AT = PrimCur & 0x00FFFFFF */ \
|
, shift_ll( R_AT, R_PrimCursor, 8) /* AT = PrimCur & 0x00FFFFFF */ \
|
||||||
, shift_lr( R_AT, R_AT, 8) \
|
, shift_lr( R_AT, R_AT, 8) \
|
||||||
, store_word(R_AT, R_T1, 0) /* OrderingTable[OTZ] = PrimCur */
|
, store_word(R_AT, R_T1, 0) /* OrderingTable[OTZ] = PrimCur */
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ internal MipsAtom_(sync_prim_cursor) {
|
|||||||
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimCursor,PrimtiveBase)),
|
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimCursor,PrimtiveBase)),
|
||||||
add_ui_1( R_TapePtr, S_(Binds_SyncPrimCursor)),
|
add_ui_1( R_TapePtr, S_(Binds_SyncPrimCursor)),
|
||||||
/* Calculate byte offset and store directly back to RAM */
|
/* Calculate byte offset and store directly back to RAM */
|
||||||
sub_u(R_T0, R_PrimCur, R_T0), // R_T0 = PrimitiveArea_Used(R_AT) - R_PrimCur
|
sub_u(R_T0, R_PrimCursor, R_T0), // R_T0 = PrimitiveArea_Used(R_AT) - R_PrimCur
|
||||||
store_word(R_T0, R_AT, 0), // PrimitiveBase(R_AT)[0] = R_T0
|
store_word(R_T0, R_AT, 0), // PrimitiveBase(R_AT)[0] = R_T0
|
||||||
mac_yield()
|
mac_yield()
|
||||||
};
|
};
|
||||||
@@ -227,11 +228,11 @@ typedef Struct_(Binds_CubeTri) {
|
|||||||
};
|
};
|
||||||
internal MipsAtom_(rbind_cube_tri) {
|
internal MipsAtom_(rbind_cube_tri) {
|
||||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||||
load_word(R_PrimCur, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
|
load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
|
||||||
load_word(R_FaceCur, R_TapePtr, O_(Binds_CubeTri,FaceCursor)),
|
load_word(R_FaceCursor, R_TapePtr, O_(Binds_CubeTri,FaceCursor)),
|
||||||
load_word(R_VertBase, R_TapePtr, O_(Binds_CubeTri,VertBase)),
|
load_word(R_VertBase, R_TapePtr, O_(Binds_CubeTri,VertBase)),
|
||||||
load_word(R_OtBase, R_TapePtr, O_(Binds_CubeTri,OtBase)),
|
load_word(R_OtBase, R_TapePtr, O_(Binds_CubeTri,OtBase)),
|
||||||
add_ui_1( R_TapePtr, S_(Binds_CubeTri)),
|
add_ui_1( R_TapePtr, S_(Binds_CubeTri)),
|
||||||
// Note(Ed): This entire thing is argument shuffle?
|
// Note(Ed): This entire thing is argument shuffle?
|
||||||
// TODO(Ed): Eliminate
|
// TODO(Ed): Eliminate
|
||||||
mac_yield()
|
mac_yield()
|
||||||
@@ -275,10 +276,10 @@ internal MipsAtom_(rbind_cube_tri) {
|
|||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
internal MipsAtom_(cube_tri) {
|
internal MipsAtom_(cube_tri) {
|
||||||
/* ── 1. Load 4 face indices from R_FaceCur ──────────────────────────── */
|
/* ── 1. Load 4 face indices from R_FaceCur ──────────────────────────── */
|
||||||
load_half_u(R_T0, R_FaceCur, 0), /* T0 = face->x (vertex 0 index) */
|
load_half_u(R_T0, R_FaceCursor, 0), /* T0 = face->x (vertex 0 index) */
|
||||||
load_half_u(R_T1, R_FaceCur, 2), /* T1 = face->y (vertex 1 index) */
|
load_half_u(R_T1, R_FaceCursor, 2), /* T1 = face->y (vertex 1 index) */
|
||||||
load_half_u(R_T2, R_FaceCur, 4), /* T2 = face->z (vertex 2 index) */
|
load_half_u(R_T2, R_FaceCursor, 4), /* T2 = face->z (vertex 2 index) */
|
||||||
load_half_u(R_T3, R_FaceCur, 6), /* T3 = face->w (vertex 3 index) */
|
load_half_u(R_T3, R_FaceCursor, 6), /* T3 = face->w (vertex 3 index) */
|
||||||
|
|
||||||
/* ── 2. Load V0, V1, V2 into GTE ────────────────────────────────────── */
|
/* ── 2. Load V0, V1, V2 into GTE ────────────────────────────────────── */
|
||||||
/* V0 = verts[face->x] */
|
/* V0 = verts[face->x] */
|
||||||
@@ -311,32 +312,32 @@ internal MipsAtom_(cube_tri) {
|
|||||||
nop, /* BD slot */
|
nop, /* BD slot */
|
||||||
|
|
||||||
/* ── 6. Store p0,p1,p2 to primitive buffer (BEFORE RTPS overwrites) ─── */
|
/* ── 6. Store p0,p1,p2 to primitive buffer (BEFORE RTPS overwrites) ─── */
|
||||||
store_word(R_0, R_PrimCur, 0),
|
store_word(R_0, R_PrimCursor, 0),
|
||||||
|
|
||||||
/* Word 1: c0 (BGR) + code = 0x38FF00FF (magenta, opcode 0x38) */
|
/* Word 1: c0 (BGR) + code = 0x38FF00FF (magenta, opcode 0x38) */
|
||||||
load_ui(R_AT, 0x38FF), or_i(R_AT, R_AT, 0x00FF),
|
load_ui(R_AT, 0x38FF), or_i(R_AT, R_AT, 0x00FF),
|
||||||
store_word(R_AT, R_PrimCur, 4),
|
store_word(R_AT, R_PrimCursor, 4),
|
||||||
|
|
||||||
/* Word 2: p0 = SXY0 (stored BEFORE RTPS overwrites it) */
|
/* Word 2: p0 = SXY0 (stored BEFORE RTPS overwrites it) */
|
||||||
gte_sw(C2_SXY0, R_PrimCur, 8),
|
gte_sw(C2_SXY0, R_PrimCursor, 8),
|
||||||
|
|
||||||
/* Word 3: c1 (BGR) + pad = 0x0000FFFF (yellow) */
|
/* Word 3: c1 (BGR) + pad = 0x0000FFFF (yellow) */
|
||||||
load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFFFF),
|
load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFFFF),
|
||||||
store_word(R_AT, R_PrimCur, 12),
|
store_word(R_AT, R_PrimCursor, 12),
|
||||||
|
|
||||||
/* Word 4: p1 = SXY1 */
|
/* Word 4: p1 = SXY1 */
|
||||||
gte_sw(C2_SXY1, R_PrimCur, 16),
|
gte_sw(C2_SXY1, R_PrimCursor, 16),
|
||||||
|
|
||||||
/* Word 5: c2 (BGR) + pad = 0x00FFFF00 (cyan) */
|
/* Word 5: c2 (BGR) + pad = 0x00FFFF00 (cyan) */
|
||||||
load_ui(R_AT, 0x00FF), or_i(R_AT, R_AT, 0xFF00),
|
load_ui(R_AT, 0x00FF), or_i(R_AT, R_AT, 0xFF00),
|
||||||
store_word(R_AT, R_PrimCur, 20),
|
store_word(R_AT, R_PrimCursor, 20),
|
||||||
|
|
||||||
/* Word 6: p2 = SXY2 */
|
/* Word 6: p2 = SXY2 */
|
||||||
gte_sw(C2_SXY2, R_PrimCur, 24),
|
gte_sw(C2_SXY2, R_PrimCursor, 24),
|
||||||
|
|
||||||
/* Word 7: c3 (BGR) + pad = 0x0000FF00 (green) */
|
/* Word 7: c3 (BGR) + pad = 0x0000FF00 (green) */
|
||||||
load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFF00),
|
load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFF00),
|
||||||
store_word(R_AT, R_PrimCur, 28),
|
store_word(R_AT, R_PrimCursor, 28),
|
||||||
|
|
||||||
/* ── 7. Load V3 = verts[face->w] into V0 ─────────────────────────────── */
|
/* ── 7. Load V3 = verts[face->w] into V0 ─────────────────────────────── */
|
||||||
shift_ll(R_AT, R_T3, 3), add_u(R_AT, R_AT, R_VertBase),
|
shift_ll(R_AT, R_T3, 3), add_u(R_AT, R_AT, R_VertBase),
|
||||||
@@ -347,7 +348,7 @@ internal MipsAtom_(cube_tri) {
|
|||||||
nop, nop, gte_cmdw_rtps,
|
nop, nop, gte_cmdw_rtps,
|
||||||
|
|
||||||
/* Word 8: p3 = SXY0 (written AFTER RTPS with V3's screen coords) */
|
/* Word 8: p3 = SXY0 (written AFTER RTPS with V3's screen coords) */
|
||||||
gte_sw(C2_SXY0, R_PrimCur, 32),
|
gte_sw(C2_SXY0, R_PrimCursor, 32),
|
||||||
|
|
||||||
/* ── 9. AVSZ4 — average Z from SZ0/SZ1/SZ2/SZ3 ────────────── */
|
/* ── 9. AVSZ4 — average Z from SZ0/SZ1/SZ2/SZ3 ────────────── */
|
||||||
nop, nop, gte_cmdw_avsz4,
|
nop, nop, gte_cmdw_avsz4,
|
||||||
@@ -364,8 +365,8 @@ internal MipsAtom_(cube_tri) {
|
|||||||
mac_insert_ot_tag(R_T1, 0x0800), /* 0x0800 = 8 << 8 = length 8 in tag */
|
mac_insert_ot_tag(R_T1, 0x0800), /* 0x0800 = 8 << 8 = length 8 in tag */
|
||||||
|
|
||||||
/* ── 12. Advance cursors & yield ─────────────────────────────────────── */
|
/* ── 12. Advance cursors & yield ─────────────────────────────────────── */
|
||||||
add_ui(R_PrimCur, R_PrimCur, 36), /* 9 words × 4 bytes */
|
add_ui(R_PrimCursor, R_PrimCursor, 36), /* 9 words × 4 bytes */
|
||||||
add_ui(R_FaceCur, R_FaceCur, 8), /* 4 × S2 = 8 bytes */
|
add_ui(R_FaceCursor, R_FaceCursor, 8), /* 4 × S2 = 8 bytes */
|
||||||
mac_yield()
|
mac_yield()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -379,11 +380,11 @@ typedef Struct_(Binds_FloorTri) {
|
|||||||
|
|
||||||
internal MipsAtom_(rbind_floor_tri) {
|
internal MipsAtom_(rbind_floor_tri) {
|
||||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||||
load_word(R_PrimCur, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
|
load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
|
||||||
load_word(R_FaceCur, R_TapePtr, O_(Binds_FloorTri,FaceCursor)),
|
load_word(R_FaceCursor, R_TapePtr, O_(Binds_FloorTri,FaceCursor)),
|
||||||
load_word(R_VertBase, R_TapePtr, O_(Binds_FloorTri,VertBase)),
|
load_word(R_VertBase, R_TapePtr, O_(Binds_FloorTri,VertBase)),
|
||||||
load_word(R_OtBase, R_TapePtr, O_(Binds_FloorTri,OtBase)),
|
load_word(R_OtBase, R_TapePtr, O_(Binds_FloorTri,OtBase)),
|
||||||
add_ui_1( R_TapePtr, S_(Binds_FloorTri)),
|
add_ui_1( R_TapePtr, S_(Binds_FloorTri)),
|
||||||
// Note(Ed): This entire thing is argument shuffle?
|
// Note(Ed): This entire thing is argument shuffle?
|
||||||
// TODO(Ed): Eliminate
|
// TODO(Ed): Eliminate
|
||||||
mac_yield()
|
mac_yield()
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
// Auto-generated by gen_atom_offsets.lua — DO NOT EDIT
|
// Auto-generated by tape_atom_offset_gen.meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\gte_hello\hello_gte_tape.c
|
// Source: C:\projects\Pikuma\ps1\code\gte_hello\hello_gte_tape.c
|
||||||
#ifndef HELLO_GTE_TAPE_OFFSETS_H
|
#pragma once
|
||||||
#define HELLO_GTE_TAPE_OFFSETS_H
|
|
||||||
|
|
||||||
#pragma region hello_gte_tape
|
#pragma region hello_gte_tape
|
||||||
|
|
||||||
// Override the placeholder atom_offset() to dispatch via token pasting.
|
// Dispatch macro: token-pastes <tag>_<target> to the enum name
|
||||||
#undef atom_offset
|
#undef atom_offset
|
||||||
#define atom_offset(name) atom_offset_##name
|
#define atom_offset(tag, name) atom_offset_##tag##_##name
|
||||||
|
|
||||||
// --- atom: floor_tri (49 words) ---
|
// --- atom: floor_tri (32 words) ---
|
||||||
|
|
||||||
#define atom_offset_floor_tri_exit (17)
|
#define _atom_offset_culling_floor_tri_exit 17
|
||||||
|
#define _atom_offset_bounds_chk_floor_tri_exit 3
|
||||||
|
|
||||||
|
enum {
|
||||||
|
atom_offset_culling_floor_tri_exit = _atom_offset_culling_floor_tri_exit,
|
||||||
|
atom_offset_bounds_chk_floor_tri_exit = _atom_offset_bounds_chk_floor_tri_exit,
|
||||||
|
};
|
||||||
|
|
||||||
#pragma endregion hello_gte_tape
|
#pragma endregion hello_gte_tape
|
||||||
|
|
||||||
#endif // HELLO_GTE_TAPE_OFFSETS_H
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
|
|||||||
U4 prim_cursor = prim_base + pa->used;
|
U4 prim_cursor = prim_base + pa->used;
|
||||||
|
|
||||||
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
||||||
TapeBuilder tb = tb_make(&tape_arena); tb_scope(& tb) {
|
TapeBuilder tb = tb_make_old(&tape_arena); tb_scope(& tb) {
|
||||||
tb_emit(& tb, code_rbind_cube_tri);
|
tb_emit(& tb, code_rbind_cube_tri);
|
||||||
tb_data(& tb, prim_cursor);
|
tb_data(& tb, prim_cursor);
|
||||||
tb_data(& tb, u4_(static_mem.cube.faces));
|
tb_data(& tb, u4_(static_mem.cube.faces));
|
||||||
@@ -334,12 +334,11 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
|
|||||||
U4 prim_cursor = prim_base + pa->used;
|
U4 prim_cursor = prim_base + pa->used;
|
||||||
|
|
||||||
// Prepare the tape.
|
// Prepare the tape.
|
||||||
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
LP_ U4 mem_temp_tape[512];
|
||||||
TapeBuilder tb = tb_make(&tape_arena); tb_scope(& tb) {
|
TapeBuilder tb = tb_make(slice_ut_arr(mem_temp_tape)); tb_scope(& tb) {
|
||||||
// Push "Protocol" to tape
|
// Push "Protocol" to tape
|
||||||
tb_emit(& tb, code_rbind_floor_tri);
|
tb_emit(& tb, code_rbind_floor_tri);
|
||||||
// Note(Ed): This is technically argument shuffle and would be better if we did a single reference at most to a global batch context.
|
// TODO(Ed): Just use a single context struct ref
|
||||||
// Note(Ed): We can technically allocate a single ptr with the global offset to the working context instead of utilizing the tape for this stack of refs.
|
|
||||||
tb_data(& tb, prim_cursor);
|
tb_data(& tb, prim_cursor);
|
||||||
tb_data(& tb, u4_(static_mem.floor.faces));
|
tb_data(& tb, u4_(static_mem.floor.faces));
|
||||||
tb_data(& tb, u4_(static_mem.floor.verts));
|
tb_data(& tb, u4_(static_mem.floor.verts));
|
||||||
@@ -367,7 +366,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
|
|||||||
if (0)
|
if (0)
|
||||||
{
|
{
|
||||||
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
||||||
TapeBuilder tb = tb_make(& tape_arena); tb_scope(& tb) {
|
TapeBuilder tb = tb_make_old(& tape_arena); tb_scope(& tb) {
|
||||||
// Skip set_gte_world atom for diagnostics to isolate the triangle loop
|
// Skip set_gte_world atom for diagnostics to isolate the triangle loop
|
||||||
for (U4 i = 0; i < Floor_num_faces; i++) {
|
for (U4 i = 0; i < Floor_num_faces; i++) {
|
||||||
// =======================================================
|
// =======================================================
|
||||||
|
|||||||
@@ -5,36 +5,44 @@
|
|||||||
# include "gen/hello_gte_tape.offsets.h"
|
# include "gen/hello_gte_tape.offsets.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma region MACs
|
#pragma region MACs (Mips Atom components)
|
||||||
|
|
||||||
/* Words: 3; High: 0x20/B, Low: G/R */
|
/* Words: 3; High: 0x20/B, Low: G/R */
|
||||||
#define mac_format_f3_color(color_hi, color_lo) \
|
#define mac_format_f3_color(color_hi, color_lo) \
|
||||||
load_ui(R_AT, color_hi), or_i(R_AT, R_AT, color_lo) \
|
load_ui(R_AT, color_hi), or_i(R_AT, R_AT, color_lo) \
|
||||||
, store_word(R_AT, R_PrimCur, O_(Poly_F3,color)) \
|
, store_word(R_AT, R_PrimCursor, O_(Poly_F3,color)) \
|
||||||
|
|
||||||
/* Words: 3 */
|
/* Words: 3 */
|
||||||
#define mac_gte_store_f3() \
|
#define mac_gte_store_f3() \
|
||||||
gte_sw(C2_SXY0, R_PrimCur, O_(Poly_F3,p0)) \
|
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_F3,p0)) \
|
||||||
, gte_sw(C2_SXY1, R_PrimCur, O_(Poly_F3,p1)) \
|
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)) \
|
||||||
, gte_sw(C2_SXY2, R_PrimCur, O_(Poly_F3,p2))
|
, gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2))
|
||||||
|
|
||||||
#pragma endregion MACs
|
#pragma endregion MACs
|
||||||
|
|
||||||
#pragma region Baked Atoms
|
#pragma region Baked Atoms
|
||||||
|
|
||||||
internal MipsAtom_(floor_tri) {
|
internal MipsCode code_floor_tri [] __attribute__((aligned (4))) = {
|
||||||
// T0-T2 allocated
|
// T0-T2 allocated
|
||||||
mac_load_tri_indices(R_T0, R_T1, R_T2),
|
mac_load_tri_indices(R_T0, R_T1, R_T2),
|
||||||
|
// load_half_u(R_T0, R_FaceCur, 0 * S_(S2))
|
||||||
|
// , load_half_u(R_T1, R_FaceCur, 1 * S_(S2))
|
||||||
|
// , load_half_u(R_T2, R_FaceCur, 2 * S_(S2))
|
||||||
|
// ,
|
||||||
mac_load_tri_verts( R_T0, R_T1, R_T2),
|
mac_load_tri_verts( R_T0, R_T1, R_T2),
|
||||||
|
// shift_ll(R_AT, R_T0, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY0), gte_mt(R_V1, C2_VZ0)
|
||||||
|
// , shift_ll(R_AT, R_T1, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY1), gte_mt(R_V1, C2_VZ1)
|
||||||
|
// , shift_ll(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY2), gte_mt(R_V1, C2_VZ2)
|
||||||
|
// ,
|
||||||
|
|
||||||
/* 3. Execute Math */
|
/* 3. Execute Math */
|
||||||
nop, nop, gte_cmdw_rotate_translate_perspective_triple,
|
nop, nop, gte_cmdw_rotate_translate_perspective_triple,
|
||||||
nop, nop, gte_cmdw_nclip,
|
nop, nop, gte_cmdw_nclip,
|
||||||
nop, nop,
|
nop, nop,
|
||||||
|
|
||||||
/* 4. Culling (Branch forward 29 instructions if Backface) */
|
/* 4. Culling (Branch forward if Backface) */
|
||||||
gte_mf(R_T0, C2_MAC0),
|
gte_mf(R_T0, C2_MAC0),
|
||||||
nop, branch_le_zero(R_T0, atom_offset(floor_tri_exit)),
|
nop, branch_le_zero(R_T0, atom_offset(culling, floor_tri_exit)),
|
||||||
nop,
|
nop,
|
||||||
|
|
||||||
/* 5. Format Primitive */
|
/* 5. Format Primitive */
|
||||||
@@ -45,19 +53,19 @@ internal MipsAtom_(floor_tri) {
|
|||||||
nop, nop, gte_avg_sort_z3,
|
nop, nop, gte_avg_sort_z3,
|
||||||
nop, nop, gte_mf(R_T1, C2_OTZ),
|
nop, nop, gte_mf(R_T1, C2_OTZ),
|
||||||
|
|
||||||
/* 7. Bounds Check OTZ < 2048 (Branch forward 13 instructions to skip insertion) */
|
/* 7. Bounds Check OTZ < 2048 (Branch forward to skip insertion) */
|
||||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||||
slt_u( R_AT, R_T1, R_AT),
|
slt_u( R_AT, R_T1, R_AT),
|
||||||
branch_equal(R_AT, R_0, 13),
|
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_tri_exit)),
|
||||||
nop,
|
nop,
|
||||||
|
|
||||||
/* 8. Insert into Ordering Table Linked List */
|
/* 8. Insert into Ordering Table Linked List */
|
||||||
mac_insert_ot_tag(R_T1, 0x0400),
|
mac_insert_ot_tag(R_T1, 0x0400),
|
||||||
add_ui(R_PrimCur, R_PrimCur, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
add_ui(R_PrimCursor, R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
||||||
|
|
||||||
/* 9. Advance Input Cursor & Yield (Both branch targets land here) */
|
/* 9. Advance Input Cursor & Yield (Both branch targets land here) */
|
||||||
atom_label(floor_tri_exit)
|
atom_label(floor_tri_exit)
|
||||||
add_ui(R_FaceCur, R_FaceCur, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */
|
add_ui(R_FaceCursor, R_FaceCursor, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */
|
||||||
mac_yield()
|
mac_yield()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
||||||
|
|
||||||
WORD_COUNT(nop, 1)
|
WORD_COUNT(nop, 1)
|
||||||
|
WORD_COUNT(jump_reg, 1)
|
||||||
|
WORD_COUNT(jump_link, 1)
|
||||||
WORD_COUNT(branch_le_zero, 1)
|
WORD_COUNT(branch_le_zero, 1)
|
||||||
WORD_COUNT(branch_equal, 1)
|
WORD_COUNT(branch_equal, 1)
|
||||||
WORD_COUNT(add_ui, 1)
|
WORD_COUNT(add_ui, 1)
|
||||||
@@ -18,6 +20,12 @@ WORD_COUNT(load_ui, 1)
|
|||||||
WORD_COUNT(load_word, 1)
|
WORD_COUNT(load_word, 1)
|
||||||
WORD_COUNT(load_half_u, 1)
|
WORD_COUNT(load_half_u, 1)
|
||||||
WORD_COUNT(store_word, 1)
|
WORD_COUNT(store_word, 1)
|
||||||
|
WORD_COUNT(add_ui_1, 1)
|
||||||
|
WORD_COUNT(add_u, 1)
|
||||||
|
WORD_COUNT(or_i, 1)
|
||||||
|
WORD_COUNT(or_u, 1)
|
||||||
|
WORD_COUNT(shift_ll, 1)
|
||||||
|
WORD_COUNT(shift_lr, 1)
|
||||||
WORD_COUNT(gte_mf, 1)
|
WORD_COUNT(gte_mf, 1)
|
||||||
WORD_COUNT(gte_mt, 1)
|
WORD_COUNT(gte_mt, 1)
|
||||||
WORD_COUNT(gte_ct, 1)
|
WORD_COUNT(gte_ct, 1)
|
||||||
|
|||||||
+95
-95
@@ -7,123 +7,123 @@ Converts a raw binary file to PlayStation 1 (PS-X) executable format.
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
function file_size(filename)
|
function file_size(filename)
|
||||||
local file = io.open(filename, "rb")
|
local file = io.open(filename, "rb")
|
||||||
if not file then return nil end
|
if not file then return nil end
|
||||||
local size = file:seek("end")
|
local size = file:seek("end")
|
||||||
file:close()
|
file:close()
|
||||||
return size
|
return size
|
||||||
end
|
end
|
||||||
|
|
||||||
function main(args)
|
function main(args)
|
||||||
if #args ~= 2 then
|
if #args ~= 2 then
|
||||||
io.stderr:write(usage)
|
io.stderr:write(usage)
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print(string.format("Input file: %s", args[1]))
|
-- print(string.format("Input file: %s", args[1]))
|
||||||
-- print(string.format("Output file: %s", args[2]))
|
-- print(string.format("Output file: %s", args[2]))
|
||||||
|
|
||||||
-- PS1 executables have a maximum size limit of 2MB
|
-- PS1 executables have a maximum size limit of 2MB
|
||||||
local max_size = 0x200000
|
local max_size = 0x200000
|
||||||
-- print(string.format("\nChecking input file size (max: %d bytes)...", max_size))
|
-- print(string.format("\nChecking input file size (max: %d bytes)...", max_size))
|
||||||
|
|
||||||
local infile_size = file_size(args[1])
|
local infile_size = file_size(args[1])
|
||||||
if not infile_size then
|
if not infile_size then
|
||||||
io.stderr:write("Error: Cannot open input file " .. args[1] .. "\n")
|
io.stderr:write("Error: Cannot open input file " .. args[1] .. "\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print(string.format("Input file size: %d bytes", infile_size))
|
-- print(string.format("Input file size: %d bytes", infile_size))
|
||||||
|
|
||||||
if infile_size > max_size then
|
if infile_size > max_size then
|
||||||
io.stderr:write(string.format("Error: Input file %s longer than %d bytes\n", args[1], max_size))
|
io.stderr:write(string.format("Error: Input file %s longer than %d bytes\n", args[1], max_size))
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print("\nOpening files...")
|
-- print("\nOpening files...")
|
||||||
local ofile = io.open(args[2], "wb")
|
local ofile = io.open(args[2], "wb")
|
||||||
if not ofile then
|
if not ofile then
|
||||||
io.stderr:write("Error: Cannot open output file " .. args[2] .. "\n")
|
io.stderr:write("Error: Cannot open output file " .. args[2] .. "\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ifile = io.open(args[1], "rb")
|
local ifile = io.open(args[1], "rb")
|
||||||
if not ifile then
|
if not ifile then
|
||||||
io.stderr:write("Error: Cannot open input file " .. args[1] .. "\n")
|
io.stderr:write("Error: Cannot open input file " .. args[1] .. "\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PS1 executables start with "PS-X EXE" magic string
|
-- PS1 executables start with "PS-X EXE" magic string
|
||||||
-- print("Writing PS-X executable header...")
|
-- print("Writing PS-X executable header...")
|
||||||
ofile:write("PS-X EXE")
|
ofile:write("PS-X EXE")
|
||||||
|
|
||||||
-- Write entry point address (where the PS1 will jump to start execution)
|
-- Write entry point address (where the PS1 will jump to start execution)
|
||||||
-- 0x80010000 is a standard entry point in PS1 RAM
|
-- 0x80010000 is a standard entry point in PS1 RAM
|
||||||
ofile:seek("set", 0x10)
|
ofile:seek("set", 0x10)
|
||||||
ofile:write(string.pack("<I4", 0x80010000))
|
ofile:write(string.pack("<I4", 0x80010000))
|
||||||
-- print(" Entry point: 0x80010000")
|
-- print(" Entry point: 0x80010000")
|
||||||
|
|
||||||
-- Initial GP/R28 register value (Global Pointer for data addressing)
|
-- Initial GP/R28 register value (Global Pointer for data addressing)
|
||||||
-- 0xFFFFFFFF means it will be set by crt0.S startup code
|
-- 0xFFFFFFFF means it will be set by crt0.S startup code
|
||||||
ofile:write(string.pack("<I4", 0xFFFFFFFF))
|
ofile:write(string.pack("<I4", 0xFFFFFFFF))
|
||||||
|
|
||||||
-- Destination address in RAM where the executable will be loaded
|
-- Destination address in RAM where the executable will be loaded
|
||||||
ofile:write(string.pack("<I4", 0x80010000))
|
ofile:write(string.pack("<I4", 0x80010000))
|
||||||
-- print(" Load address: 0x80010000")
|
-- print(" Load address: 0x80010000")
|
||||||
|
|
||||||
-- Initial stack pointer (SP/R29) and frame pointer (FP/R30)
|
-- Initial stack pointer (SP/R29) and frame pointer (FP/R30)
|
||||||
-- 0x801FFF00 points near the top of the 2MB main RAM
|
-- 0x801FFF00 points near the top of the 2MB main RAM
|
||||||
ofile:seek("set", 0x30)
|
ofile:seek("set", 0x30)
|
||||||
ofile:write(string.pack("<I4", 0x801FFF00))
|
ofile:write(string.pack("<I4", 0x801FFF00))
|
||||||
-- print(" Stack pointer: 0x801FFF00")
|
-- print(" Stack pointer: 0x801FFF00")
|
||||||
|
|
||||||
-- PS1 executables have an 0x800 (2048) byte header
|
-- PS1 executables have an 0x800 (2048) byte header
|
||||||
-- Zero fill the rest of the header
|
-- Zero fill the rest of the header
|
||||||
ofile:seek("set", 0x800)
|
ofile:seek("set", 0x800)
|
||||||
-- print(" Header padding complete (2048 bytes)")
|
-- print(" Header padding complete (2048 bytes)")
|
||||||
|
|
||||||
-- Copy the actual program binary data after the header
|
-- Copy the actual program binary data after the header
|
||||||
-- print("\nCopying program data...")
|
-- print("\nCopying program data...")
|
||||||
local buffer_size = 0x2000 -- 8KB chunks for efficient copying
|
local buffer_size = 0x2000 -- 8KB chunks for efficient copying
|
||||||
local bytes_copied = 0
|
local bytes_copied = 0
|
||||||
|
|
||||||
for i = 0, math.ceil(infile_size / buffer_size) - 1 do
|
for i = 0, math.ceil(infile_size / buffer_size) - 1 do
|
||||||
local buffer = ifile:read(buffer_size)
|
local buffer = ifile:read(buffer_size)
|
||||||
if buffer then
|
if buffer then
|
||||||
ofile:write(buffer)
|
ofile:write(buffer)
|
||||||
bytes_copied = bytes_copied + #buffer
|
bytes_copied = bytes_copied + #buffer
|
||||||
-- Show progress every 64KB
|
-- Show progress every 64KB
|
||||||
if bytes_copied % 0x10000 == 0 or bytes_copied == infile_size then
|
if bytes_copied % 0x10000 == 0 or bytes_copied == infile_size then
|
||||||
print(string.format(" Copied %d/%d bytes (%.1f%%)",
|
print(string.format(" Copied %d/%d bytes (%.1f%%)",
|
||||||
bytes_copied, infile_size, (bytes_copied / infile_size) * 100))
|
bytes_copied, infile_size, (bytes_copied / infile_size) * 100))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PS1 executables must be padded to 0x800 (2048) byte boundaries
|
-- PS1 executables must be padded to 0x800 (2048) byte boundaries
|
||||||
print("\nAligning to 2048-byte boundary...")
|
print("\nAligning to 2048-byte boundary...")
|
||||||
local exe_size = ofile:seek()
|
local exe_size = ofile:seek()
|
||||||
if exe_size % 0x800 ~= 0 then
|
if exe_size % 0x800 ~= 0 then
|
||||||
local padding = 0x800 - (exe_size % 0x800)
|
local padding = 0x800 - (exe_size % 0x800)
|
||||||
exe_size = exe_size + padding
|
exe_size = exe_size + padding
|
||||||
ofile:seek("set", exe_size - 1)
|
ofile:seek("set", exe_size - 1)
|
||||||
ofile:write(string.pack("B", 0))
|
ofile:write(string.pack("B", 0))
|
||||||
print(string.format(" Added %d bytes of padding", padding))
|
print(string.format(" Added %d bytes of padding", padding))
|
||||||
else
|
else
|
||||||
print(" No padding needed")
|
print(" No padding needed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write the size of the executable (excluding the 0x800 byte header)
|
-- Write the size of the executable (excluding the 0x800 byte header)
|
||||||
-- This goes at offset 0x1C in the header
|
-- This goes at offset 0x1C in the header
|
||||||
ofile:seek("set", 0x1C)
|
ofile:seek("set", 0x1C)
|
||||||
ofile:write(string.pack("<I4", exe_size - 0x800))
|
ofile:write(string.pack("<I4", exe_size - 0x800))
|
||||||
-- print(string.format("\nProgram size field set to: %d bytes", exe_size - 0x800))
|
-- print(string.format("\nProgram size field set to: %d bytes", exe_size - 0x800))
|
||||||
|
|
||||||
ifile:close()
|
ifile:close()
|
||||||
ofile:close()
|
ofile:close()
|
||||||
|
|
||||||
-- print(string.format("\nSuccess! PS1 executable created: %s", args[2]))
|
-- print(string.format("\nSuccess! PS1 executable created: %s", args[2]))
|
||||||
print(string.format("Total file size: %d bytes\n", exe_size))
|
print(string.format("Total file size: %d bytes\n", exe_size))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run main with command line arguments
|
-- Run main with command line arguments
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ function generate-TapeAtomOffsets {param(
|
|||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$metadata)
|
[string]$metadata)
|
||||||
|
|
||||||
$gen_atom_offsets_script = join-path $path_scripts 'tape_attom.offset_gen.meta.lua'
|
$gen_atom_offsets_script = join-path $path_scripts 'tape_atom.offset_gen.meta.lua'
|
||||||
|
|
||||||
$any_stale = $false
|
$any_stale = $false
|
||||||
foreach ($src in $sources) {
|
foreach ($src in $sources) {
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
#!/usr/bin/env lua
|
||||||
|
-- tape_atom_offset_gen.lua
|
||||||
|
--
|
||||||
|
-- Finds every `MipsAtom_(name) { ... }` declaration in the given sources,
|
||||||
|
-- counts the words in each body using the WORD_COUNT manifest, computes
|
||||||
|
-- branch offsets for atom_label(name) / atom_offset(tag, name) markers,
|
||||||
|
-- and writes one header per source into <source_dir>/gen/<basename>.offsets.h
|
||||||
|
--
|
||||||
|
-- Generated header layout (per source):
|
||||||
|
-- #pragma region <basename>
|
||||||
|
-- #undef atom_offset
|
||||||
|
-- #define atom_offset(tag, name) atom_offset_##tag##_##name
|
||||||
|
-- // --- atom: <name> (<n> words) ---
|
||||||
|
-- #define atom_offset_<tag>_<target> (N) // preprocessor form
|
||||||
|
-- #undef atom_offset_<tag>_<target> // (so enum can reuse)
|
||||||
|
-- enum {
|
||||||
|
-- atom_offset_<tag>_<target> = N, // C enum form
|
||||||
|
-- };
|
||||||
|
-- #define atom_offset_<tag>_<target> (N) // re-define for preprocessor
|
||||||
|
-- #pragma endregion <basename>
|
||||||
|
--
|
||||||
|
-- Usage:
|
||||||
|
-- lua gen_atom_offsets.lua <metadata.h> <source1> [source2 ...]
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Character classification
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function is_space(c) return c == " " or c == "\t" or c == "\n" or c == "\r" or c == "\v" or c == "\f" end
|
||||||
|
local function is_alpha(c)
|
||||||
|
if not c or #c == 0 then return false end
|
||||||
|
if c >= "a" and c <= "z" then return true end
|
||||||
|
if c >= "A" and c <= "Z" then return true end
|
||||||
|
return c == "_"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_digit(c) return c and c >= "0" and c <= "9" end
|
||||||
|
local function is_alnum(c) return is_alpha(c) or is_digit(c) end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- I/O
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function read_file(path)
|
||||||
|
local f = io.open(path, "r")
|
||||||
|
if not f then error("Cannot open " .. path) end
|
||||||
|
local content = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
return content
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write_file(path, content)
|
||||||
|
local f = io.open(path, "w")
|
||||||
|
if not f then error("Cannot write " .. path) end
|
||||||
|
f:write(content)
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PowerShell aliases `mkdir` to New-Item, which treats `-p` as a path, so guard the call.
|
||||||
|
local function ensure_dir(path)
|
||||||
|
local is_win = package.config:sub(1, 1) == "\\"
|
||||||
|
os.execute(is_win and ('if not exist "' .. path .. '" mkdir "' .. path .. '"') or ('mkdir -p "' .. path .. '" 2>/dev/null'))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- String primitives
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function trim(s)
|
||||||
|
local a = 1; while a <= #s and is_space(s:sub(a, a)) do a = a + 1 end
|
||||||
|
local b = #s; while b >= a and is_space(s:sub(b, b)) do b = b - 1 end
|
||||||
|
return s:sub(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function starts_with(s, prefix)
|
||||||
|
if #s < #prefix then return false end
|
||||||
|
for i = 1, #prefix do
|
||||||
|
if s:sub(i, i) ~= prefix:sub(i, i) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ends_with(s, suffix)
|
||||||
|
if #s < #suffix then return false end
|
||||||
|
local off = #s - #suffix
|
||||||
|
for i = 1, #suffix do
|
||||||
|
if s:sub(off + i, off + i) ~= suffix:sub(i, i) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_byte(haystack, target, start)
|
||||||
|
for i = start or 1, #haystack do
|
||||||
|
if haystack:sub(i, i) == target then return i end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dirname(path)
|
||||||
|
local last_sep = 0
|
||||||
|
for i = 1, #path do
|
||||||
|
local c = path:sub(i, i)
|
||||||
|
if c == "/" or c == "\\" then last_sep = i end
|
||||||
|
end
|
||||||
|
if last_sep == 0 then return "." end
|
||||||
|
return path:sub(1, last_sep - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function basename_no_ext(path)
|
||||||
|
local last_sep = 0
|
||||||
|
for i = 1, #path do
|
||||||
|
local c = path:sub(i, i)
|
||||||
|
if c == "/" or c == "\\" then last_sep = i end
|
||||||
|
end
|
||||||
|
local a = last_sep + 1
|
||||||
|
local last_dot = #path + 1
|
||||||
|
for i = #path, a, -1 do
|
||||||
|
if path:sub(i, i) == "." then last_dot = i; break end
|
||||||
|
end
|
||||||
|
return path:sub(a, last_dot - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function to_upper(s) return s:upper() end
|
||||||
|
local function to_alnum_underscore(s)
|
||||||
|
local out = ""
|
||||||
|
for i = 1, #s do
|
||||||
|
local c = s:sub(i, i)
|
||||||
|
if is_alnum(c) then out = out .. c
|
||||||
|
else out = out .. "_" end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
local function pad_right(s, w) return s .. string.rep(" ", w - #s) end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Lexer helpers
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- If position i starts a C string literal ("..."), char literal ('.'),
|
||||||
|
-- // line comment, or /* block comment, advance past it and return the
|
||||||
|
-- position just after the construct (or #s+1 if unterminated).
|
||||||
|
-- Otherwise return i unchanged.
|
||||||
|
local function skip_str_or_cmt(s, i)
|
||||||
|
local c = s:sub(i, i)
|
||||||
|
if c == '"' or c == "'" then
|
||||||
|
i = i + 1
|
||||||
|
while i <= #s do
|
||||||
|
if s:sub(i, i) == "\\" then i = i + 2
|
||||||
|
elseif s:sub(i, i) == c then return i + 1
|
||||||
|
else i = i + 1 end
|
||||||
|
end
|
||||||
|
return #s + 1
|
||||||
|
elseif c == "/" then
|
||||||
|
local nx = s:sub(i+1, i+1)
|
||||||
|
if nx == "/" then
|
||||||
|
while i <= #s and s:sub(i, i) ~= "\n" do i = i + 1 end
|
||||||
|
return i
|
||||||
|
elseif nx == "*" then
|
||||||
|
i = i + 2
|
||||||
|
while i <= #s - 1 do
|
||||||
|
if s:sub(i, i) == "*" and s:sub(i+1, i+1) == "/" then
|
||||||
|
return i + 2
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return #s + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
local function skip_ws_and_cmt(s, i)
|
||||||
|
while i <= #s do
|
||||||
|
if is_space(s:sub(i, i)) then i = i + 1
|
||||||
|
else
|
||||||
|
local nx = skip_str_or_cmt(s, i)
|
||||||
|
if nx > i then i = nx else break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read_ident(source, i)
|
||||||
|
if not is_alpha(source:sub(i, i)) then return nil, i end
|
||||||
|
local a = i
|
||||||
|
i = i + 1
|
||||||
|
while i <= #source and is_alnum(source:sub(i, i)) do i = i + 1 end
|
||||||
|
return source:sub(a, i - 1), i
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read_balanced(s, open_char, close_char, i)
|
||||||
|
if s:sub(i, i) ~= open_char then return nil, i end
|
||||||
|
i = i + 1
|
||||||
|
local len = #s
|
||||||
|
local depth = 1
|
||||||
|
local a = i
|
||||||
|
while i <= len and depth > 0 do
|
||||||
|
local c = s:sub(i, i)
|
||||||
|
if c == open_char then
|
||||||
|
depth = depth + 1
|
||||||
|
i = i + 1
|
||||||
|
elseif c == close_char then
|
||||||
|
depth = depth - 1
|
||||||
|
if depth == 0 then break end
|
||||||
|
i = i + 1
|
||||||
|
else
|
||||||
|
local nx = skip_str_or_cmt(s, i)
|
||||||
|
if nx > i then i = nx else i = i + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return s:sub(a, i - 1), i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local read_parens = function(s, i) return read_balanced(s, "(", ")", i) end
|
||||||
|
local read_braces = function(s, i) return read_balanced(s, "{", "}", i) end
|
||||||
|
local read_brackets = function(s, i) return read_balanced(s, "[", "]", i) end
|
||||||
|
|
||||||
|
local function scan_to_char(s, target, start)
|
||||||
|
local i = start
|
||||||
|
while i <= #s do
|
||||||
|
local c = s:sub(i, i)
|
||||||
|
if c == target then return i end
|
||||||
|
if c == "(" then local _, a = read_balanced(s, "(", ")", i); i = a
|
||||||
|
elseif c == "{" then local _, a = read_balanced(s, "{", "}", i); i = a
|
||||||
|
elseif c == "[" then local _, a = read_balanced(s, "[", "]", i); i = a
|
||||||
|
else
|
||||||
|
local nx = skip_str_or_cmt(s, i)
|
||||||
|
if nx > i then i = nx else i = i + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Extract comma-separated identifier args from a parenthesized group
|
||||||
|
-- after a function-like macro call.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function extract_ident_args(token, after_ident)
|
||||||
|
local arg_start = skip_ws_and_cmt(token, after_ident)
|
||||||
|
if token:sub(arg_start, arg_start) ~= "(" then return {}, nil end
|
||||||
|
local inner, after_paren = read_parens(token, arg_start)
|
||||||
|
|
||||||
|
local args = {}
|
||||||
|
local n = 1
|
||||||
|
local len = #inner
|
||||||
|
while n <= len do
|
||||||
|
n = skip_ws_and_cmt(inner, n)
|
||||||
|
if n > len then break end
|
||||||
|
local ident, after = read_ident(inner, n)
|
||||||
|
if ident and ident ~= "" then
|
||||||
|
table.insert(args, ident)
|
||||||
|
n = after
|
||||||
|
else
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
n = skip_ws_and_cmt(inner, n)
|
||||||
|
if n <= len and inner:sub(n, n) == "," then n = n + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
return args, after_paren
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Load WORD_COUNT manifest
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function load_word_counts(metadata_path)
|
||||||
|
local counts = {}
|
||||||
|
local content = read_file(metadata_path)
|
||||||
|
local len = #content
|
||||||
|
local i = 1
|
||||||
|
local prefix = "WORD_COUNT("
|
||||||
|
while i <= len do
|
||||||
|
local nl = find_byte(content, "\n", i)
|
||||||
|
local line_end = nl or (len + 1)
|
||||||
|
local line = content:sub(i, line_end - 1)
|
||||||
|
local trimmed = trim(line)
|
||||||
|
if starts_with(trimmed, prefix) and ends_with(trimmed, ")") then
|
||||||
|
local inner = trimmed:sub(#prefix + 1, #trimmed - 1)
|
||||||
|
local comma = find_byte(inner, ",", 1)
|
||||||
|
if comma then
|
||||||
|
counts[trim(inner:sub(1, comma - 1))] = tonumber(trim(inner:sub(comma + 1)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = line_end + 1
|
||||||
|
end
|
||||||
|
return counts
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Count words for a single comma-separated token
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function word_count_of_token(token, wc)
|
||||||
|
local s = trim(token)
|
||||||
|
if s == "" then return 0 end
|
||||||
|
local name, after = read_ident(s, 1)
|
||||||
|
if not name then return 1 end
|
||||||
|
if wc[name] then return wc[name] end
|
||||||
|
local j = skip_ws_and_cmt(s, after)
|
||||||
|
if s:sub(j, j) == "(" then
|
||||||
|
io.stderr:write(" warning: unknown macro '" .. name .. "', assuming 1 word\n")
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Split brace-body into top-level comma-separated tokens
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function split_top_level_commas(body)
|
||||||
|
local tokens = {}
|
||||||
|
local i = 1
|
||||||
|
local token_start = 1
|
||||||
|
while i <= #body do
|
||||||
|
local c = body:sub(i, i)
|
||||||
|
if c == "(" then local _, a = read_parens(body, i); i = a
|
||||||
|
elseif c == "{" then local _, a = read_braces(body, i); i = a
|
||||||
|
elseif c == "[" then local _, a = read_brackets(body, i); i = a
|
||||||
|
elseif c == "," then
|
||||||
|
table.insert(tokens, body:sub(token_start, i - 1))
|
||||||
|
i = i + 1
|
||||||
|
token_start = i
|
||||||
|
else
|
||||||
|
local nx = skip_str_or_cmt(body, i)
|
||||||
|
if nx > i then i = nx else i = i + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local last = body:sub(token_start)
|
||||||
|
if trim(last) ~= "" then table.insert(tokens, last) end
|
||||||
|
return tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Scan token for atom_label/atom_offset markers, walking through
|
||||||
|
-- balanced groups transparently (so nested calls are found)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function scan_for_atom_markers(token, at_pos, labels, branches)
|
||||||
|
local i = 1
|
||||||
|
local len = #token
|
||||||
|
while i <= len do
|
||||||
|
i = skip_ws_and_cmt(token, i)
|
||||||
|
if i > len then break end
|
||||||
|
local c = token:sub(i, i)
|
||||||
|
if is_alpha(c) then
|
||||||
|
local ident, after = read_ident(token, i)
|
||||||
|
if ident == "atom_label" then
|
||||||
|
local args, after_paren = extract_ident_args(token, after)
|
||||||
|
if #args >= 1 then labels[args[1]] = at_pos end
|
||||||
|
if after_paren then i = after_paren else i = after end
|
||||||
|
elseif ident == "atom_offset" then
|
||||||
|
local args, after_paren = extract_ident_args(token, after)
|
||||||
|
if #args >= 2 then table.insert(branches, {pos = at_pos, target = args[2], tag = args[1]}) end
|
||||||
|
if after_paren then i = after_paren else i = after end
|
||||||
|
else
|
||||||
|
i = after
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local nx = skip_str_or_cmt(token, i)
|
||||||
|
if nx > i then i = nx else i = i + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Scan atom body, count words, find markers
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function scan_atom_body(body, word_counts)
|
||||||
|
local pos = 0
|
||||||
|
local labels = {}
|
||||||
|
local branches = {}
|
||||||
|
for _, tok in ipairs(split_top_level_commas(body)) do
|
||||||
|
local k = 1
|
||||||
|
local tlen = #tok
|
||||||
|
while k <= tlen and is_space(tok:sub(k, k)) do k = k + 1 end
|
||||||
|
local leading_ident = read_ident(tok, k)
|
||||||
|
if leading_ident == "atom_label" or leading_ident == "atom_offset" then
|
||||||
|
scan_for_atom_markers(tok, pos, labels, branches)
|
||||||
|
else
|
||||||
|
local words = word_count_of_token(tok, word_counts)
|
||||||
|
scan_for_atom_markers(tok, pos, labels, branches)
|
||||||
|
pos = pos + words
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return labels, branches, pos
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Find every MipsAtom_(name) { ... } in a source
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function skip_qualifiers(source, i)
|
||||||
|
local keywords = {
|
||||||
|
["static"] = true, ["const"] = true, ["volatile"] = true,
|
||||||
|
["extern"] = true, ["register"] = true, ["auto"] = true,
|
||||||
|
["inline"] = true, ["typedef"] = true,
|
||||||
|
["internal"]= true, ["LP_"] = true, ["global"] = true, ["gkknown"] = true
|
||||||
|
}
|
||||||
|
while true do
|
||||||
|
i = skip_ws_and_cmt(source, i)
|
||||||
|
local ident, after = read_ident(source, i)
|
||||||
|
if not ident then return i end
|
||||||
|
if keywords[ident] then i = after else return i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_atoms(source_text)
|
||||||
|
local atoms = {}
|
||||||
|
local len = #source_text
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
local function try_wrapped(after_pos)
|
||||||
|
local paren_pos = skip_ws_and_cmt(source_text, after_pos)
|
||||||
|
if source_text:sub(paren_pos, paren_pos) ~= "(" then return nil end
|
||||||
|
local inner, after_paren = read_parens(source_text, paren_pos)
|
||||||
|
local n = 1
|
||||||
|
while n <= #inner and is_space(inner:sub(n, n)) do n = n + 1 end
|
||||||
|
local ns = n
|
||||||
|
while n <= #inner and is_alnum(inner:sub(n, n)) do n = n + 1 end
|
||||||
|
local name = inner:sub(ns, n - 1)
|
||||||
|
if name == "" then return nil end
|
||||||
|
local brace_pos = scan_to_char(source_text, "{", after_paren)
|
||||||
|
if not brace_pos then return nil end
|
||||||
|
local body, after_brace = read_braces(source_text, brace_pos)
|
||||||
|
return {name = name, body = body, after_brace = after_brace}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function try_raw(after_pos)
|
||||||
|
local next_pos = skip_ws_and_cmt(source_text, after_pos)
|
||||||
|
local next_ident, next_after = read_ident(source_text, next_pos)
|
||||||
|
if not next_ident then return nil end
|
||||||
|
if not starts_with(next_ident, "code_") then return nil end
|
||||||
|
if #next_ident <= 5 then return nil end
|
||||||
|
local atom_name = next_ident:sub(6)
|
||||||
|
local brace_pos = scan_to_char(source_text, "{", next_after)
|
||||||
|
if not brace_pos then return nil end
|
||||||
|
local body, after_brace = read_braces(source_text, brace_pos)
|
||||||
|
return {name = atom_name, body = body, after_brace = after_brace}
|
||||||
|
end
|
||||||
|
|
||||||
|
while i <= len do
|
||||||
|
i = skip_ws_and_cmt(source_text, i); if i > len then break end
|
||||||
|
i = skip_qualifiers(source_text, i); if i > len then break end
|
||||||
|
local ident, after = read_ident(source_text, i)
|
||||||
|
if not ident then
|
||||||
|
i = i + 1
|
||||||
|
elseif ident == "MipsAtom_" then
|
||||||
|
local atom = try_wrapped(after)
|
||||||
|
if atom then
|
||||||
|
table.insert(atoms, {name = atom.name, body = atom.body})
|
||||||
|
i = atom.after_brace
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
elseif ident == "MipsCode" then
|
||||||
|
local atom = try_raw(after)
|
||||||
|
if atom then
|
||||||
|
table.insert(atoms, {name = atom.name, body = atom.body})
|
||||||
|
i = atom.after_brace
|
||||||
|
else
|
||||||
|
i = after
|
||||||
|
end
|
||||||
|
else
|
||||||
|
i = after
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return atoms
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Compute branch offsets (target - branch - 1)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function compute_offsets(labels, branches)
|
||||||
|
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.pos .. ")")
|
||||||
|
end
|
||||||
|
table.insert(results, {target = br.target, tag = br.tag, offset = target - br.pos - 1 })
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Generate header for one source
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function generate_header(source_path, atoms_data)
|
||||||
|
local basename = basename_no_ext(source_path)
|
||||||
|
local guard = to_alnum_underscore(to_upper(basename)) .. "_OFFSETS_H"
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
local function add(s) table.insert(lines, s) end
|
||||||
|
|
||||||
|
add("// Auto-generated by tape_atom_offset_gen.meta.lua — DO NOT EDIT")
|
||||||
|
add("// Source: " .. source_path)
|
||||||
|
add("#pragma once")
|
||||||
|
add("")
|
||||||
|
add("#pragma region " .. basename)
|
||||||
|
add("")
|
||||||
|
add("// Dispatch macro: token-pastes <tag>_<target> to the enum name")
|
||||||
|
add("#undef atom_offset")
|
||||||
|
add("#define atom_offset(tag, name) atom_offset_##tag##_##name")
|
||||||
|
add("")
|
||||||
|
for _, atom in ipairs(atoms_data) do
|
||||||
|
if #atom.offsets > 0 then
|
||||||
|
add("// --- atom: " .. atom.name .. " (" .. atom.total_words .. " words) ---")
|
||||||
|
add("")
|
||||||
|
local consts = {}
|
||||||
|
for _, r in ipairs(atom.offsets) do
|
||||||
|
table.insert(consts, {
|
||||||
|
macro_name = "_atom_offset_" .. r.tag .. "_" .. r.target,
|
||||||
|
enum_name = "atom_offset_" .. r.tag .. "_" .. r.target,
|
||||||
|
value = r.offset
|
||||||
|
})
|
||||||
|
end
|
||||||
|
for _, c in ipairs(consts) do add("#define " .. pad_right(c.macro_name, 44) .. " " .. c.value .. "") end
|
||||||
|
add("")
|
||||||
|
add("enum {")
|
||||||
|
for _, c in ipairs(consts) do add(" " .. c.enum_name .. " = " .. c.macro_name .. ",") end
|
||||||
|
add("};")
|
||||||
|
add("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
add("#pragma endregion " .. basename)
|
||||||
|
add("")
|
||||||
|
return table.concat(lines, "\n") .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Process one source
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function process_source(source_path, word_counts)
|
||||||
|
local source = read_file(source_path)
|
||||||
|
local atoms_raw = find_atoms(source)
|
||||||
|
|
||||||
|
if #atoms_raw == 0 then
|
||||||
|
-- io.stderr:write(" note: no MipsAtom_ declarations in " .. source_path .. "\n")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local atoms_data = {}
|
||||||
|
for _, atom in ipairs(atoms_raw) do
|
||||||
|
local labels, branches, total = scan_atom_body(atom.body, word_counts)
|
||||||
|
local offsets = compute_offsets(labels, branches)
|
||||||
|
table.insert(atoms_data, {
|
||||||
|
name = atom.name,
|
||||||
|
total_words = total,
|
||||||
|
offsets = offsets
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local basename = basename_no_ext(source_path)
|
||||||
|
local out_dir = dirname(source_path) .. "/gen"
|
||||||
|
ensure_dir(out_dir)
|
||||||
|
local out_path = out_dir .. "/" .. basename .. ".offsets.h"
|
||||||
|
write_file(out_path, generate_header(source_path, atoms_data))
|
||||||
|
|
||||||
|
local total_branches = 0
|
||||||
|
for _, a in ipairs(atoms_data) do total_branches = total_branches + #a.offsets end
|
||||||
|
print(" " .. basename .. ": " .. #atoms_data .. " atom(s), " .. total_branches .. " branch(es)")
|
||||||
|
for _, a in ipairs(atoms_data) do
|
||||||
|
for _, r in ipairs(a.offsets) do
|
||||||
|
print(" " .. a.name .. " -> " .. r.tag .. ":" .. r.target .. " : " .. r.offset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Main
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
local function main(args)
|
||||||
|
if #args < 2 then
|
||||||
|
print("Usage: gen_atom_offsets.lua <metadata.h> <source1> [source2 ...]")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
local word_counts = load_word_counts(args[1])
|
||||||
|
for i = 2, #args do process_source(args[i], word_counts) end
|
||||||
|
end
|
||||||
|
|
||||||
|
main({...})
|
||||||
@@ -1,725 +0,0 @@
|
|||||||
#!/usr/bin/env lua
|
|
||||||
-- gen_atom_offsets.lua
|
|
||||||
--
|
|
||||||
-- Finds every `MipsAtom_(name) { ... }` declaration in the given sources,
|
|
||||||
-- counts the words in each body using the WORD_COUNT manifest, computes
|
|
||||||
-- branch offsets for atom_label(name)/atom_offset(name) markers, and writes
|
|
||||||
-- one header per source into `<source_dir>/gen/<basename>.offsets.h`.
|
|
||||||
--
|
|
||||||
-- Usage:
|
|
||||||
-- lua gen_atom_offsets.lua <metadata.h> <source1> [source2 ...]
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Character classification
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function is_space(c)
|
|
||||||
return c == " " or c == "\t" or c == "\n" or c == "\r" or c == "\v" or c == "\f"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_alpha(c)
|
|
||||||
if not c or #c == 0 then return false end
|
|
||||||
if c >= "a" and c <= "z" then return true end
|
|
||||||
if c >= "A" and c <= "Z" then return true end
|
|
||||||
return c == "_"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_digit(c)
|
|
||||||
return c and c >= "0" and c <= "9"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_alnum(c)
|
|
||||||
return is_alpha(c) or is_digit(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- I/O
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function read_file(path)
|
|
||||||
local f = io.open(path, "r")
|
|
||||||
if not f then error("Cannot open " .. path) end
|
|
||||||
local content = f:read("*a")
|
|
||||||
f:close()
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write_file(path, content)
|
|
||||||
local f = io.open(path, "w")
|
|
||||||
if not f then error("Cannot write " .. path) end
|
|
||||||
f:write(content)
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ensure_dir(path)
|
|
||||||
os.execute('mkdir -p "' .. path .. '"')
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- String primitives (no patterns)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function trim(s)
|
|
||||||
local a = 1
|
|
||||||
while a <= #s and is_space(s:sub(a, a)) do a = a + 1 end
|
|
||||||
local b = #s
|
|
||||||
while b >= a and is_space(s:sub(b, b)) do b = b - 1 end
|
|
||||||
return s:sub(a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function starts_with(s, prefix)
|
|
||||||
if #s < #prefix then return false end
|
|
||||||
for i = 1, #prefix do
|
|
||||||
if s:sub(i, i) ~= prefix:sub(i, i) then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ends_with(s, suffix)
|
|
||||||
if #s < #suffix then return false end
|
|
||||||
local off = #s - #suffix
|
|
||||||
for i = 1, #suffix do
|
|
||||||
if s:sub(off + i, off + i) ~= suffix:sub(i, i) then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_byte(haystack, target, start)
|
|
||||||
for i = start or 1, #haystack do
|
|
||||||
if haystack:sub(i, i) == target then return i end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dirname(path)
|
|
||||||
local last_sep = 0
|
|
||||||
for i = 1, #path do
|
|
||||||
local c = path:sub(i, i)
|
|
||||||
if c == "/" or c == "\\" then last_sep = i end
|
|
||||||
end
|
|
||||||
if last_sep == 0 then return "." end
|
|
||||||
return path:sub(1, last_sep - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function basename_no_ext(path)
|
|
||||||
local last_sep = 0
|
|
||||||
for i = 1, #path do
|
|
||||||
local c = path:sub(i, i)
|
|
||||||
if c == "/" or c == "\\" then last_sep = i end
|
|
||||||
end
|
|
||||||
local a = last_sep + 1
|
|
||||||
local last_dot = #path + 1
|
|
||||||
for i = #path, a, -1 do
|
|
||||||
if path:sub(i, i) == "." then last_dot = i; break end
|
|
||||||
end
|
|
||||||
return path:sub(a, last_dot - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function to_upper(s)
|
|
||||||
local out = ""
|
|
||||||
for i = 1, #s do
|
|
||||||
local code = string.byte(s, i)
|
|
||||||
if code >= 97 and code <= 122 then
|
|
||||||
out = out .. string.char(code - 32)
|
|
||||||
else
|
|
||||||
out = out .. s:sub(i, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
local function to_alnum_underscore(s)
|
|
||||||
local out = ""
|
|
||||||
for i = 1, #s do
|
|
||||||
local c = s:sub(i, i)
|
|
||||||
if is_alnum(c) then out = out .. c
|
|
||||||
else out = out .. "_" end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pad_right(s, width)
|
|
||||||
while #s < width do s = s .. " " end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Skip whitespace and comments
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function skip_ws_and_comments(source, i)
|
|
||||||
local len = #source
|
|
||||||
while i <= len do
|
|
||||||
local c = source:sub(i, i)
|
|
||||||
if is_space(c) then
|
|
||||||
i = i + 1
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "/" then
|
|
||||||
while i <= len and source:sub(i, i) ~= "\n" do i = i + 1 end
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "*" then
|
|
||||||
i = i + 2
|
|
||||||
while i <= len - 1 do
|
|
||||||
if source:sub(i, i) == "*" and source:sub(i+1, i+1) == "/" then
|
|
||||||
i = i + 2
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Read identifier
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function read_ident(source, i)
|
|
||||||
if not is_alpha(source:sub(i, i)) then return nil, i end
|
|
||||||
local a = i
|
|
||||||
i = i + 1
|
|
||||||
while i <= #source and is_alnum(source:sub(i, i)) do i = i + 1 end
|
|
||||||
return source:sub(a, i - 1), i
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Read balanced (open_char, close_char) group, return inner + new pos
|
|
||||||
-- Skips strings and comments inside.
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function read_balanced(source, open_char, close_char, i)
|
|
||||||
if source:sub(i, i) ~= open_char then return nil, i end
|
|
||||||
i = i + 1
|
|
||||||
local len = #source
|
|
||||||
local depth = 1
|
|
||||||
local a = i
|
|
||||||
while i <= len and depth > 0 do
|
|
||||||
local c = source:sub(i, i)
|
|
||||||
if c == open_char then
|
|
||||||
depth = depth + 1
|
|
||||||
i = i + 1
|
|
||||||
elseif c == close_char then
|
|
||||||
depth = depth - 1
|
|
||||||
if depth == 0 then break end
|
|
||||||
i = i + 1
|
|
||||||
elseif c == '"' then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if source:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif source:sub(i, i) == '"' then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "'" then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if source:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif source:sub(i, i) == "'" then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "/" then
|
|
||||||
while i <= len and source:sub(i, i) ~= "\n" do i = i + 1 end
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "*" then
|
|
||||||
i = i + 2
|
|
||||||
while i <= len - 1 do
|
|
||||||
if source:sub(i, i) == "*" and source:sub(i+1, i+1) == "/" then
|
|
||||||
i = i + 2
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return source:sub(a, i - 1), i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_parens(source, i) return read_balanced(source, "(", ")", i) end
|
|
||||||
local function read_braces(source, i) return read_balanced(source, "{", "}", i) end
|
|
||||||
local function read_brackets(source, i) return read_balanced(source, "[", "]", i) end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Scan forward from `start`, skipping balanced (), [], {}, strings, comments.
|
|
||||||
-- Returns position of first occurrence of `target` char at top level, or nil.
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function scan_to_char(source, target, start)
|
|
||||||
local len = #source
|
|
||||||
local i = start
|
|
||||||
while i <= len do
|
|
||||||
local c = source:sub(i, i)
|
|
||||||
if c == target then
|
|
||||||
return i
|
|
||||||
elseif c == "(" then
|
|
||||||
local _, after = read_parens(source, i); i = after
|
|
||||||
elseif c == "{" then
|
|
||||||
local _, after = read_braces(source, i); i = after
|
|
||||||
elseif c == "[" then
|
|
||||||
local _, after = read_brackets(source, i); i = after
|
|
||||||
elseif c == '"' then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if source:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif source:sub(i, i) == '"' then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "'" then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if source:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif source:sub(i, i) == "'" then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "/" then
|
|
||||||
while i <= len and source:sub(i, i) ~= "\n" do i = i + 1 end
|
|
||||||
elseif c == "/" and source:sub(i+1, i+1) == "*" then
|
|
||||||
i = i + 2
|
|
||||||
while i <= len - 1 do
|
|
||||||
if source:sub(i, i) == "*" and source:sub(i+1, i+1) == "/" then
|
|
||||||
i = i + 2
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Load WORD_COUNT manifest from metadata.h
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function load_word_counts(metadata_path)
|
|
||||||
local counts = {}
|
|
||||||
local content = read_file(metadata_path)
|
|
||||||
local len = #content
|
|
||||||
local i = 1
|
|
||||||
local prefix = "WORD_COUNT("
|
|
||||||
while i <= len do
|
|
||||||
local nl = find_byte(content, "\n", i)
|
|
||||||
local line_end = nl or (len + 1)
|
|
||||||
local line = content:sub(i, line_end - 1)
|
|
||||||
local trimmed = trim(line)
|
|
||||||
|
|
||||||
if starts_with(trimmed, prefix) and ends_with(trimmed, ")") then
|
|
||||||
local inner = trimmed:sub(#prefix + 1, #trimmed - 1)
|
|
||||||
local comma = find_byte(inner, ",", 1)
|
|
||||||
if comma then
|
|
||||||
local name = trim(inner:sub(1, comma - 1))
|
|
||||||
local cnt = trim(inner:sub(comma + 1))
|
|
||||||
counts[name] = tonumber(cnt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
i = line_end + 1
|
|
||||||
end
|
|
||||||
return counts
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Count words for a single comma-separated token
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function word_count_of_token(token, word_counts)
|
|
||||||
local i = 1
|
|
||||||
local len = #token
|
|
||||||
while i <= len and is_space(token:sub(i, i)) do i = i + 1 end
|
|
||||||
if i > len then return 0 end
|
|
||||||
local name, after = read_ident(token, i)
|
|
||||||
if not name then return 1 end
|
|
||||||
local j = skip_ws_and_comments(token, after)
|
|
||||||
if token:sub(j, j) == "(" then
|
|
||||||
local wc = word_counts[name]
|
|
||||||
if wc then return wc end
|
|
||||||
io.stderr:write(" warning: unknown macro '" .. name .. "', assuming 1 word\n")
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Split brace-body into top-level comma-separated tokens
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function split_top_level_commas(body)
|
|
||||||
local tokens = {}
|
|
||||||
local len = #body
|
|
||||||
local i = 1
|
|
||||||
local token_start = 1
|
|
||||||
while i <= len do
|
|
||||||
local c = body:sub(i, i)
|
|
||||||
if c == "(" then
|
|
||||||
local _, after = read_parens(body, i); i = after
|
|
||||||
elseif c == "{" then
|
|
||||||
local _, after = read_braces(body, i); i = after
|
|
||||||
elseif c == "[" then
|
|
||||||
local _, after = read_brackets(body, i); i = after
|
|
||||||
elseif c == '"' then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if body:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif body:sub(i, i) == '"' then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "'" then
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if body:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif body:sub(i, i) == "'" then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "/" and body:sub(i+1, i+1) == "/" then
|
|
||||||
while i <= len and body:sub(i, i) ~= "\n" do i = i + 1 end
|
|
||||||
elseif c == "/" and body:sub(i+1, i+1) == "*" then
|
|
||||||
i = i + 2
|
|
||||||
while i <= len - 1 do
|
|
||||||
if body:sub(i, i) == "*" and body:sub(i+1, i+1) == "/" then
|
|
||||||
i = i + 2
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
elseif c == "," then
|
|
||||||
table.insert(tokens, body:sub(token_start, i - 1))
|
|
||||||
i = i + 1
|
|
||||||
token_start = i
|
|
||||||
else
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local last = body:sub(token_start, len)
|
|
||||||
if trim(last) ~= "" then
|
|
||||||
table.insert(tokens, last)
|
|
||||||
end
|
|
||||||
return tokens
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Scan an atom body for atom_label/atom_offset markers, count words
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function scan_for_atom_markers(token, at_pos, labels, branches)
|
|
||||||
local i = 1
|
|
||||||
local len = #token
|
|
||||||
while i <= len do
|
|
||||||
i = skip_ws_and_comments(token, i)
|
|
||||||
if i > len then break end
|
|
||||||
local c = token:sub(i, i)
|
|
||||||
if c == '"' then
|
|
||||||
-- Skip string literal
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if token:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif token:sub(i, i) == '"' then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "'" then
|
|
||||||
-- Skip char literal
|
|
||||||
i = i + 1
|
|
||||||
while i <= len do
|
|
||||||
if token:sub(i, i) == "\\" then i = i + 2
|
|
||||||
elseif token:sub(i, i) == "'" then i = i + 1; break
|
|
||||||
else i = i + 1 end
|
|
||||||
end
|
|
||||||
elseif c == "/" and token:sub(i+1, i+1) == "/" then
|
|
||||||
while i <= len and token:sub(i, i) ~= "\n" do i = i + 1 end
|
|
||||||
elseif c == "/" and token:sub(i+1, i+1) == "*" then
|
|
||||||
i = i + 2
|
|
||||||
while i <= len - 1 do
|
|
||||||
if token:sub(i, i) == "*" and token:sub(i+1, i+1) == "/" then
|
|
||||||
i = i + 2
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
elseif is_alpha(c) then
|
|
||||||
local ident, after = read_ident(token, i)
|
|
||||||
if ident == "atom_label" or ident == "atom_offset" then
|
|
||||||
local arg_start = skip_ws_and_comments(token, after)
|
|
||||||
if token:sub(arg_start, arg_start) == "(" then
|
|
||||||
local inner, after_paren = read_parens(token, arg_start)
|
|
||||||
local n = 1
|
|
||||||
while n <= #inner and is_space(inner:sub(n, n)) do n = n + 1 end
|
|
||||||
local ns = n
|
|
||||||
while n <= #inner and is_alnum(inner:sub(n, n)) do n = n + 1 end
|
|
||||||
local name = inner:sub(ns, n - 1)
|
|
||||||
if name ~= "" then
|
|
||||||
if ident == "atom_label" then
|
|
||||||
labels[name] = at_pos
|
|
||||||
else
|
|
||||||
table.insert(branches, {pos = at_pos, target = name})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
i = after_paren
|
|
||||||
else
|
|
||||||
i = arg_start
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = after
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Anything else (parens, brackets, braces, commas, operators) — walk past
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function scan_atom_body(body, word_counts)
|
|
||||||
local pos = 0
|
|
||||||
local labels = {}
|
|
||||||
local branches = {}
|
|
||||||
|
|
||||||
for _, tok in ipairs(split_top_level_commas(body)) do
|
|
||||||
local k = 1
|
|
||||||
local tlen = #tok
|
|
||||||
while k <= tlen and is_space(tok:sub(k, k)) do k = k + 1 end
|
|
||||||
local leading_ident, leading_after = read_ident(tok, k)
|
|
||||||
|
|
||||||
if leading_ident == "atom_label" or leading_ident == "atom_offset" then
|
|
||||||
scan_for_atom_markers(tok, pos, labels, branches)
|
|
||||||
else
|
|
||||||
local words = word_count_of_token(tok, word_counts)
|
|
||||||
scan_for_atom_markers(tok, pos, labels, branches)
|
|
||||||
pos = pos + words
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return labels, branches, pos
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Token classification for atom detection
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- Skip past storage-class / qualifier noise. These appear before MipsCode
|
|
||||||
-- in raw expanded forms: `static`, `const`, the user's `internal`/`LP_`/
|
|
||||||
-- `global` macros (which all expand to `static`), `RO_` (which expands to
|
|
||||||
-- a section attribute), plus standard C qualifiers.
|
|
||||||
local function skip_qualifiers(source, i)
|
|
||||||
local keywords = {
|
|
||||||
["static"]=true, ["const"]=true, ["volatile"]=true,
|
|
||||||
["extern"]=true, ["register"]=true, ["auto"]=true,
|
|
||||||
["inline"]=true, ["typedef"]=true,
|
|
||||||
["internal"]=true, ["LP_"]=true, ["global"]=true, ["gkknown"]=true
|
|
||||||
}
|
|
||||||
while true do
|
|
||||||
i = skip_ws_and_comments(source, i)
|
|
||||||
local ident, after = read_ident(source, i)
|
|
||||||
if not ident then return i end
|
|
||||||
if keywords[ident] then
|
|
||||||
i = after
|
|
||||||
else
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Test whether `s` starts with literal `prefix` (no patterns).
|
|
||||||
local function has_prefix(s, prefix)
|
|
||||||
if #s < #prefix then return false end
|
|
||||||
for i = 1, #prefix do
|
|
||||||
if s:sub(i, i) ~= prefix:sub(i, i) then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Find every atom declaration in a source, both wrapped and raw
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function find_atoms(source_text)
|
|
||||||
local atoms = {}
|
|
||||||
local len = #source_text
|
|
||||||
local i = 1
|
|
||||||
|
|
||||||
-- First, scan inside source_text normally
|
|
||||||
local function try_wrapped_form(ident_pos)
|
|
||||||
local paren_pos = skip_ws_and_comments(source_text, ident_pos)
|
|
||||||
if source_text:sub(paren_pos, paren_pos) ~= "(" then
|
|
||||||
return nil -- not a MipsAtom_() call
|
|
||||||
end
|
|
||||||
local inner, after_paren = read_parens(source_text, paren_pos)
|
|
||||||
-- Extract name (first identifier from inner)
|
|
||||||
local n = 1
|
|
||||||
while n <= #inner and is_space(inner:sub(n, n)) do n = n + 1 end
|
|
||||||
local ns = n
|
|
||||||
while n <= #inner and is_alnum(inner:sub(n, n)) do n = n + 1 end
|
|
||||||
local name = inner:sub(ns, n - 1)
|
|
||||||
if name == "" then return nil end
|
|
||||||
|
|
||||||
local brace_pos = scan_to_char(source_text, "{", after_paren)
|
|
||||||
if not brace_pos then return nil end
|
|
||||||
local body, after_brace = read_braces(source_text, brace_pos)
|
|
||||||
return {name = name, body = body, after_brace = after_brace}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function try_raw_form(after_type_pos)
|
|
||||||
local next_pos = skip_ws_and_comments(source_text, after_type_pos)
|
|
||||||
local next_ident, next_after = read_ident(source_text, next_pos)
|
|
||||||
if not next_ident then return nil end
|
|
||||||
if not has_prefix(next_ident, "code_") then return nil end
|
|
||||||
if #next_ident <= 5 then return nil end -- bare "code_" — not an atom
|
|
||||||
|
|
||||||
local atom_name = next_ident:sub(6)
|
|
||||||
local brace_pos = scan_to_char(source_text, "{", next_after)
|
|
||||||
if not brace_pos then return nil end
|
|
||||||
local body, after_brace = read_braces(source_text, brace_pos)
|
|
||||||
return {name = atom_name, body = body, after_brace = after_brace}
|
|
||||||
end
|
|
||||||
|
|
||||||
while i <= len do
|
|
||||||
i = skip_ws_and_comments(source_text, i)
|
|
||||||
if i > len then break end
|
|
||||||
|
|
||||||
-- Skip past storage-class noise
|
|
||||||
i = skip_qualifiers(source_text, i)
|
|
||||||
if i > len then break end
|
|
||||||
|
|
||||||
local ident, after = read_ident(source_text, i)
|
|
||||||
if not ident then
|
|
||||||
i = i + 1
|
|
||||||
elseif ident == "MipsAtom_" then
|
|
||||||
local atom = try_wrapped_form(after)
|
|
||||||
if atom then
|
|
||||||
table.insert(atoms, {name = atom.name, body = atom.body})
|
|
||||||
i = atom.after_brace
|
|
||||||
else
|
|
||||||
i = i + 1 -- not actually MipsAtom_(), skip and continue
|
|
||||||
end
|
|
||||||
elseif ident == "MipsCode" then
|
|
||||||
local atom = try_raw_form(after)
|
|
||||||
if atom then
|
|
||||||
table.insert(atoms, {name = atom.name, body = atom.body})
|
|
||||||
i = atom.after_brace
|
|
||||||
else
|
|
||||||
i = after -- some other MipsCode use; skip just this token
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Anything else: skip just this identifier. The next loop
|
|
||||||
-- iteration will see whatever follows (might be more qualifiers,
|
|
||||||
-- another type keyword, etc.).
|
|
||||||
i = after
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return atoms
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Compute branch offsets: target - branch - 1
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function compute_offsets(labels, branches)
|
|
||||||
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.pos .. ")")
|
|
||||||
end
|
|
||||||
table.insert(results, {target = br.target, offset = target - br.pos - 1})
|
|
||||||
end
|
|
||||||
return results
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Generate header for one source
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function generate_header(source_path, atoms_data)
|
|
||||||
local basename = basename_no_ext(source_path)
|
|
||||||
local guard = to_alnum_underscore(to_upper(basename)) .. "_OFFSETS_H"
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
local function add(s) table.insert(lines, s) end
|
|
||||||
|
|
||||||
add("// Auto-generated by gen_atom_offsets.lua — DO NOT EDIT")
|
|
||||||
add("// Source: " .. source_path)
|
|
||||||
add("#ifndef " .. guard)
|
|
||||||
add("#define " .. guard)
|
|
||||||
add("")
|
|
||||||
add("#pragma region " .. basename)
|
|
||||||
add("")
|
|
||||||
add("// Override the placeholder atom_offset() to dispatch via token pasting.")
|
|
||||||
add("#undef atom_offset")
|
|
||||||
add("#define atom_offset(name) atom_offset_##name")
|
|
||||||
add("")
|
|
||||||
|
|
||||||
for _, atom in ipairs(atoms_data) do
|
|
||||||
add("// --- atom: " .. atom.name .. " (" .. atom.total_words .. " words) ---")
|
|
||||||
add("")
|
|
||||||
for _, r in ipairs(atom.offsets) do
|
|
||||||
local const_name = "atom_offset_" .. r.target
|
|
||||||
add("#define " .. pad_right(const_name, 40) .. " (" .. r.offset .. ")")
|
|
||||||
end
|
|
||||||
add("")
|
|
||||||
end
|
|
||||||
|
|
||||||
add("#pragma endregion " .. basename)
|
|
||||||
add("")
|
|
||||||
add("#endif // " .. guard)
|
|
||||||
return table.concat(lines, "\n") .. "\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Process one source
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function process_source(source_path, word_counts)
|
|
||||||
local source = read_file(source_path)
|
|
||||||
local atoms_raw = find_atoms(source)
|
|
||||||
|
|
||||||
if #atoms_raw == 0 then
|
|
||||||
io.stderr:write(" note: no MipsAtom_ declarations in " .. source_path .. "\n")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local atoms_data = {}
|
|
||||||
for _, atom in ipairs(atoms_raw) do
|
|
||||||
local labels, branches, total = scan_atom_body(atom.body, word_counts)
|
|
||||||
local offsets = compute_offsets(labels, branches)
|
|
||||||
table.insert(atoms_data, {
|
|
||||||
name = atom.name,
|
|
||||||
total_words = total,
|
|
||||||
offsets = offsets
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local basename = basename_no_ext(source_path)
|
|
||||||
local out_dir = dirname(source_path) .. "/gen"
|
|
||||||
ensure_dir(out_dir)
|
|
||||||
local out_path = out_dir .. "/" .. basename .. ".offsets.h"
|
|
||||||
write_file(out_path, generate_header(source_path, atoms_data))
|
|
||||||
|
|
||||||
local total_branches = 0
|
|
||||||
for _, a in ipairs(atoms_data) do total_branches = total_branches + #a.offsets end
|
|
||||||
print(" " .. basename .. ": " .. #atoms_data .. " atom(s), " .. total_branches .. " branch(es)")
|
|
||||||
for _, a in ipairs(atoms_data) do
|
|
||||||
for _, r in ipairs(a.offsets) do
|
|
||||||
print(" " .. a.name .. " -> " .. r.target .. " : " .. r.offset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Main
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
local function main(args)
|
|
||||||
if #args < 2 then
|
|
||||||
print("Usage: gen_atom_offsets.lua <metadata.h> <source1> [source2 ...]")
|
|
||||||
os.exit(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local metadata_path = args[1]
|
|
||||||
local sources = {}
|
|
||||||
for i = 2, #args do table.insert(sources, args[i]) end
|
|
||||||
|
|
||||||
local word_counts = load_word_counts(metadata_path)
|
|
||||||
for _, src in ipairs(sources) do process_source(src, word_counts) end
|
|
||||||
end
|
|
||||||
|
|
||||||
main({...})
|
|
||||||
Reference in New Issue
Block a user