Spaces:
Sleeping
Sleeping
| const fs = require("fs"); | |
| const path = require("path"); | |
| function loadJson(filePath) { | |
| return JSON.parse(fs.readFileSync(filePath, "utf8")); | |
| } | |
| function formatPct(part, total) { | |
| if (!total) { | |
| return "0.00%"; | |
| } | |
| return `${((part / total) * 100).toFixed(2)}%`; | |
| } | |
| function analyzeBytecodeLayout(db) { | |
| const zeroByWord = [0, 0, 0, 0, 0]; | |
| const nonZeroByWord = [0, 0, 0, 0, 0]; | |
| const profileCounts = { | |
| op_only: 0, | |
| op_v: 0, | |
| op_v_a: 0, | |
| op_v_s: 0, | |
| full: 0, | |
| }; | |
| let abilities = 0; | |
| let instructions = 0; | |
| let compactWords = 0; | |
| let instructionsWithHighAttr = 0; | |
| let totalBits = 0; | |
| let zeroBits = 0; | |
| const uniqueInstructions = new Map(); | |
| const opcodeHistogram = new Map(); | |
| for (const dbName of ["member_db", "live_db"]) { | |
| for (const card of Object.values(db[dbName] || {})) { | |
| for (const ability of card.abilities || []) { | |
| abilities += 1; | |
| const bytecode = ability.bytecode || []; | |
| for (let i = 0; i + 4 < bytecode.length; i += 5) { | |
| const [op, v, aLow, aHigh, s] = bytecode.slice(i, i + 5); | |
| instructions += 1; | |
| const key = `${op},${v},${aLow},${aHigh},${s}`; | |
| uniqueInstructions.set(key, (uniqueInstructions.get(key) || 0) + 1); | |
| opcodeHistogram.set(op, (opcodeHistogram.get(op) || 0) + 1); | |
| const hasV = v !== 0; | |
| const hasA = aLow !== 0 || aHigh !== 0; | |
| const hasS = s !== 0; | |
| if (aHigh !== 0) { | |
| instructionsWithHighAttr += 1; | |
| } | |
| [op, v, aLow, aHigh, s].forEach((value, idx) => { | |
| if (value === 0) { | |
| zeroByWord[idx] += 1; | |
| } else { | |
| nonZeroByWord[idx] += 1; | |
| } | |
| totalBits += 32; | |
| zeroBits += 32 - popcount32(value); | |
| }); | |
| if (!hasV && !hasA && !hasS) { | |
| profileCounts.op_only += 1; | |
| } else if (hasV && !hasA && !hasS) { | |
| profileCounts.op_v += 1; | |
| } else if (hasV && hasA && !hasS) { | |
| profileCounts.op_v_a += 1; | |
| } else if (hasV && !hasA && hasS) { | |
| profileCounts.op_v_s += 1; | |
| } else { | |
| profileCounts.full += 1; | |
| } | |
| // Compact estimate: | |
| // 1 word for opcode, optional V, 1-2 words for A, optional S. | |
| compactWords += 1; | |
| if (hasV) { | |
| compactWords += 1; | |
| } | |
| if (hasA) { | |
| compactWords += aHigh !== 0 ? 2 : 1; | |
| } | |
| if (hasS) { | |
| compactWords += 1; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| const fixedWords = instructions * 5; | |
| return { | |
| abilities, | |
| instructions, | |
| zeroByWord, | |
| nonZeroByWord, | |
| zeroBits, | |
| totalBits, | |
| profileCounts, | |
| fixedWords, | |
| compactWords, | |
| savedWords: fixedWords - compactWords, | |
| savedPct: fixedWords ? ((fixedWords - compactWords) / fixedWords) * 100 : 0, | |
| instructionsWithHighAttr, | |
| uniqueInstructionCount: uniqueInstructions.size, | |
| topInstructions: [...uniqueInstructions.entries()] | |
| .sort((a, b) => b[1] - a[1]) | |
| .slice(0, 15), | |
| topOpcodes: [...opcodeHistogram.entries()] | |
| .sort((a, b) => b[1] - a[1]) | |
| .slice(0, 20), | |
| }; | |
| } | |
| function popcount32(value) { | |
| let x = value >>> 0; | |
| let count = 0; | |
| while (x) { | |
| x &= x - 1; | |
| count += 1; | |
| } | |
| return count; | |
| } | |
| function main() { | |
| const root = path.resolve(__dirname, ".."); | |
| const jsonPath = path.join(root, "data", "cards_compiled.json"); | |
| const binPath = path.join(root, "data", "cards_compiled.bin"); | |
| const db = loadJson(jsonPath); | |
| const analysis = analyzeBytecodeLayout(db); | |
| const jsonSize = fs.statSync(jsonPath).size; | |
| const binSize = fs.existsSync(binPath) ? fs.statSync(binPath).size : null; | |
| console.log("Bytecode Layout Analysis"); | |
| console.log("======================="); | |
| console.log(`Abilities inspected: ${analysis.abilities}`); | |
| console.log(`Instructions: ${analysis.instructions}`); | |
| console.log(`JSON size: ${jsonSize} bytes`); | |
| if (binSize !== null) { | |
| console.log(`Binary snapshot: ${binSize} bytes`); | |
| } | |
| console.log(""); | |
| console.log("Word usage"); | |
| console.log(` OP non-zero: ${analysis.nonZeroByWord[0]} / ${analysis.instructions} (${formatPct(analysis.nonZeroByWord[0], analysis.instructions)})`); | |
| console.log(` V non-zero: ${analysis.nonZeroByWord[1]} / ${analysis.instructions} (${formatPct(analysis.nonZeroByWord[1], analysis.instructions)})`); | |
| console.log(` A_LOW non-zero: ${analysis.nonZeroByWord[2]} / ${analysis.instructions} (${formatPct(analysis.nonZeroByWord[2], analysis.instructions)})`); | |
| console.log(` A_HIGH non-zero: ${analysis.nonZeroByWord[3]} / ${analysis.instructions} (${formatPct(analysis.nonZeroByWord[3], analysis.instructions)})`); | |
| console.log(` S non-zero: ${analysis.nonZeroByWord[4]} / ${analysis.instructions} (${formatPct(analysis.nonZeroByWord[4], analysis.instructions)})`); | |
| console.log(` Zero bits: ${analysis.zeroBits} / ${analysis.totalBits} (${formatPct(analysis.zeroBits, analysis.totalBits)})`); | |
| console.log(""); | |
| console.log("Instruction profiles"); | |
| for (const [name, count] of Object.entries(analysis.profileCounts)) { | |
| console.log(` ${name.padEnd(12)} ${String(count).padStart(4)} (${formatPct(count, analysis.instructions)})`); | |
| } | |
| console.log(""); | |
| console.log("Compact estimate"); | |
| console.log(` Fixed words: ${analysis.fixedWords}`); | |
| console.log(` Compact words: ${analysis.compactWords}`); | |
| console.log(` Saved words: ${analysis.savedWords} (${analysis.savedPct.toFixed(2)}%)`); | |
| console.log(` Attr high used: ${analysis.instructionsWithHighAttr} instructions (${formatPct(analysis.instructionsWithHighAttr, analysis.instructions)})`); | |
| console.log(""); | |
| console.log("Reuse"); | |
| console.log(` Unique instructions: ${analysis.uniqueInstructionCount} / ${analysis.instructions} (${formatPct(analysis.uniqueInstructionCount, analysis.instructions)})`); | |
| console.log(" Top repeated instructions:"); | |
| for (const [instruction, count] of analysis.topInstructions) { | |
| console.log(` ${String(count).padStart(4)} ${instruction}`); | |
| } | |
| console.log(" Top opcodes:"); | |
| for (const [opcode, count] of analysis.topOpcodes) { | |
| console.log(` ${String(count).padStart(4)} ${opcode}`); | |
| } | |
| } | |
| main(); | |