@@ -184,10 +184,15 @@ typedef enum
184184 FUNC_OSOF ,
185185 FUNC_BADJ ,
186186 FUNC_OUTJ ,
187- FUNC_BADR ,
188- FUNC_BADW ,
187+ #ifndef NDEBUG
188+ FUNC_HIBITSET ,
189+ #endif
189190 FUNC_ERR_BEGIN = FUNC_PSOF ,
190- FUNC_ERR_END = FUNC_BADW ,
191+ #ifndef NDEBUG
192+ FUNC_ERR_END = FUNC_HIBITSET ,
193+ #else
194+ FUNC_ERR_END = FUNC_OUTJ ,
195+ #endif
191196 OFFSET_T_LAST
192197} offset_t ;
193198
@@ -206,7 +211,6 @@ static uint32_t funcOffset[ OFFSET_T_LAST ];
206211static uint32_t branchOffset [ OFFSET_T_LAST ];
207212static uint32_t savedBranchOffset [ OFFSET_T_LAST ];
208213
209- static qboolean forceDataMask ;
210214
211215static void VM_FreeBuffers ( void )
212216{
@@ -243,6 +247,44 @@ static void VM_FreeBuffers( void )
243247 ( (((unsigned)(op)&0x3F)<<26) | (((unsigned)(rt)&0x1F)<<21) | \
244248 (((unsigned)(ra)&0x1F)<<16) | ((unsigned)(d)&0xFFFF) )
245249
250+ // Debug-only range checks for D-form immediate fields. The encoding
251+ // silently truncates to 16 bits; these helpers turn out-of-range
252+ // immediates into a loud Com_Error rather than a wrong instruction
253+ // at JIT-compile time. Useful as defence against the
254+ // signed/unsigned-16-bit immediate confusion class of bug.
255+ #ifndef NDEBUG
256+ // Signed 16-bit immediate: accept either the signed view
257+ // (INT16_MIN..INT16_MAX) or the unsigned bit-pattern (0..UINT16_MAX),
258+ // since callers sometimes pre-extract a uint16 half (e.g. emit_MOVi64).
259+ // The CPU's sign-extension means both views encode the same 16 bits.
260+ static inline uint32_t _ppc_chk_si16 ( int32_t v , const char * opname )
261+ {
262+ if ( v < INT16_MIN || v > UINT16_MAX ) {
263+ Com_Error ( ERR_DROP ,
264+ "JIT: %s signed-16 immediate out of range: 0x%x" ,
265+ opname , (uint32_t )v );
266+ }
267+ return (uint32_t )v ;
268+ }
269+ // Unsigned 16-bit immediate (ANDI/ORI/XORI/CMPLWI etc.): the
270+ // instruction zero-extends to 32 bits, so anything with the upper
271+ // 16 bits non-zero is a sign-vs-unsigned bug in waiting.
272+ static inline uint32_t _ppc_chk_ui16 ( int32_t v , const char * opname )
273+ {
274+ if ( ( (uint32_t )v & 0xFFFF0000u ) != 0 ) {
275+ Com_Error ( ERR_DROP ,
276+ "JIT: %s unsigned-16 immediate out of range: 0x%x" ,
277+ opname , (uint32_t )v );
278+ }
279+ return (uint32_t )v ;
280+ }
281+ #define PPC_IMM_S16 (v , op ) _ppc_chk_si16((v), (op))
282+ #define PPC_IMM_U16 (v , op ) _ppc_chk_ui16((v), (op))
283+ #else
284+ #define PPC_IMM_S16 (v , op ) ((uint32_t)(v))
285+ #define PPC_IMM_U16 (v , op ) ((uint32_t)(v))
286+ #endif
287+
246288// ---- DS-form ----
247289// ld, std, lwa
248290// The DS field is 14 bits; actual byte displacement = DS << 2
@@ -335,17 +377,17 @@ static void VM_FreeBuffers( void )
335377
336378// -- Arithmetic (D-form) --
337379// addi rt, ra, si (li rt, si when ra=0)
338- #define PPC_ADDI (rt , ra , si ) PPC_D(14, rt, ra, si )
380+ #define PPC_ADDI (rt , ra , si ) PPC_D(14, rt, ra, PPC_IMM_S16(si, "ADDI") )
339381// addis rt, ra, si (lis rt, si when ra=0)
340- #define PPC_ADDIS (rt , ra , si ) PPC_D(15, rt, ra, si )
382+ #define PPC_ADDIS (rt , ra , si ) PPC_D(15, rt, ra, PPC_IMM_S16(si, "ADDIS") )
341383// li rt, si (pseudo for addi rt, 0, si)
342384#define PPC_LI (rt , si ) PPC_ADDI(rt, R0, si)
343385// lis rt, si (pseudo for addis rt, 0, si)
344386#define PPC_LIS (rt , si ) PPC_ADDIS(rt, R0, si)
345387// subfic rt, ra, si
346- #define PPC_SUBFIC (rt , ra , si ) PPC_D(8, rt, ra, si )
388+ #define PPC_SUBFIC (rt , ra , si ) PPC_D(8, rt, ra, PPC_IMM_S16(si, "SUBFIC") )
347389// mulli rt, ra, si
348- #define PPC_MULLI (rt , ra , si ) PPC_D(7, rt, ra, si )
390+ #define PPC_MULLI (rt , ra , si ) PPC_D(7, rt, ra, PPC_IMM_S16(si, "MULLI") )
349391
350392// -- Arithmetic (XO-form) --
351393// add rt, ra, rb
@@ -383,15 +425,15 @@ static void VM_FreeBuffers( void )
383425#define PPC_MR (ra , rs ) PPC_OR(ra, rs, rs)
384426
385427// andi. ra, rs, ui (always sets CR0)
386- #define PPC_ANDI (ra , rs , ui ) PPC_D(28, rs, ra, ui )
428+ #define PPC_ANDI (ra , rs , ui ) PPC_D(28, rs, ra, PPC_IMM_U16(ui, "ANDI") )
387429// ori ra, rs, ui
388- #define PPC_ORI (ra , rs , ui ) PPC_D(24, rs, ra, ui )
430+ #define PPC_ORI (ra , rs , ui ) PPC_D(24, rs, ra, PPC_IMM_U16(ui, "ORI") )
389431// oris ra, rs, ui
390- #define PPC_ORIS (ra , rs , ui ) PPC_D(25, rs, ra, ui )
432+ #define PPC_ORIS (ra , rs , ui ) PPC_D(25, rs, ra, PPC_IMM_U16(ui, "ORIS") )
391433// xori ra, rs, ui
392- #define PPC_XORI (ra , rs , ui ) PPC_D(26, rs, ra, ui )
434+ #define PPC_XORI (ra , rs , ui ) PPC_D(26, rs, ra, PPC_IMM_U16(ui, "XORI") )
393435// xoris ra, rs, ui
394- #define PPC_XORIS (ra , rs , ui ) PPC_D(27, rs, ra, ui )
436+ #define PPC_XORIS (ra , rs , ui ) PPC_D(27, rs, ra, PPC_IMM_U16(ui, "XORIS") )
395437// nop (ori 0, 0, 0)
396438#define PPC_NOP () PPC_ORI(R0, R0, 0)
397439
@@ -421,9 +463,9 @@ static void VM_FreeBuffers( void )
421463// -- Compare (D-form and X-form) --
422464// cmpwi cr, ra, si (signed word compare immediate)
423465// L=0 for 32-bit compare, cr field in bits 21-23
424- #define PPC_CMPWI (cr , ra , si ) PPC_D(11, ((cr)<<2), ra, si )
466+ #define PPC_CMPWI (cr , ra , si ) PPC_D(11, ((cr)<<2), ra, PPC_IMM_S16(si, "CMPWI") )
425467// cmplwi cr, ra, ui (unsigned word compare immediate)
426- #define PPC_CMPLWI (cr , ra , ui ) PPC_D(10, ((cr)<<2), ra, ui )
468+ #define PPC_CMPLWI (cr , ra , ui ) PPC_D(10, ((cr)<<2), ra, PPC_IMM_U16(ui, "CMPLWI") )
427469// cmpw cr, ra, rb (signed word compare)
428470#define PPC_CMPW (cr , ra , rb ) PPC_X(31, ((cr)<<2), ra, rb, 0, 0)
429471// cmplw cr, ra, rb (unsigned word compare)
@@ -1051,52 +1093,46 @@ static void __attribute__((__noreturn__)) ErrBadOpStack( void )
10511093}
10521094
10531095
1054- static void __attribute__((__noreturn__ )) ErrBadDataRead ( void )
1055- {
1056- Com_Error ( ERR_DROP , "program tried to read out of data segment" );
1057- }
1058-
1059-
1060- static void __attribute__((__noreturn__ )) ErrBadDataWrite ( void )
1096+ #ifndef NDEBUG
1097+ // Diagnostic: fired when emit_CheckReg sees a data-segment address whose
1098+ // 64-bit value exceeds dataMask. The JIT loads the offending QVM ip into
1099+ // R3 and the unmasked address into R4 before calling this stub, so we
1100+ // can identify the buggy op directly from the error message.
1101+ static void __attribute__((__noreturn__ )) ErrHighBitsSet ( int qvm_ip , uint64_t bad_addr )
10611102{
1062- Com_Error ( ERR_DROP , "program tried to write out of data segment" );
1103+ Com_Error ( ERR_DROP ,
1104+ "JIT: data address overflowed dataMask: ip=%d (0x%x) bad_addr=0x%llx" ,
1105+ qvm_ip , qvm_ip , (unsigned long long )bad_addr );
10631106}
1107+ #endif
10641108
10651109
10661110// =========================================================================
10671111// Runtime check emission
10681112// =========================================================================
10691113
1070- // Data access check: either mask the address or check bounds and call error
1071- // reg contains the address to check, it will be masked/checked in place
1072- static void emit_CheckReg ( vm_t * vm , int reg , offset_t func )
1114+ // Data access check: mask the address to fit within the data segment.
1115+ // reg holds the address; it is modified in place. This matches the
1116+ // interpreter's 32-bit wrap semantics and side-effect-clears any high
1117+ // bits left by a 64-bit ADD overflow (e.g. lwzx-loaded 0xFFFFFFFF +
1118+ // positive constant), which the prior cmplw/bgt check could not detect.
1119+ static void emit_CheckReg ( vm_t * vm , int reg , int qvm_ip )
10731120{
1074- int32_t offset ;
1075-
1076- if ( forceDataMask ) {
1077- emit ( PPC_AND ( reg , reg , rDATAMASK ) ); // reg = reg & rDATAMASK
1078- return ;
1079- }
1080-
1081- // QVM addresses are 32-bit. A preceding 64-bit ADD on PPC64 can leave
1082- // non-zero high bits in `reg` (e.g. lwzx-loaded 0xFFFFFFFF + positive
1083- // constant carries into bit 32). cmplw below only inspects the low 32
1084- // bits, but the indexed lbzx/lhzx/lwzx/stbx/sthx/stwx that follows
1085- // uses the full 64-bit register as index, so the bounds check would
1086- // pass while the load/store would address far outside the data
1087- // segment. Clear the high 32 bits so the check and the access agree.
1088- emit ( PPC_CLRLDI ( reg , reg , 32 ) );
1089-
1090- // compare and branch to error if out of range
1091- emit ( PPC_CMPLW ( 0 , reg , rDATAMASK ) );
1092-
1093- offset = branchOffset [ func ] - compiledOfs ;
1094- if ( (int16_t )offset == offset ) {
1095- emit ( PPC_BGT ( offset ) ); // unsigned >
1096- } else {
1097- emit ( PPC_BLE ( +8 ) ); // if reg <= dataMask, skip error (unsigned)
1098- emitFuncBranch ( vm , func );
1099- }
1121+ #ifndef NDEBUG
1122+ // Diagnostic trap: 64-bit unsigned compare reg against dataMask.
1123+ // On fail, hand ErrHighBitsSet(qvm_ip, bad_addr) via R3,R4 so the
1124+ // error message names the offending QVM op directly.
1125+ emit ( PPC_CMPLD ( 0 , reg , rDATAMASK ) );
1126+ emit ( PPC_BLE ( +20 ) ); // skip 4 insns
1127+ emit ( PPC_MR ( R4 , reg ) ); // R4 = bad addr (full 64-bit)
1128+ emit ( PPC_LIS ( R3 , ( qvm_ip >> 16 ) & 0xFFFF ) ); // R3 = ip hi
1129+ emit ( PPC_ORI ( R3 , R3 , qvm_ip & 0xFFFF ) ); // R3 |= ip lo
1130+ emitFuncBranch ( vm , FUNC_HIBITSET );
1131+ #else
1132+ (void )vm ;
1133+ (void )qvm_ip ;
1134+ #endif
1135+ emit ( PPC_AND ( reg , reg , rDATAMASK ) );
11001136}
11011137
11021138
@@ -1166,7 +1202,7 @@ static void emit_CheckProc( vm_t *vm, instruction_t *ins )
11661202 uint32_t n = ins -> opStack ;
11671203 mov_rx_imm32 ( R11 , n );
11681204 emit ( PPC_ADD ( R11 , rOPSTACK , R11 ) );
1169- emit ( PPC_CMPLD ( 0 , R11 , rOPSTACKTOP ) ); //
1205+ emit ( PPC_CMPLD ( 0 , R11 , rOPSTACKTOP ) ); //
11701206
11711207 offset = branchOffset [FUNC_PSOF ] - compiledOfs ;
11721208 if ( (int16_t )offset == offset ) {
@@ -1485,20 +1521,41 @@ static qboolean ConstOptimize( vm_t* vm, instruction_t* ci, instruction_t* ni )
14851521 case OP_SUB :
14861522 case OP_MULI :
14871523 case OP_MULU :
1488- case OP_BAND :
1489- case OP_BOR :
1490- case OP_BXOR :
1524+ // PPC ADDI/MULLI take a signed 16-bit immediate
14911525 if ( (int16_t )ci -> value != ci -> value )
14921526 return qfalse ;
1527+ // SUB folds via ADDI(-imm); guard against the -INT16_MIN overflow
1528+ if ( ni -> op == OP_SUB && ci -> value == INT16_MIN )
1529+ return qfalse ;
14931530 load_rx_opstack2 ( & rx [1 ], R4 , & rx [0 ], R3 ); // r4 = r3 = *opStack
14941531 switch ( ni -> op ) {
14951532 case OP_ADD : emit ( PPC_ADDI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
14961533 case OP_SUB : emit ( PPC_ADDI ( rx [1 ], rx [0 ], - ci -> value ) ); break ;
14971534 case OP_MULI :
14981535 case OP_MULU : emit ( PPC_MULLI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1499- case OP_BAND : emit ( PPC_ANDI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1500- case OP_BOR : emit ( PPC_ORI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1501- case OP_BXOR : emit ( PPC_XORI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1536+ };
1537+ if ( rx [0 ] != rx [1 ] ) {
1538+ unmask_rx ( rx [0 ] );
1539+ }
1540+ store_rx_opstack ( rx [1 ] ); // *opStack = r4
1541+ ip += 1 ;
1542+ return qtrue ;
1543+
1544+ case OP_BAND :
1545+ case OP_BOR :
1546+ case OP_BXOR :
1547+ // PPC ANDI/ORI/XORI take an UNSIGNED 16-bit immediate that is
1548+ // zero-extended to 32 bits. Folding a CONST whose upper 16 bits
1549+ // are non-zero (e.g. negative values like -4 for pointer
1550+ // alignment) would silently clobber the upper half of the
1551+ // operand. Fall back to the non-folded path in that case.
1552+ if ( ( (uint32_t )ci -> value & 0xFFFF0000u ) != 0 )
1553+ return qfalse ;
1554+ load_rx_opstack2 ( & rx [1 ], R4 , & rx [0 ], R3 ); // r4 = r3 = *opStack
1555+ switch ( ni -> op ) {
1556+ case OP_BAND : emit ( PPC_ANDI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1557+ case OP_BOR : emit ( PPC_ORI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
1558+ case OP_BXOR : emit ( PPC_XORI ( rx [1 ], rx [0 ], ci -> value ) ); break ;
15021559 };
15031560 if ( rx [0 ] != rx [1 ] ) {
15041561 unmask_rx ( rx [0 ] );
@@ -1588,8 +1645,24 @@ static qboolean ConstOptimize( vm_t* vm, instruction_t* ci, instruction_t* ni )
15881645 case OP_LEU :
15891646 case OP_GTU :
15901647 case OP_GEU :
1591- if ( (int16_t )ci -> value != ci -> value )
1592- return qfalse ;
1648+ // PPC CMPWI takes signed 16-bit (sign-extended); CMPLWI takes
1649+ // UNSIGNED 16-bit (zero-extended). For unsigned comparisons
1650+ // we must reject negative ci->value (= upper 16 bits set in
1651+ // the 32-bit form) to avoid silently comparing against the
1652+ // 16-bit truncated value.
1653+ switch ( ni -> op ) {
1654+ case OP_LTU :
1655+ case OP_LEU :
1656+ case OP_GTU :
1657+ case OP_GEU :
1658+ if ( ( (uint32_t )ci -> value & 0xFFFF0000u ) != 0 )
1659+ return qfalse ;
1660+ break ;
1661+ default :
1662+ if ( (int16_t )ci -> value != ci -> value )
1663+ return qfalse ;
1664+ break ;
1665+ }
15931666 rx [0 ] = load_rx_opstack ( R3 | RCONST ); dec_opstack (); // r3 = *opstack; opstack -= 4
15941667 switch ( ni -> op ) {
15951668 case OP_LTU :
@@ -1781,10 +1854,11 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
17811854 emitFuncBranch ( vm , FUNC_OUTJ );
17821855 emitFuncBranch ( vm , FUNC_BADJ );
17831856 }
1784- if ( vm_rtChecks -> integer & VM_RTCHECK_DATA && !vm -> forceDataMask ) {
1785- emitFuncBranch ( vm , FUNC_BADR );
1786- emitFuncBranch ( vm , FUNC_BADW );
1787- }
1857+ #ifndef NDEBUG
1858+ // FUNC_HIBITSET is emitted unconditionally in debug builds so that
1859+ // emit_CheckReg's diagnostic trap can reach it via a short bl.
1860+ emitFuncBranch ( vm , FUNC_HIBITSET );
1861+ #endif
17881862
17891863 saveBranchOffsets ();
17901864
@@ -2073,7 +2147,7 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
20732147 } else {
20742148 // address specified by a register
20752149 rx [0 ] = load_rx_opstack ( R4 ); // R4 = *opStack
2076- emit_CheckReg ( vm , rx [0 ], FUNC_BADR ); // check for ( R4 < dataMask)
2150+ emit_CheckReg ( vm , rx [0 ], ip ); // R4 &= dataMask
20772151 sx [0 ] = alloc_sx ( F0 );
20782152 emit ( PPC_LFSX ( sx [0 ], rx [0 ], rDATABASE ) ); // F0 = dataBase[R4]
20792153 store_sx_opstack ( sx [0 ] ); // *opStack = F0
@@ -2133,12 +2207,11 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
21332207 }
21342208 } else {
21352209 // address specified by a register
2136- if ( forceDataMask ) {
2137- rx [0 ] = rx [1 ] = load_rx_opstack ( R3 ); // target = address = *opStack
2138- } else {
2139- load_rx_opstack2 ( & rx [0 ], R3 , & rx [1 ], R4 ); // target, address (const) = *opStack
2140- }
2141- emit_CheckReg ( vm , rx [1 ], FUNC_BADR );
2210+ // emit_CheckReg masks the address in place, so allocate
2211+ // the address into a writable register (no RCONST) and
2212+ // reuse it as the load destination.
2213+ rx [0 ] = rx [1 ] = load_rx_opstack ( R3 ); // target = address = *opStack
2214+ emit_CheckReg ( vm , rx [1 ], ip ); // R3 &= dataMask
21422215 switch ( ci -> op ) {
21432216 case OP_LOAD1 : emit ( PPC_LBZX ( rx [0 ], rx [1 ], rDATABASE ) ); set_rx_ext ( rx [0 ], Z_EXT8 ); break ; // R3 = dataBase[R4] (byte)
21442217 case OP_LOAD2 : emit ( PPC_LHZX ( rx [0 ], rx [1 ], rDATABASE ) ); set_rx_ext ( rx [0 ], Z_EXT16 ); break ; // R3 = dataBase[R4] (halfword)
@@ -2175,9 +2248,9 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
21752248 set_sx_var ( sx [0 ], & var ); // update metadata
21762249 } else {
21772250 // address specified by register
2178- rx [0 ] = load_rx_opstack ( forceDataMask ? R4 : R4 | RCONST );
2179- dec_opstack (); // R4 = *opStack; opStack -= 4
2180- emit_CheckReg ( vm , rx [0 ], FUNC_BADW ); // check for ( R4 < dataMask) or R4 = R4 & dataMask
2251+ rx [0 ] = load_rx_opstack ( R4 ); // R4 = *opStack
2252+ dec_opstack (); // opStack -= 4
2253+ emit_CheckReg ( vm , rx [0 ], ip ); // R4 &= dataMask
21812254 emit ( PPC_STFSX ( sx [0 ], rx [0 ], rDATABASE ) ); // dataBase[R4] = F0
21822255 unmask_rx ( rx [0 ] );
21832256 wipe_vars (); // unknown/dynamic address, wipe all register mappings
@@ -2211,9 +2284,9 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
22112284 set_rx_var ( rx [0 ], & var ); // update metadata for memory
22122285 } else {
22132286 // address specified by register
2214- rx [1 ] = load_rx_opstack ( forceDataMask ? R4 : R4 | RCONST );
2215- dec_opstack (); // R4 = *opStack; opStack -= 4
2216- emit_CheckReg ( vm , rx [1 ], FUNC_BADW );
2287+ rx [1 ] = load_rx_opstack ( R4 ); // R4 = *opStack
2288+ dec_opstack (); // opStack -= 4
2289+ emit_CheckReg ( vm , rx [1 ], ip ); // R4 &= dataMask
22172290 switch ( ci -> op ) {
22182291 case OP_STORE1 : emit ( PPC_STBX ( rx [0 ], rx [1 ], rDATABASE ) ); break ; // (byte) dataBase[R4] = R3
22192292 case OP_STORE2 : emit ( PPC_STHX ( rx [0 ], rx [1 ], rDATABASE ) ); break ; // (short) dataBase[R4] = R3
@@ -2464,11 +2537,10 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header )
24642537 funcOffset [ FUNC_PSOF ] = compiledOfs ;
24652538 emitFuncEntry ( ErrBadProgramStack );
24662539
2467- funcOffset [ FUNC_BADR ] = compiledOfs ;
2468- emitFuncEntry ( ErrBadDataRead );
2469-
2470- funcOffset [ FUNC_BADW ] = compiledOfs ;
2471- emitFuncEntry ( ErrBadDataWrite );
2540+ #ifndef NDEBUG
2541+ funcOffset [ FUNC_HIBITSET ] = compiledOfs ;
2542+ emitFuncEntry ( ErrHighBitsSet );
2543+ #endif
24722544
24732545 } // pass
24742546
0 commit comments