interpreter-expansion #2

Merged
giteadmin merged 9 commits from interpreter-expansion into scaffolding 2024-12-24 12:02:08 +00:00
2 changed files with 144 additions and 8 deletions
Showing only changes of commit 92e353dcda - Show all commits
+39 -4
View File
@@ -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
View File
@@ -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");
}
} }