interpreter-expansion #2
@@ -6,8 +6,7 @@ import "forge-std/console.sol";
|
|||||||
/**
|
/**
|
||||||
* @title Push3Interpreter
|
* @title Push3Interpreter
|
||||||
* @dev Demonstrates a minimal Push3-like interpreter that uses
|
* @dev Demonstrates a minimal Push3-like interpreter that uses
|
||||||
* a token-based bytecode approach. We fix the assembly
|
* a token-based bytecode approach.
|
||||||
* endianness issues by copying exact bytes for 2- and 4-byte reads.
|
|
||||||
*/
|
*/
|
||||||
contract Push3Interpreter {
|
contract Push3Interpreter {
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
@@ -22,7 +21,9 @@ contract Push3Interpreter {
|
|||||||
|
|
||||||
enum OpCode {
|
enum OpCode {
|
||||||
NOOP, // 0
|
NOOP, // 0
|
||||||
INTEGER_PLUS // 1
|
INTEGER_PLUS, // 1
|
||||||
|
INTEGER_MINUS, // 2
|
||||||
|
INTEGER_MULT // 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
@@ -111,12 +112,15 @@ contract Push3Interpreter {
|
|||||||
*/
|
*/
|
||||||
function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) {
|
function readUint16(bytes calldata code, uint32 start) internal pure returns (uint16 val) {
|
||||||
require(start + 2 <= code.length, "readUint16 out of range");
|
require(start + 2 <= code.length, "readUint16 out of range");
|
||||||
|
uint256 word;
|
||||||
assembly {
|
assembly {
|
||||||
let buf := mload(0x40)
|
let buf := mload(0x40)
|
||||||
calldatacopy(buf, add(code.offset, start), 2)
|
calldatacopy(buf, add(code.offset, start), 2)
|
||||||
let word := mload(buf)
|
word := mload(buf)
|
||||||
val := and(word, 0xffff)
|
val := and(word, 0xffff)
|
||||||
}
|
}
|
||||||
|
// Not sure this is correct
|
||||||
|
val = uint16(word >> 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
@@ -128,6 +132,7 @@ contract Push3Interpreter {
|
|||||||
* 0x01 => INTEGER_PLUS
|
* 0x01 => INTEGER_PLUS
|
||||||
* 0x02 => INT_LITERAL => read next 4 bytes => int32
|
* 0x02 => INT_LITERAL => read next 4 bytes => int32
|
||||||
* 0x03 => SUBLIST => read next 2 bytes => subLen => parse that
|
* 0x03 => SUBLIST => read next 2 bytes => subLen => parse that
|
||||||
|
* 0x04 => INTEGER_MINUS
|
||||||
*/
|
*/
|
||||||
function parseSublist(bytes calldata code, uint32 off, uint32 len)
|
function parseSublist(bytes calldata code, uint32 off, uint32 len)
|
||||||
internal
|
internal
|
||||||
@@ -187,6 +192,16 @@ contract Push3Interpreter {
|
|||||||
break;
|
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 {
|
else {
|
||||||
// unknown => NOOP
|
// unknown => NOOP
|
||||||
temp[count] = makeInstruction(OpCode.NOOP);
|
temp[count] = makeInstruction(OpCode.NOOP);
|
||||||
@@ -260,6 +275,26 @@ contract Push3Interpreter {
|
|||||||
intTop++;
|
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 (tag == CodeTag.INT_LITERAL) {
|
else if (tag == CodeTag.INT_LITERAL) {
|
||||||
int32 val = extractInt32(topDesc);
|
int32 val = extractInt32(topDesc);
|
||||||
|
|||||||
+104
-3
@@ -2,6 +2,7 @@
|
|||||||
pragma solidity ^0.8.20;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
import {Test} from "forge-std/Test.sol";
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
import "forge-std/console.sol";
|
||||||
import {Push3Interpreter} from "../src/Push3Interpreter.sol";
|
import {Push3Interpreter} from "../src/Push3Interpreter.sol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +33,7 @@ contract Push3InterpreterTest is Test {
|
|||||||
* So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes
|
* So total = 1 + 2 + (1+4 + 1+4 + 1) = 14 bytes
|
||||||
* => 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
|
* => 0x03 00 0B 02 00 00 00 0A 02 00 00 00 20 01
|
||||||
*/
|
*/
|
||||||
function test_LiteralsAndPlus() public {
|
function test_LiteralsAndPlus() public view {
|
||||||
// The final code array (14 bytes):
|
// The final code array (14 bytes):
|
||||||
// [0] = 0x03
|
// [0] = 0x03
|
||||||
// [1..2] = 0x000B
|
// [1..2] = 0x000B
|
||||||
@@ -63,8 +64,8 @@ contract Push3InterpreterTest is Test {
|
|||||||
|
|
||||||
// Run it
|
// Run it
|
||||||
(
|
(
|
||||||
uint256[] memory finalCodeStack,
|
,
|
||||||
uint256[] memory finalExecStack,
|
,
|
||||||
int256[] memory finalIntStack
|
int256[] memory finalIntStack
|
||||||
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
|
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
|
||||||
|
|
||||||
@@ -72,4 +73,104 @@ contract Push3InterpreterTest is Test {
|
|||||||
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
|
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
|
||||||
assertEq(finalIntStack[0], 42, "Should be 10+32=42");
|
assertEq(finalIntStack[0], 42, "Should be 10+32=42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Test pushing literal 10, literal 3, then INTEGER_MINUS => (10 - 3) = 7
|
||||||
|
*
|
||||||
|
* The plan:
|
||||||
|
* 0x03 => SUBLIST token
|
||||||
|
* 0x00 0x0B => subLen=11
|
||||||
|
*
|
||||||
|
* Then 11 bytes:
|
||||||
|
* 0x02 + (00 00 00 0A) => INT_LITERAL(10)
|
||||||
|
* 0x02 + (00 00 00 03) => INT_LITERAL(3)
|
||||||
|
* 0x04 => INTEGER_MINUS (assuming 0x04 is assigned to minus)
|
||||||
|
*/
|
||||||
|
function test_Minus() public view {
|
||||||
|
// We'll produce 14 total bytes, same structure as the plus example
|
||||||
|
// Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x04 => minus]
|
||||||
|
|
||||||
|
bytes memory code = hex"03000B020000000A020000000304";
|
||||||
|
// Breaking it down:
|
||||||
|
// 0x03 => SUBLIST
|
||||||
|
// 0x00 0x0B => length=11
|
||||||
|
// 0x02 0000000A => INT_LITERAL(10)
|
||||||
|
// 0x02 00000003 => INT_LITERAL(3)
|
||||||
|
// 0x04 => INTEGER_MINUS
|
||||||
|
|
||||||
|
// We'll parse offset=0, length=14
|
||||||
|
uint256 sublistDesc = interpreter.makeDescriptor(
|
||||||
|
Push3Interpreter.CodeTag.SUBLIST,
|
||||||
|
0, // offset
|
||||||
|
14, // length
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
uint256[] memory initCodeStack = new uint256[](0);
|
||||||
|
uint256[] memory initExecStack = new uint256[](1);
|
||||||
|
initExecStack[0] = sublistDesc;
|
||||||
|
|
||||||
|
int256[] memory initIntStack = new int256[](0);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
(
|
||||||
|
,
|
||||||
|
,
|
||||||
|
int256[] memory finalIntStack
|
||||||
|
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
|
||||||
|
|
||||||
|
// Check final result => should be [7]
|
||||||
|
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
|
||||||
|
assertEq(finalIntStack[0], 7, "Should be 10 - 3 = 7");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Test pushing literal 10, literal 3, then INTEGER_MULT => (10 * 3) = 30
|
||||||
|
*
|
||||||
|
* The plan:
|
||||||
|
* 0x03 => SUBLIST token
|
||||||
|
* 0x00 0x0B => subLen=11
|
||||||
|
*
|
||||||
|
* Then 11 bytes:
|
||||||
|
* 0x02 + (00 00 00 0A) => INT_LITERAL(10)
|
||||||
|
* 0x02 + (00 00 00 03) => INT_LITERAL(3)
|
||||||
|
* 0x05 => INTEGER_MULT
|
||||||
|
*/
|
||||||
|
function test_Mult() public view {
|
||||||
|
// We'll produce 14 total bytes, same structure as the plus example
|
||||||
|
// Format: [0x03, 0x000B, 0x02(10), 0x03(3), 0x05 => mul]
|
||||||
|
|
||||||
|
bytes memory code = hex"03000B020000000A020000000305";
|
||||||
|
// Breaking it down:
|
||||||
|
// 0x03 => SUBLIST
|
||||||
|
// 0x00 0x0B => length=11
|
||||||
|
// 0x02 0000000A => INT_LITERAL(10)
|
||||||
|
// 0x02 00000003 => INT_LITERAL(3)
|
||||||
|
// 0x05 => INTEGER_MUL
|
||||||
|
|
||||||
|
// We'll parse offset=0, length=14
|
||||||
|
uint256 sublistDesc = interpreter.makeDescriptor(
|
||||||
|
Push3Interpreter.CodeTag.SUBLIST,
|
||||||
|
0, // offset
|
||||||
|
14, // length
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
uint256[] memory initCodeStack = new uint256[](0);
|
||||||
|
uint256[] memory initExecStack = new uint256[](1);
|
||||||
|
initExecStack[0] = sublistDesc;
|
||||||
|
|
||||||
|
int256[] memory initIntStack = new int256[](0);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
(
|
||||||
|
,
|
||||||
|
,
|
||||||
|
int256[] memory finalIntStack
|
||||||
|
) = interpreter.runInterpreter(code, initCodeStack, initExecStack, initIntStack);
|
||||||
|
|
||||||
|
// Check final result => should be [30]
|
||||||
|
assertEq(finalIntStack.length, 1, "finalIntStack should have length 1");
|
||||||
|
assertEq(finalIntStack[0], 30, "Should be 10 * 3 = 30");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user