From 2c3d0c4af774955d2f896483aac7158a5739ed3c Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 14 Jun 2026 17:27:52 -0400 Subject: [PATCH] orgnaizing, improving asm annotation --- code/duffle/dsl.h | 4 + code/duffle/gcc_asm.h | 217 +++++++++++++++++++++++------------------- code/duffle/gte.h | 96 ++++++++++--------- code/duffle/memory.h | 2 +- code/duffle/mips.h | 107 ++++++++++----------- 5 files changed, 223 insertions(+), 203 deletions(-) diff --git a/code/duffle/dsl.h b/code/duffle/dsl.h index cdfb60d..77fff3a 100644 --- a/code/duffle/dsl.h +++ b/code/duffle/dsl.h @@ -33,6 +33,7 @@ #define align_(value) __attribute__((aligned (value))) // for easy alignment #define C_(type,data) ((type)(data)) // for enforced precedence #define expect_(x, y) __builtin_expect(x, y) // so compiler knows the common path +#define cexpr_ __builtin_constant_p #define I_ internal inline #define FI_ inline __attribute__((always_inline)) // inline always #define NI_ internal __attribute__((noinline)) // inline never @@ -128,6 +129,9 @@ typedef __UINT32_TYPE__ TSet_(B4); #define u4_v(value) C_(U4 V_*, value) enum { false = 0, true = 1, true_overflow, }; +#define u4_lo(value) ((value) & 0xFFFFU) +#define u4_hi(value) ((value) >> 12) + typedef void Proc_(VoidFn) (void); #define kilo(n) (C_(U4, n) << 10) diff --git a/code/duffle/gcc_asm.h b/code/duffle/gcc_asm.h index 5dcc95f..a6175bc 100644 --- a/code/duffle/gcc_asm.h +++ b/code/duffle/gcc_asm.h @@ -4,15 +4,125 @@ #endif /* ============================================================================ - * GCC INLINE ASSEMBLY MACRO DSL - * - * - * ============================================================================ */ + * GCC INLINE ASM STATEMENT DSL + * ============================================================================ + * A complete GCC inline-asm statement has up to 5 sections separated by `:` + * asm volatile ( "code template" : OUTPUTS : INPUTS : CLOBBERS : GOTO_LABELS ); + */ - #pragma region Cruft +// Below are used purely for annotation. +#define asm_out // OUTPUTS section /* cannot be used with asm_words */ +#define asm_in // INPUTS section /* can be appended onto after asm_words for pinned registers */ +#define asm_clobber // CLOBBERS section + +// Pinned Registers after asm_words list (Semantic marker) +// We aren't starting a new offical section, its just a continuation of the input section. +// asm_words(...) // ".words " code word ids... : : code_words... +// asm_rpins, r_use(r0), ... // , pinned registers... +// asm_clobber: +#define asm_rpins + +/* --- Logic & Control Flow --- */ + +/* Annotation for the 'Goto' section of 'asm volatile goto'. + * Allows you to jump from assembly directly to a C label. */ +#define asm_goto // Annotate the last `:` in an asm expression. + +/* `asm_words(...)` dispatches into `_INL_` to emit up to 99 encoded + * instruction words. This is the "compiled-instruction" form of `asm_code`. + * + * Result is a 2-colon body WITHOUT the final clobber section: + * ".word %c0, %c1, ..." : --- empty --- : "i"(p0), "i"(p1), ... + * |------ code --------| |--- outputs ---| |------- inputs -------| + * + * Use it inside `asm volatile( ... )` like so: + * asm volatile( + * asm_words(w0, w1, w3) + * asm_clobber: clobbers + * ) + * which expands to: + * asm volatile(".word %c0, %c1, %c2" + * asm_out: // empty outputs + * asm_in: "i"(w0), "i"(w1), "i"(w2) + * asm_clobber: "$2", "$8", ... + * ) + */ +#define asm_words(...) m_expand(glue(GCC_ASM_INL_, GCC_ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)) +// Very nasty macro expansion. See the Cruft pragma region after all the DSL defines + +/* reg_str(n) — Stringify an integer register id into the GCC asm + * string form (e.g. 12 → "$12"). Use this anywhere GCC's parser + * expects a literal string identifying a register: clobber lists, + * asm templates, etc. The two-level macro is the standard preprocessor + * idiom for forcing one level of expansion before stringify — without + * it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead + * of expanding `R_T4` to its value first. + * + * For declaring a register variable bound to a specific GPR, use the + * `rgcc(n)` bundle from gcc_asm.h instead — it adds the `__asm__()` + * qualifier around the string. + * + * register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose + * register V3_S2* p0 rgcc(R_T4) = ...; // bundled + * + * asm volatile("nop" : : : reg_str(R_RA), "memory"); // clobber list */ +#define rlit_impl(n) "$" #n +#define rlit(n) rlit_impl(n) + +/* ------------------------------------------------------------------------ * + * rgcc(n) — GCC-specific bundle for register-variable declarations. + * + * Produces `__asm__(reg_str(tmpl(n, Code)))` at expansion time. + * The `tmpl(n, Code)` indirection derives the preprocessor-visible `_Code` + * form from the enum name (which the preprocessor can't expand on its own). + * So a call is: register V3_S2* p rgcc(R_T4) = verts[0].ptr; + * expands (via tmpl) to: register V3_S2* p __asm__(rlit(R_T4_Code)) = verts[0].ptr; + * which (via reg_str) becomes: register V3_S2* p __asm__("$12") = verts[0].ptr; + * + * Why bundle the `__asm__()` wrapper? + * - The integer R_T4 (= 12, via R_T4_Code) already indicates the register. + * - The string "$12" is derived from it via reg_str, so they cannot drift apart. + * - Spelling `__asm__(reg_str(R_T4_Code))` at every call site is noise. + * + * tmpl defined in dsl.h (the token-paste glue). + * rgcc define here (gcc_asm.h) because the `__asm__` keyword is GCC-specific. + * Anyone porting to a different compiler's asm dialect overrides rgcc, + * and the integer→string derivation in rlit can be retargeted in one place. + * + * For clobber lists and asm-template strings, use the bare `rlit(R_T4_Code)`. + * ------------------------------------------------------------------------ */ +#define rgcc_(n) __asm__(rlit(tmpl(n, Code))) +#define rgcc(n) rgcc_(n) + +/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used + * by the placeholder-pun macros (the .word bodies are fully baked + * at compile time and have no runtime operand references), but kept + * here for completeness in case a future asm template needs to refer + * to a runtime input by position. Mirror of rgcc but produces "%N" + * instead of "$N". */ +#define rgcc_ref_(n) "%" #n +#define rgcc_ref(n) rgcc_ref_(n) + +/* --- Register Constraint Aliases (for Pinned Variables) --- */ + +#define r_use(var) "r"(var) /* General Purpose Register */ +#define r_set(var) "=r"(var) /* Write-only output */ +#define r_mod(var) "+r"(var) /* Read-write */ +#define r_imm(val) "i"(val) /* Immediate / Constant */ +/* Memory: Forces GCC to sync the variable to RAM before the asm runs. + * Essential for DMA buffers or when the hardware reads from memory. */ +#define r_mem(var) "m"(var) +#define r_imm(val) "i"(val) /* Immediate: Forces a compile-time constant. */ +#define r_fpu(var) "f"(var) /* FPU (PS2/MIPS III/IV): Use for COP1 floating point registers. */ +#define r_acc(var) "a"(var) /* Accumulator: Use for HI/LO register results (multiplication/division). */ + +#define clb_mem_drain "memory" + +// C Preprocessor Iterative Expansion Jank +#pragma region Cruft /* --- 1. The Argument Counter --- */ -#define _ASM_COUNT_ARGS_IMPL( \ +#define GCC_ASM_COUNT_ARGS_IMPL( \ _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ @@ -24,7 +134,7 @@ _81,_82,_83,_84,_85,_86,_87,_88,_89,_90, \ _91,_92,_93,_94,_95,_96,_97,_98,_99, N, ...) N -#define _ASM_COUNT_ARGS(...) m_expand(_ASM_COUNT_ARGS_IMPL(__VA_ARGS__, \ +#define GCC_ASM_COUNT_ARGS(...) m_expand(GCC_ASM_COUNT_ARGS_IMPL(__VA_ARGS__, \ 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, \ 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, \ 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, \ @@ -344,95 +454,4 @@ #define GCC_ASM_INL_98(a, ...) ".word " GCC_ASM_W98 : : GCC_ASM_I98(a, __VA_ARGS__) #define GCC_ASM_INL_99(a, ...) ".word " GCC_ASM_W99 : : GCC_ASM_I99(a, __VA_ARGS__) - #pragma endregion Cruft - -/* ============================================================================ - * AST BUILDERS — assemble a complete inline-asm block - * ============================================================================ - * - * A complete GCC inline-asm statement has up to 4 sections separated by `:`: - * asm volatile ( "code" : OUTPUTS : INPUTS : CLOBBERS ); - * Below are used purely for annotation. - */ -#define asm_out -#define asm_in -#define asm_clobber - -/* `asm_inline(...)` dispatches into `_INL_` to emit up to 99 encoded - * instruction words. This is the "compiled-instruction" form of `asm_code`. - * - * Result is a 2-colon body WITHOUT the final clobber section: - * ".word %c0, %c1, ..." : --- empty --- : "i"(p0), "i"(p1), ... - * |------ code --------| |--- outputs ---| |------- inputs -------| - * - * Use it inside `asm volatile( ... )` like so: - * asm volatile( - * asm_inline(w0, w1, w3) - * asm_clobber: clobbers - * ) - * which expands to: - * asm volatile(".word %c0, %c1, %c2" - * asm_out: // empty outputs - * asm_in: "i"(w0), "i"(w1), "i"(w2) - * asm_clobber: "$2", "$8", ... - * ) - */ -#define asm_words(...) m_expand(glue(GCC_ASM_INL_, _ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)) - -/* reg_str(n) — Stringify an integer register id into the GCC asm - * string form (e.g. 12 → "$12"). Use this anywhere GCC's parser - * expects a literal string identifying a register: clobber lists, - * asm templates, etc. The two-level macro is the standard preprocessor - * idiom for forcing one level of expansion before stringify — without - * it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead - * of expanding `R_T4` to its value first. - * - * For declaring a register variable bound to a specific GPR, use the - * `rgcc(n)` bundle from gcc_asm.h instead — it adds the `__asm__()` - * qualifier around the string. - * - * register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose - * register V3_S2* p0 rgcc(R_T4) = ...; // bundled - * - * asm volatile("nop" : : : reg_str(R_RA), "memory"); // clobber list */ -#define rlit_impl(n) "$" #n -#define rlit(n) rlit_impl(n) - -/* ------------------------------------------------------------------------ * - * rgcc(n) — GCC-specific bundle for register-variable declarations. - * - * Produces `__asm__(reg_str(tmpl(n, Code)))` at expansion time. - * The `tmpl(n, Code)` indirection derives the preprocessor-visible `_Code` - * form from the enum name (which the preprocessor can't expand on its own). - * So a call is: register V3_S2* p rgcc(R_T4) = verts[0].ptr; - * expands (via tmpl) to: register V3_S2* p __asm__(rlit(R_T4_Code)) = verts[0].ptr; - * which (via reg_str) becomes: register V3_S2* p __asm__("$12") = verts[0].ptr; - * - * Why bundle the `__asm__()` wrapper? - * - The integer R_T4 (= 12, via R_T4_Code) already indicates the register. - * - The string "$12" is derived from it via reg_str, so they cannot drift apart. - * - Spelling `__asm__(reg_str(R_T4_Code))` at every call site is noise. - * - * tmpl defined in dsl.h (the token-paste glue). - * rgcc define here (gcc_asm.h) because the `__asm__` keyword is GCC-specific. - * Anyone porting to a different compiler's asm dialect overrides rgcc, - * and the integer→string derivation in rlit can be retargeted in one place. - * - * For clobber lists and asm-template strings, use the bare `rlit(R_T4_Code)`. - * ------------------------------------------------------------------------ */ -#define rgcc_(n) __asm__(rlit(tmpl(n, Code))) -#define rgcc(n) rgcc_(n) - -/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used - * by the placeholder-pun macros (the .word bodies are fully baked - * at compile time and have no runtime operand references), but kept - * here for completeness in case a future asm template needs to refer - * to a runtime input by position. Mirror of rgcc but produces "%N" - * instead of "$N". */ -#define rgcc_ref_(n) "%" #n -#define rgcc_ref(n) rgcc_ref_(n) -/* --- Register Constraint Aliases (for Pinned Variables) --- */ - -#define r_use(var) "r"(var) /* Input: Tells GCC we are reading this pinned register */ -#define r_set(var) "=r"(var) /* Output: Tells GCC we are overwriting this pinned register */ -#define r_mod(var) "+r"(var) /* Modify: Tells GCC we are reading AND writing this pinned register */ +#pragma endregion Cruft diff --git a/code/duffle/gte.h b/code/duffle/gte.h index df17434..b0de607 100644 --- a/code/duffle/gte.h +++ b/code/duffle/gte.h @@ -271,6 +271,9 @@ enum { _C2_OPS_ = 0 * - rd: COP2 control register index (0..31) */ #define enc_gte_tx(sub, rt, rd) (enc_op(op_cop2) | enc_rs(sub) | enc_rt(rt) | enc_rd(rd)) +#define gte_mt(rt, rd) enc_gte_tx(cop_mt, (rt), (rd)) /* Move GPR (rt) to GTE Control Register (rd) */ +#define gte_mf(rt, rd) enc_gte_tx(cop_mf, (rt), (rd)) /* Move GTE Control Register (rd) to GPR (rt) */ + /* COP2 Data Load (lwc2): `lwc2 rt, off(rs)` * Layout: [op_lwc2:6][rs:5][rt:5][imm:16] * - rs: GPR base address @@ -332,7 +335,7 @@ enum { _C2_OPS_ = 0 * * Decomposition (per the `enc_gte_` definitions above): * gte_cmdw_ = gte_cmd_base | enc_gte_cmd() - * + * The SF/MX/V/CV/LM fields are all zero in the common cases (standard * rotation-matrix, no scaling factor, V0 vector, translation vector, * no clamp), so the only varying bits are the `cmd` field. @@ -401,12 +404,12 @@ enum { GTE_Z_Offset = 4 }; -#define gte_load_word_v0(base) enc_gte_lw(gte_in_v0_xy, (base), 0) -#define gte_load_word_v0z(base) enc_gte_lw(gte_in_v0_z, (base), GTE_Z_Offset) -#define gte_load_word_v1(base) enc_gte_lw(gte_in_v1_xy, (base), 0) -#define gte_load_word_v1z(base) enc_gte_lw(gte_in_v1_z, (base), GTE_Z_Offset) -#define gte_load_word_v2(base) enc_gte_lw(gte_in_v2_xy, (base), 0) -#define gte_load_word_v2z(base) enc_gte_lw(gte_in_v2_z, (base), GTE_Z_Offset) +#define gte_lw_v0(base) enc_gte_lw(gte_in_v0_xy, (base), 0) +#define gte_lw_v0z(base) enc_gte_lw(gte_in_v0_z, (base), GTE_Z_Offset) +#define gte_lw_v1(base) enc_gte_lw(gte_in_v1_xy, (base), 0) +#define gte_lw_v1z(base) enc_gte_lw(gte_in_v1_z, (base), GTE_Z_Offset) +#define gte_lw_v2(base) enc_gte_lw(gte_in_v2_xy, (base), 0) +#define gte_lw_v2z(base) enc_gte_lw(gte_in_v2_z, (base), GTE_Z_Offset) /* gte_load_vN(r_ptr, base) — placeholder-punned lwc2 loaders * @@ -445,26 +448,23 @@ enum { * * The `asm_clobber(...)` helper from gcc_asm.h prepends the colon that * starts the clobbers section. */ -#define gte_load_v0(r_ptr, base) \ - asm volatile( \ - asm_words( gte_load_word_v0(base), gte_load_word_v0z(base) ) \ - , "r"(r_ptr) \ - asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), "memory" \ - ) +#define gte_load_v0(r_ptr, base) asm volatile( \ + asm_words( gte_lw_v0(base), gte_lw_v0z(base) ) \ + asm_rpins, r_use(r_ptr) \ + asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), clb_mem_drain \ +) -#define gte_load_v1(r_ptr, base) \ - asm volatile( \ - asm_words( gte_load_word_v1(base), gte_load_word_v1z(base) ) \ - , "r"(r_ptr) \ - asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), "memory" \ - ) +#define gte_load_v1(r_ptr, base) asm volatile( \ + asm_words( gte_lw_v1(base), gte_lw_v1z(base) ) \ + asm_rpins, r_use(r_ptr) \ + asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), clb_mem_drain \ +) -#define gte_load_v2(r_ptr, base) \ - asm volatile( \ - asm_words( gte_load_word_v2(base), gte_load_word_v2z(base) ) \ - , "r"(r_ptr) \ - asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), "memory" \ - ) +#define gte_load_v2(r_ptr, base) asm volatile( \ + asm_words( gte_lw_v2(base), gte_lw_v2z(base) ) \ + asm_rpins, r_use(r_ptr) \ + asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), clb_mem_drain \ +) /* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — the canonical prelude to gte_cmd_rtpt. * @@ -478,14 +478,15 @@ enum { * gte_load_v0v1v2(p0, p1, p2, R_T4, R_T5, R_T6); * gte_rtpt(); */ -#define gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) \ - asm volatile( \ - asm_words( gte_lwc2_v0(b0), gte_lwc2_v0z(b0), \ - gte_lwc2_v1(b1), gte_lwc2_v1z(b1), \ - gte_lwc2_v2(b2), gte_lwc2_v2z(b2) ) \ - , "r"(p0), "r"(p1), "r"(p2) \ - asm_clobber( rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), "memory" ) \ - ) +#define gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) asm volatile( \ + asm_words( \ + gte_lw_v0(b0), gte_lw_v0z(b0), \ + gte_lw_v1(b1), gte_lw_v1z(b1), \ + gte_lw_v2(b2), gte_lw_v2z(b2) ) \ + asm_rpins \ + , r_use(p0), r_use(p1), r_use(p2) \ + asm_clobber: rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), clb_mem_drain \ +) /** * @brief Rotate, Translate and Perspective Triple (23 cycles) @@ -623,19 +624,20 @@ enum { * get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output. */ #define asm_gte_matrix_set_rotation(r0) \ - asm volatile(asm_words( \ - load_imm(R_T4, r0, 0) \ - , load_imm(R_T5, r0, 4) \ - , enc_cop2_tx(cop_mt, R_T4, 0) \ - , enc_cop2_tx(cop_mt, R_T5, 1) \ - , load_imm(R_T4, r0, 8) \ - , load_imm(R_T5, r0, 12) \ - , load_imm(R_T6, r0, 16) \ - , enc_cop2_tx(cop_mt, R_T4, 2) \ - , enc_cop2_tx(cop_mt, R_T5, 3) \ - , enc_cop2_tx(cop_mt, R_T6, 4) \ + asm volatile( \ + asm_words( \ + load_word(R_T5, R_T4, 0) \ + , load_word(R_T6, R_T4, 4) \ + , gte_mt( R_T5, 0) \ + , gte_mt( R_T6, 1) \ + , load_word(R_T5, R_T4, 8) \ + , load_word(R_T6, R_T4, 12) \ + , load_word(R_T4, R_T4, 16) \ + , gte_mt( R_T5, 2) \ + , gte_mt( R_T6, 3) \ + , gte_mt( R_T4, 4) \ ) \ - asm_clobber: clb_system, reg_str(R_T4_Code), reg_str(R_T5_Code), reg_str(R_T6_Code) \ - : \ - : "r"(r0) \ + , r_use(r0) \ + asm_clobber: clb_system, rlit(R_T4_Code), rlit(R_T5_Code), rlit(R_T6_Code) \ ) + diff --git a/code/duffle/memory.h b/code/duffle/memory.h index 5b90b8c..b2999bc 100644 --- a/code/duffle/memory.h +++ b/code/duffle/memory.h @@ -10,7 +10,7 @@ assert((point) <= (end)); \ } while(0) -inline U4 align_pow2(U4 x, U4 b) { +I_ U4 align_pow2(U4 x, U4 b) { assert(b != 0); assert((b & (b - 1)) == 0); // Check power of 2 return ((x + b - 1) & (~(b - 1))); diff --git a/code/duffle/mips.h b/code/duffle/mips.h index 9487f10..d65b156 100644 --- a/code/duffle/mips.h +++ b/code/duffle/mips.h @@ -80,6 +80,7 @@ enum { , rtmp_2 = R_T2 /* Temporary (Caller saved) */ , rtmp_3 = R_T3 /* Temporary (Caller saved) */ , rtmp_4 = R_T4 /* Temporary (Caller saved) — common GTE base pointer */ + , rtmp_9 = R_T9 /* Temporary (Caller saved) — common GTE base pointer */ , rstatic_0 = R_S0 /* Static (Callee saved, preserved across calls) */ , rstatic_1 = R_S1 , rstatic_2 = R_S2 @@ -165,7 +166,7 @@ enum { /* --- Coprocessor 0 (System Control & Exceptions) --- */ , cop_mf = 0x00 /* Move From Coprocessor */ - , cop_mt = 0x04 /* Move To Coprocessor */ + , cop_mt = 0x04 /* Move To Coprocessor */ }; @@ -231,7 +232,7 @@ enum { _BitOffsets = 0 * shift_ll(rd, rt, shamt) → sll rd, rt, shamt * jump_reg(rs) → jr rs * jump_link(rs, rd) → jalr rs (link in rd, default $ra) - * nop → sll $0, $0, 0 + * nop → sll $0, $0, 0 */ #define load_word(rt, base, off) enc_i(op_lw, (base), (rt), (off)) #define load_byte(rt, base, off) enc_i(op_lb, (base), (rt), (off)) @@ -240,10 +241,10 @@ enum { _BitOffsets = 0 #define load_half_u(rt, base, off) enc_i(op_lhu, (base), (rt), (off)) #define store_word(rt, base, off) enc_i(op_sw, (base), (rt), (off)) #define add_ui(rt, rs, imm) enc_i(op_addiu, (rs), (rt), (imm)) -#define andi_op(rt, rs, imm) enc_i(op_andi, (rs), (rt), (imm)) -#define ori_op(rt, rs, imm) enc_i(op_ori, (rs), (rt), (imm)) -#define xori_op(rt, rs, imm) enc_i(op_xori, (rs), (rt), (imm)) -#define lui_op(rt, imm) enc_i(op_lui, R_0, (rt), (imm)) +#define and_si(rt, rs, imm) enc_i(op_andi, (rs), (rt), (imm)) +#define or_i(rt, rs, imm) enc_i(op_ori, (rs), (rt), (imm)) +#define xor_i(rt, rs, imm) enc_i(op_xori, (rs), (rt), (imm)) +#define load_ui(rt, imm) enc_i(op_lui, R_0, (rt), (imm)) /* Shift family (R-type). shift_ll/lr/ra: `sll rd, rt, shamt` */ #define shift_ll(rd, rt, shamt) enc_r(op_special, R_0, (rt), (rd), (shamt), fc_sll) @@ -332,7 +333,7 @@ enum { _BitOffsets = 0 #define breakpoint() enc_r(op_special, R_0, R_0, R_0, 0, fc_break) /* --- Shift-amount alias (matches the gas convention `\p3 = shamt`) --- */ -#define shamt(rd, rt, n) shift_ll(rd, rt, n) +#define shift_amount(rd, rt, n) shift_ll(rd, rt, n) /* nop — canonical sll $0, $0, 0 */ #define nop shift_ll(rdiscard, rdiscard, 0) @@ -360,48 +361,44 @@ enum { _BitOffsets = 0 * * Statement-level (not expression-level): emits its own `asm volatile(...)`. */ -#define load_imm_2w(rt, imm) do { \ - U4 _li2_imm_ = (U4)(imm); \ - U4 _li2_lo_ = _li2_imm_ & 0xFFFFU; \ - U4 _li2_hi_ = _li2_imm_ >> 16; \ - if (_li2_lo_ <= 0x7FFFU) { \ - asm volatile( \ - asm_words(lui_op((rt), _li2_hi_), \ - add_si((rt), (rt), (S2)(U2)_li2_lo_)) \ - asm_clobber(rlit(R_AT_Code), "memory") \ - ); \ - } \ - else { \ - asm volatile( \ - asm_words(lui_op((rt), _li2_hi_), \ - ori_op((rt), (rt), (U2)_li2_lo_)) \ - asm_clobber(rlit(R_AT_Code), "memory") \ - ); \ - } \ +#define load_imm_2w(rt, imm) do { \ + if (u4_low(imm) <= 0x7FFFU) { \ + asm volatile( \ + asm_words(load_ui((rt), u4_hi(imm), \ + add_si((rt), (rt), (S2)C_(U2,u4_lo(imm))) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ + ); \ + } \ + else { \ + asm volatile(asm_words( \ + load_ui((rt), u4_hi(imm)), \ + or_i((rt), (rt), C_(U2,u4_lo(imm)) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ + ); \ + } \ } while (0) /* load_imm_2w_ori — force the `lui` + `ori` form regardless of lo16 sign. * Use when you specifically need zero-extension in the lo half. */ -#define load_imm_2w_ori(rt, imm) do { \ - U4 _li2o_imm_ = (U4)(imm); \ - asm volatile( \ - asm_words(lui_op((rt), _li2o_imm_ >> 16), \ - ori_op((rt), (rt), (U2)(_li2o_imm_ & 0xFFFFU))) \ - asm_clobber(rlit(R_AT_Code), "memory") \ - ); \ +#define load_imm_2w_ori(rt, imm) do { \ + asm volatile( \ + asm_words(load_ui((rt), u4_lo(imm)), \ + or_i((rt), (rt), C_(U2,u4_hi(imm))) ) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ + ); \ } while (0) /* load_imm_2w_addi — force the `lui` + `addi` form regardless of lo16 sign. * Use when you know sign-extension is fine (e.g. lo16 is treated as * signed downstream) and you want a smaller effective instruction * (the assembler/MIPS hardware will sign-extend the imm16). */ -#define load_imm_2w_addi(rt, imm) do { \ - U4 _li2a_imm_ = (U4)(imm); \ - asm volatile( \ - asm_words(lui_op((rt), _li2a_imm_ >> 16), \ - add_si((rt), (rt), (S2)(U2)(_li2a_imm_ & 0xFFFFU))) \ - asm_clobber(rlit(R_AT_Code), "memory") \ - ); \ +#define load_imm_2w_addi(rt, imm) do { \ + /*U4 _li2a_imm_ = (U4)(imm);*/ \ + asm volatile(asm_words( \ + lui_op((rt), u4_lo(imm)), \ + add_si((rt), (rt), (S2)C_(U2,u4_hi(imm))) ) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ + ); \ } while (0) /* load_imm rt, imm — true `li` semantics (assembler `li` pseudo) @@ -424,18 +421,18 @@ enum { _BitOffsets = 0 * but that path is unusual (load_imm is most useful with literal * addresses and magic numbers). */ #define load_imm(rt, imm) do { \ - if (__builtin_constant_p(imm) && ((U4)(imm) <= 0x7FFFU)) { \ + if (cexpr_(imm) && ((imm) <= 0x7FFFU)) { \ /* Small positive: addi rt, $0, imm */ \ asm volatile( \ asm_words(add_si((rt), R_0, (imm))) \ - asm_clobber(rlit(R_AT_Code), "memory") \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ ); \ } \ - else if (__builtin_constant_p(imm) && ((U4)(imm) <= 0xFFFFU)) { \ + else if (cexpr_(imm) && ((U4)(imm) <= 0xFFFFU)) { \ /* 0x8000..0xFFFF: ori rt, $0, imm (zero-extends) */ \ asm volatile( \ - asm_words(ori_op((rt), R_0, (imm))) \ - asm_clobber(rlit(R_AT_Code), "memory") \ + asm_words(or_i((rt), R_0, (imm))) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ ); \ } \ else \ @@ -443,20 +440,18 @@ enum { _BitOffsets = 0 /* > 16 bits: lui + (ori | addi). \ * If lo16 is in [0, 0x7FFF] use addi (sign-ext is harmless \ * since the high half cleared bits 15..0). Otherwise ori. */ \ - U4 _li_imm_ = (U4)(imm); \ - U4 _li_lo_ = _li_imm_ & 0xFFFFU; \ - U4 _li_hi_ = _li_imm_ >> 16; \ - if (_li_lo_ <= 0x7FFFU) { \ + if (u4_lo(imm) <= 0x7FFFU) { \ asm volatile(asm_words( \ - lui_op((rt), _li_hi_), \ - add_si((rt), (rt), (S2)(U2)_li_lo_)) \ - asm_clobber(rlit(R_AT_Code), "memory") \ + load_ui((rt), u4_hi(imm)), \ + add_si((rt), (rt), (S2)C_(U2,u4_lo(imm))) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ ); \ } \ else { \ asm volatile(asm_words( \ - lui_op((rt), _li_hi_), ori_op((rt), (rt), (U2)_li_lo_)) \ - asm_clobber: rlit(R_AT_Code), "memory" \ + load_ui((rt), u4_hi(imm)), \ + or_i((rt), (rt), C_(U2,u4_lo(imm)) \ + asm_clobber: rlit(R_AT_Code), clb_mem_drain \ ); \ } \ } \ @@ -465,7 +460,7 @@ enum { _BitOffsets = 0 // Binary Metaprogramming typedef U4 const Code; -#define CodeBlob_(sym) tmpl(codeblob,sym) [] align_(4) = +#define CodeBlob_(sym) tmpl(code,sym) [] align_(4) = enum { bios_flushcache = 0x44, @@ -495,13 +490,13 @@ Code CodeBlob_(mips_flush_icache) { , jump_reg(rret_addr) /* jr $ra */ , add_ui(rstack_ptr, rstack_ptr, 8) /* sp += 8 (BD) */ }; -FI_ void mips_flush_icache(void) { C_(VoidFn*, codeblob_mips_flush_icache)(); } +FI_ void mips_flush_icache(void) { C_(VoidFn*, code_mips_flush_icache)(); } /* Standard clobber list for pure-MIPS asm volatile blocks: caller-saved * GPRs that the kernel treats as volatile (v0/v1/t0/t1/ra) plus the * "memory" barrier. The register ids are passed through `rlit` so * the R_*_Code `#define`s are stringified into "$N" at expansion time. */ -#define clb_system rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), "memory" +#define clb_system rlit(R_V0_Code), rlit(R_T0_Code), rlit(R_T1_Code), rlit(R_RA_Code), clb_mem_drain #define asm_mips_flush_icache() asm volatile( asm_words( \ add_ui(rstack_ptr, rstack_ptr, -8) \