@@ -255,6 +255,216 @@ assert(resultByte == ${expectedResult});`;
255255 container . appendChild ( cppSection ) ;
256256}
257257
258+ /**
259+ * The matrix RPCS3 uses to decode SHUFB's special-case control bytes.
260+ * Byte 0 (the row for output bit 7) routes control bit 6 to the sign bit,
261+ * bytes 1-7 (rows for output bits 6..0) route control bit 5 everywhere else.
262+ */
263+ export const SHUFB_GFNI_MATRIX = [ 0x40 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 ] ;
264+
265+ /**
266+ * Create a visualization of RPCS3's SHUFB special-case fast path:
267+ * one GF2P8AFFINEQB over the SHUFB control byte, followed by two blends.
268+ * @param {HTMLElement } container - The container element to render into
269+ * @param {{control: number, matrix: Uint8Array, imm: number} } state - Mutable simulator state
270+ */
271+ export function createShufbVisualization ( container , state ) {
272+ const previousDetails = container . querySelector ( 'details' ) ;
273+ const wasOpen = previousDetails ? previousDetails . open : false ;
274+
275+ container . innerHTML = '' ;
276+
277+ const rerender = ( ) => createShufbVisualization ( container , state ) ;
278+
279+ let matrixQword = 0n ;
280+ for ( let i = 0 ; i < 8 ; i ++ ) {
281+ matrixQword |= BigInt ( state . matrix [ i ] ) << BigInt ( i * 8 ) ;
282+ }
283+
284+ const gfni = affineByte ( matrixQword , state . control , state . imm ) ;
285+ const idxConst = ( gfni & 0x80 ) ? gfni : 0x00 ;
286+ const controlIsSpecial = ( state . control & 0x80 ) !== 0 ;
287+
288+ const hex = ( x ) => '0x' + x . toString ( 16 ) . padStart ( 2 , '0' ) . toUpperCase ( ) ;
289+ const bin = ( x ) => '0b' + x . toString ( 2 ) . padStart ( 8 , '0' ) ;
290+
291+ // Main visualization: matrix × control + imm = gfni
292+ const vizContainer = document . createElement ( 'div' ) ;
293+ vizContainer . classList . add ( 'gf2p8-viz-container' ) ;
294+
295+ // Matrix grid
296+ const matrixGrid = document . createElement ( 'div' ) ;
297+ matrixGrid . classList . add ( 'gf2p8-matrix-grid' ) ;
298+ for ( let row = 7 ; row >= 0 ; row -- ) {
299+ for ( let col = 7 ; col >= 0 ; col -- ) {
300+ const byteIndex = ( 7 - row ) ;
301+ const bit = ( state . matrix [ byteIndex ] >> col ) & 1 ;
302+ const cell = document . createElement ( 'div' ) ;
303+ cell . textContent = bit ;
304+ cell . classList . add ( 'gf2p8-cell' , 'interactive' , bit ? 'one' : 'zero' ) ;
305+ cell . addEventListener ( 'click' , ( ) => {
306+ state . matrix [ byteIndex ] ^= ( 1 << col ) ;
307+ rerender ( ) ;
308+ } ) ;
309+ matrixGrid . appendChild ( cell ) ;
310+ }
311+ }
312+ vizContainer . appendChild ( matrixGrid ) ;
313+
314+ const xLabel = document . createElement ( 'div' ) ;
315+ xLabel . textContent = '×' ;
316+ xLabel . classList . add ( 'gf2p8-operator' ) ;
317+ vizContainer . appendChild ( xLabel ) ;
318+
319+ // Control byte vector
320+ const controlGrid = document . createElement ( 'div' ) ;
321+ controlGrid . classList . add ( 'gf2p8-vector-grid' ) ;
322+ for ( let bit = 7 ; bit >= 0 ; bit -- ) {
323+ const bitValue = ( state . control >> bit ) & 1 ;
324+ const cell = document . createElement ( 'div' ) ;
325+ cell . textContent = bitValue ;
326+ cell . classList . add ( 'gf2p8-cell' , 'interactive' , bitValue ? 'one' : 'zero' ) ;
327+ cell . addEventListener ( 'click' , ( ) => {
328+ state . control ^= ( 1 << bit ) ;
329+ rerender ( ) ;
330+ } ) ;
331+ controlGrid . appendChild ( cell ) ;
332+ }
333+ vizContainer . appendChild ( controlGrid ) ;
334+
335+ const xorLabel = document . createElement ( 'div' ) ;
336+ xorLabel . textContent = '+' ;
337+ xorLabel . classList . add ( 'gf2p8-operator' ) ;
338+ vizContainer . appendChild ( xorLabel ) ;
339+
340+ // Immediate constant vector
341+ const immGrid = document . createElement ( 'div' ) ;
342+ immGrid . classList . add ( 'gf2p8-vector-grid' ) ;
343+ for ( let bit = 7 ; bit >= 0 ; bit -- ) {
344+ const bitValue = ( state . imm >> bit ) & 1 ;
345+ const cell = document . createElement ( 'div' ) ;
346+ cell . textContent = bitValue ;
347+ cell . classList . add ( 'gf2p8-cell' , 'interactive' , bitValue ? 'one' : 'zero' ) ;
348+ cell . addEventListener ( 'click' , ( ) => {
349+ state . imm ^= ( 1 << bit ) ;
350+ rerender ( ) ;
351+ } ) ;
352+ immGrid . appendChild ( cell ) ;
353+ }
354+ vizContainer . appendChild ( immGrid ) ;
355+
356+ const equalsLabel = document . createElement ( 'div' ) ;
357+ equalsLabel . textContent = '=' ;
358+ equalsLabel . classList . add ( 'gf2p8-operator' ) ;
359+ vizContainer . appendChild ( equalsLabel ) ;
360+
361+ // GFNI result with annotations
362+ const resultGrid = document . createElement ( 'div' ) ;
363+ resultGrid . classList . add ( 'gf2p8-result-grid' ) ;
364+ for ( let bit = 7 ; bit >= 0 ; bit -- ) {
365+ const bitValue = ( gfni >> bit ) & 1 ;
366+ const cell = document . createElement ( 'div' ) ;
367+ cell . textContent = bitValue ;
368+ cell . classList . add ( 'gf2p8-cell' , bitValue ? 'result-one' : 'zero' ) ;
369+ resultGrid . appendChild ( cell ) ;
370+
371+ const annotation = document . createElement ( 'div' ) ;
372+ annotation . classList . add ( 'gf2p8-annotation' ) ;
373+ const matrixByte = state . matrix [ 7 - bit ] ;
374+ const sourceBits = [ ] ;
375+ for ( let i = 7 ; i >= 0 ; i -- ) {
376+ if ( ( matrixByte >> i ) & 1 ) {
377+ sourceBits . push ( `control bit ${ i } ` ) ;
378+ }
379+ }
380+ const immBit = ( state . imm >> bit ) & 1 ;
381+ if ( sourceBits . length === 0 ) {
382+ annotation . textContent = immBit ? '1' : '0' ;
383+ } else if ( immBit ) {
384+ annotation . textContent = `¬(${ sourceBits . join ( ' + ' ) } )` ;
385+ } else {
386+ annotation . textContent = sourceBits . join ( ' + ' ) ;
387+ }
388+ resultGrid . appendChild ( annotation ) ;
389+ }
390+ vizContainer . appendChild ( resultGrid ) ;
391+
392+ container . appendChild ( vizContainer ) ;
393+
394+ // Blend fixup steps
395+ const blendSection = document . createElement ( 'div' ) ;
396+ blendSection . classList . add ( 'gf2p8-blend-section' ) ;
397+
398+ const step1 = document . createElement ( 'p' ) ;
399+ step1 . innerHTML =
400+ `<b>Blend 1</b> — force <code>0x00</code> when the affine result's sign bit is clear: ` +
401+ `<code>gfni = ${ bin ( gfni ) } </code>, sign bit ${ ( gfni & 0x80 ) ? 'set, kept as' : 'clear, forced to' } ` +
402+ `<code>${ hex ( idxConst ) } </code>` ;
403+ blendSection . appendChild ( step1 ) ;
404+
405+ const step2 = document . createElement ( 'p' ) ;
406+ if ( controlIsSpecial ) {
407+ step2 . innerHTML =
408+ `<b>Blend 2</b> — the control byte's own sign bit is set, so the special-case constant is selected: ` +
409+ `result byte = <code>${ hex ( idxConst ) } </code>` ;
410+ } else {
411+ step2 . innerHTML =
412+ `<b>Blend 2</b> — the control byte's own sign bit is clear, so the shuffle result is selected: ` +
413+ `result byte = byte ${ state . control & 0x1F } of RA‖RB (the affine result is discarded)` ;
414+ }
415+ blendSection . appendChild ( step2 ) ;
416+
417+ const verdict = document . createElement ( 'p' ) ;
418+ let expected ;
419+ if ( ! controlIsSpecial ) {
420+ expected = `byte ${ state . control & 0x1F } of RA‖RB` ;
421+ } else if ( ( state . control & 0xC0 ) === 0x80 ) {
422+ expected = '0x00' ;
423+ } else if ( ( state . control & 0xE0 ) === 0xC0 ) {
424+ expected = '0xFF' ;
425+ } else {
426+ expected = '0x80' ;
427+ }
428+ const actual = controlIsSpecial ? hex ( idxConst ) : `byte ${ state . control & 0x1F } of RA‖RB` ;
429+ const matches = actual === expected ;
430+ verdict . innerHTML =
431+ `Control byte <code>${ bin ( state . control ) } </code>: <i>Table 5-1</i> says <code>${ expected } </code>, ` +
432+ `this path produces <code>${ actual } </code> ${ matches ? '✓' : '✗ (you broke it — reset the matrix to 0x2020202020202040 and imm8 to 0x7F)' } ` ;
433+ blendSection . appendChild ( verdict ) ;
434+
435+ container . appendChild ( blendSection ) ;
436+
437+ // C++ code example
438+ const cppSection = document . createElement ( 'details' ) ;
439+ cppSection . classList . add ( 'gf2p8-cpp-section' ) ;
440+
441+ const cppSummary = document . createElement ( 'summary' ) ;
442+ cppSummary . textContent = 'C++ implementation' ;
443+ cppSummary . classList . add ( 'gf2p8-cpp-summary' ) ;
444+ cppSection . appendChild ( cppSummary ) ;
445+
446+ const cppCode = document . createElement ( 'pre' ) ;
447+ cppCode . classList . add ( 'gf2p8-cpp-code' ) ;
448+ const codeElement = document . createElement ( 'code' ) ;
449+ codeElement . textContent = `// ${ '0x' + matrixQword . toString ( 16 ) . padStart ( 16 , '0' ) }
450+ const __m128i k_matrix = _mm_set1_epi64x(0x${ matrixQword . toString ( 16 ) . padStart ( 16 , '0' ) } );
451+
452+ // c holds the 16 SHUFB control bytes, shuffled holds the bytes already
453+ // gathered from RA||RB (e.g. via VPERM2B)
454+ const __m128i gfni = _mm_gf2p8affine_epi64_epi8(c, k_matrix, ${ hex ( state . imm ) } );
455+
456+ // Blend 1: where gfni's sign bit is clear, force 0x00 (fixes up 10xxxxxx)
457+ const __m128i idx_consts = _mm_blendv_epi8(_mm_setzero_si128(), gfni, gfni);
458+
459+ // Blend 2: where the control byte's sign bit is set, take the special
460+ // constant; otherwise take the shuffled data
461+ const __m128i result = _mm_blendv_epi8(shuffled, idx_consts, c);` ;
462+ cppCode . appendChild ( codeElement ) ;
463+ cppSection . appendChild ( cppCode ) ;
464+ cppSection . open = wasOpen ;
465+ container . appendChild ( cppSection ) ;
466+ }
467+
258468/**
259469 * Create a matrix from bytes for visualization
260470 * @param {number[] } bytes - 8 bytes representing the matrix rows
0 commit comments