Skip to content

Commit 608e44b

Browse files
authored
Merge pull request #398 from runlevel5/refactoring
ppc64-QVM: data-check refactor + immediate signedness fixes
2 parents 7ec0607 + 0c6e7df commit 608e44b

1 file changed

Lines changed: 155 additions & 83 deletions

File tree

code/qcommon/vm_powerpc.c

Lines changed: 155 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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 ];
206211
static uint32_t branchOffset[ OFFSET_T_LAST ];
207212
static uint32_t savedBranchOffset[ OFFSET_T_LAST ];
208213

209-
static qboolean forceDataMask;
210214

211215
static 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

Comments
 (0)