// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/console.sol"; /** * @title Push3Interpreter * @dev Demonstrates a minimal Push3-like interpreter that uses * a token-based bytecode approach. */ contract Push3Interpreter { // ----------------------------------------------------- // 0. CONSTANTS // ----------------------------------------------------- uint8 internal constant OPCODE_BOOL_OFFSET = 0x6; // ----------------------------------------------------- // 1. ENUMS // ----------------------------------------------------- enum CodeTag { NO_TAG, // 0 INSTRUCTION, // 1 INT_LITERAL, // 2 SUBLIST, // 3 BOOL_LITERAL // 4 } enum OpCode { NOOP, // 0 INTEGER_PLUS, // 1 INTEGER_MINUS, // 2 INTEGER_MULT, // 3 INTEGER_DUP, // 4 INTEGER_POP, // 5 BOOL_DUP, // OPCODE_BOOL_OFFSET + 0 BOOL_POP, // OPCODE_BOOL_OFFSET + 1 BOOL_SWAP, // OPCODE_BOOL_OFFSET + 2 BOOL_FLUSH, // OPCODE_BOOL_OFFSET + 3 BOOL_STACKDEPTH, // OPCODE_BOOL_OFFSET + 4 BOOL_NOT, // OPCODE_BOOL_OFFSET + 5 BOOL_AND, // OPCODE_BOOL_OFFSET + 6 BOOL_OR, // OPCODE_BOOL_OFFSET + 7 BOOL_EQ, // OPCODE_BOOL_OFFSET + 8 BOOL_FROMFLOAT, // OPCODE_BOOL_OFFSET + 9 BOOL_FROMINTEGER,// OPCODE_BOOL_OFFSET + 10 BOOL_ROT, // OPCODE_BOOL_OFFSET + 11 BOOL_SHOVE, // OPCODE_BOOL_OFFSET + 12 BOOL_YANK, // OPCODE_BOOL_OFFSET + 13 BOOL_YANKDUP, // OPCODE_BOOL_OFFSET + 14 BOOL_DEFINE, // OPCODE_BOOL_OFFSET + 15 BOOL_RAND // OPCODE_BOOL_OFFSET + 16 } // ----------------------------------------------------- // 2. DESCRIPTOR BIT LAYOUT // ----------------------------------------------------- // [255..248] : tag (8 bits) // [247..216] : offset (32 bits) (for SUBLIST) // [215..184] : length (32 bits) (for SUBLIST) // [183..0] : leftover bits (opcode or literal data) function getTag(uint256 desc) internal pure returns (CodeTag) { return CodeTag(uint8(desc >> 248)); } function getOffset(uint256 desc) internal pure returns (uint32) { return uint32(desc >> 216); } function getLength(uint256 desc) internal pure returns (uint32) { return uint32(desc >> 184); } function getLow184(uint256 desc) internal pure returns (uint256) { return desc & ((1 << 184) - 1); } // Build a descriptor function makeDescriptor( CodeTag tag, uint32 offset, uint32 length, uint256 low184 ) public pure returns (uint256) { require(low184 < (1 << 184), "low184 out of range"); return (uint256(uint8(tag)) << 248) | (uint256(offset) << 216) | (uint256(length) << 184) | low184; } function makeInstruction(OpCode op) internal pure returns (uint256) { return makeDescriptor(CodeTag.INSTRUCTION, 0, 0, uint256(uint8(op))); } function getOpCode(uint256 desc) internal pure returns (OpCode) { return OpCode(uint8(desc & 0xFF)); } function makeIntLiteral(int32 val) internal pure returns (uint256) { // store 32-bit integer in leftover uint256 masked = uint256(uint32(val)); return makeDescriptor(CodeTag.INT_LITERAL, 0, 0, masked); } function extractInt32(uint256 desc) internal pure returns (int32) { // read the low 32 bits return int32(uint32(getLow184(desc))); } // ----------------------------------------------------- // 3. HELPER READS // ----------------------------------------------------- /** * @dev Read 4 bytes from `code` at `start` as uint32. */ function readUint32(bytes calldata code, uint32 start) internal pure returns (uint32 val) { require(start + 4 <= code.length, "readUint32 out of range"); uint256 word; assembly { let buf := mload(0x40) // free memory // copy exactly 4 bytes from code into buf calldatacopy(buf, add(code.offset, start), 4) word := mload(buf) } val = uint32(word >> 224); } /** * @dev Read 2 bytes from `code` at `start` as uint16. */ function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) { require(start + 2 <= code.length, "readUint16 out of range"); uint256 word; assembly { let buf := mload(0x40) calldatacopy(buf, add(code.offset, start), 2) word := mload(buf) val := and(word, 0xffff) } // Not sure this is correct val = uint16(word >> 240); } // ----------------------------------------------------- // 4. SUBLIST PARSING // ----------------------------------------------------- /** * Token format: * 0x00 => NOOP * 0x01 => INTEGER_PLUS * 0x02 => INT_LITERAL => read next 4 bytes => int32 * 0x03 => SUBLIST => read next 2 bytes => subLen => parse that * 0x04 => INTEGER_MINUS */ function parseSublist(bytes calldata code, uint32 off, uint32 len) internal pure returns (uint256[] memory descs) { uint256[] memory temp = new uint256[](len); uint256 count = 0; uint32 endPos = off + len; uint32 cur = off; while (cur < endPos) { if (cur >= code.length) { break; } uint8 tokenType = uint8(code[cur]); cur++; if (tokenType == 0x00) { // NOOP temp[count] = makeInstruction(OpCode.NOOP); count++; } else if (tokenType == 0x01) { // INTEGER_PLUS temp[count] = makeInstruction(OpCode.INTEGER_PLUS); count++; } else if (tokenType == 0x02) { // INT_LITERAL => 4 bytes if (cur + 4 <= endPos && (cur + 4) <= code.length) { uint32 raw = readUint32(code, cur); cur += 4; int32 val = int32(raw); temp[count] = makeIntLiteral(val); count++; } else { break; } } else if (tokenType == 0x03) { // SUBLIST => read 2 bytes => length if (cur + 2 <= endPos && (cur + 2) <= code.length) { uint16 subLen = readUint16(code, cur); cur += 2; if (cur + subLen <= endPos && (cur + subLen) <= code.length) { // create descriptor temp[count] = makeDescriptor(CodeTag.SUBLIST, cur, subLen, 0); count++; // skip over that subLen chunk cur += subLen; } else { break; } } else { break; } } else if (tokenType == 0x04) { // INTEGER_PLUS temp[count] = makeInstruction(OpCode.INTEGER_MINUS); count++; } else if (tokenType == 0x05) { // INTEGER_PLUS temp[count] = makeInstruction(OpCode.INTEGER_MULT); count++; } else if (tokenType == 0x06) { temp[count] = makeInstruction(OpCode.INTEGER_DUP); count++; } else if (tokenType == 0x07) { temp[count] = makeInstruction(OpCode.INTEGER_POP); count++; } else { // unknown => NOOP temp[count] = makeInstruction(OpCode.NOOP); count++; } } descs = new uint256[](count); for (uint256 i = 0; i < count; i++) { descs[i] = temp[i]; } } // ----------------------------------------------------- // 5. MAIN INTERPRETER // ----------------------------------------------------- function runInterpreter( bytes calldata code, uint256[] calldata initCodeStack, uint256[] calldata initExecStack, int256[] calldata initIntStack ) external pure returns ( uint256[] memory finalCodeStack, uint256[] memory finalExecStack, int256[] memory finalIntStack ) { // A) CODE STACK uint256[] memory codeStack = new uint256[](initCodeStack.length + 256); uint256 codeTop = initCodeStack.length; for (uint256 i = 0; i < initCodeStack.length; i++) { codeStack[i] = initCodeStack[i]; } // B) EXEC STACK uint256[] memory execStack = new uint256[](initExecStack.length + 256); uint256 execTop = initExecStack.length; for (uint256 e = 0; e < initExecStack.length; e++) { execStack[e] = initExecStack[e]; } // C) INT STACK int256[] memory intStack = new int256[](initIntStack.length + 256); uint256 intTop = initIntStack.length; for (uint256 k = 0; k < initIntStack.length; k++) { intStack[k] = initIntStack[k]; } // D) MAIN LOOP while (execTop > 0) { uint256 topDesc = execStack[execTop - 1]; execTop--; CodeTag tag = getTag(topDesc); if (tag == CodeTag.INSTRUCTION) { OpCode op = getOpCode(topDesc); if (op == OpCode.NOOP) { // do nothing } else if (op == OpCode.INTEGER_PLUS) { // pop top 2 => sum if (intTop >= 2) { int256 a = intStack[intTop - 1]; int256 b = intStack[intTop - 2]; intTop -= 2; intStack[intTop] = b + a; intTop++; } } else if (op == OpCode.INTEGER_MINUS) { // pop top 2 => minus if (intTop >= 2) { int256 a = intStack[intTop - 1]; int256 b = intStack[intTop - 2]; intTop -= 2; intStack[intTop] = b - a; intTop++; } } else if (op == OpCode.INTEGER_MULT) { // pop top 2 => minus if (intTop >= 2) { int256 a = intStack[intTop - 1]; int256 b = intStack[intTop - 2]; intTop -= 2; intStack[intTop] = b * a; intTop++; } } else if (op == OpCode.INTEGER_DUP) { if (intTop >= 1) { int256 a = intStack[intTop - 1]; intStack[intTop] = a; intTop++; } } else if (op == OpCode.INTEGER_POP) { if (intTop >= 1) { intTop -= 1; } } } else if (tag == CodeTag.INT_LITERAL) { int32 val = extractInt32(topDesc); intStack[intTop] = int256(val); intTop++; } else if (tag == CodeTag.SUBLIST) { // parse sublist => push in reverse uint32 off = getOffset(topDesc); uint32 len = getLength(topDesc); if (off + len <= code.length) { uint256[] memory parsed = parseSublist(code, off, len); for (uint256 p = 0; p < parsed.length; p++) { execStack[execTop] = parsed[parsed.length - 1 - p]; execTop++; } } } else { // NO_TAG => do nothing } } // E) RETURN finalCodeStack = new uint256[](codeTop); for (uint256 i = 0; i < codeTop; i++) { finalCodeStack[i] = codeStack[i]; } finalExecStack = new uint256[](execTop); for (uint256 i = 0; i < execTop; i++) { finalExecStack[i] = execStack[i]; } finalIntStack = new int256[](intTop); for (uint256 i = 0; i < intTop; i++) { finalIntStack[i] = intStack[i]; } } }