orgnaizing, improving asm annotation

This commit is contained in:
2026-06-14 17:27:52 -04:00
parent 0040f6326e
commit 2c3d0c4af7
5 changed files with 223 additions and 203 deletions
+4
View File
@@ -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)
+118 -99
View File
@@ -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_<count>` 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_<count>` 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
+49 -47
View File
@@ -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_<field>` definitions above):
* gte_cmdw_<name> = gte_cmd_base | enc_gte_cmd(<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) \
)
+1 -1
View File
@@ -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)));
+51 -56
View File
@@ -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) \