diff --git a/src/abstract/BaseRainlangExtern.sol b/src/abstract/BaseRainlangExtern.sol index bad066f96..ce8e2d336 100644 --- a/src/abstract/BaseRainlangExtern.sol +++ b/src/abstract/BaseRainlangExtern.sol @@ -13,6 +13,7 @@ import { import {IIntegrityToolingV1} from "rain.sol.codegen/interface/IIntegrityToolingV1.sol"; import {IOpcodeToolingV1} from "rain.sol.codegen/interface/IOpcodeToolingV1.sol"; import {ExternOpcodeOutOfRange, ExternPointersMismatch, ExternOpcodePointersEmpty} from "../error/ErrExtern.sol"; +import {OPCODE_FUNCTION_POINTER_SHIFT} from "../lib/eval/LibEval.sol"; /// @dev Empty opcode function pointers constant. Inheriting contracts should /// create their own constant and override `opcodeFunctionPointers` to use @@ -73,7 +74,7 @@ abstract contract BaseRainlangExtern is IInterpreterExternV4, IIntegrityToolingV function(OperandV2, StackItem[] memory) internal view returns (StackItem[] memory) f; assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(opcode, fsCount), 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(mod(opcode, fsCount), 2)))) } outputs = f(operand, inputs); } @@ -102,7 +103,7 @@ abstract contract BaseRainlangExtern is IInterpreterExternV4, IIntegrityToolingV function(OperandV2, uint256, uint256) internal pure returns (uint256, uint256) f; assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(opcode, 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(opcode, 2)))) } (actualInputs, actualOutputs) = f(operand, expectedInputs, expectedOutputs); } diff --git a/src/lib/eval/LibEval.sol b/src/lib/eval/LibEval.sol index 55d3aa543..db7395789 100644 --- a/src/lib/eval/LibEval.sol +++ b/src/lib/eval/LibEval.sol @@ -12,6 +12,16 @@ import {OperandV2, StackItem} from "rain.interpreter.interface/interface/IInterp import {InputsLengthMismatch} from "../../error/ErrEval.sol"; +/// @dev Shift to extract a packed 2-byte function pointer from the high bits +/// of a 256-bit mload. `shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(...))` yields +/// the 16-bit pointer value. +uint256 constant OPCODE_FUNCTION_POINTER_SHIFT = 0xf0; + +/// @dev Shift to extract a packed 2-byte source offset from the bytecode +/// header. Same width as function pointers but semantically distinct — these +/// are relative offsets into the bytecode, not function pointers. +uint256 constant SOURCE_OFFSET_SHIFT = 0xf0; + library LibEval { using LibMemoryKV for MemoryKV; @@ -64,7 +74,7 @@ library LibEval { // Find start of sources. let sourcesStart := add(cursor, mul(sourcesLength, 2)) // Find relative pointer to source. - let sourcesPointer := shr(0xf0, mload(add(cursor, mul(sourceIndex, 2)))) + let sourcesPointer := shr(SOURCE_OFFSET_SHIFT, mload(add(cursor, mul(sourceIndex, 2)))) // Move cursor to start of source. cursor := add(sourcesStart, sourcesPointer) // Calculate the end. @@ -97,56 +107,71 @@ library LibEval { // f needs to be looked up from the fn pointers table. // operand is 3 bytes. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(0, word), fsCount), 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(mod(byte(0, word), fsCount), 2)))) operand := and(shr(0xe0, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [24, 27]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(4, word), fsCount), 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(mod(byte(4, word), fsCount), 2)))) operand := and(shr(0xc0, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [20, 23]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(8, word), fsCount), 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(mod(byte(8, word), fsCount), 2)))) operand := and(shr(0xa0, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [16, 19]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(12, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(12, word), fsCount), 2))) + ) operand := and(shr(0x80, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [12, 15]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(16, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(16, word), fsCount), 2))) + ) operand := and(shr(0x60, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [8, 11]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(20, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(20, word), fsCount), 2))) + ) operand := and(shr(0x40, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [4, 7]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(24, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(24, word), fsCount), 2))) + ) operand := and(shr(0x20, word), 0xFFFFFF) } stackTop = f(state, operand, stackTop); // Bytes [0, 3]. assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(28, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(28, word), fsCount), 2))) + ) operand := and(word, 0xFFFFFF) } stackTop = f(state, operand, stackTop); @@ -163,7 +188,10 @@ library LibEval { while (cursor < end) { assembly ("memory-safe") { word := mload(cursor) - f := shr(0xf0, mload(add(fPointersStart, mul(mod(byte(28, word), fsCount), 2)))) + f := shr( + OPCODE_FUNCTION_POINTER_SHIFT, + mload(add(fPointersStart, mul(mod(byte(28, word), fsCount), 2))) + ) // 3 bytes mask. operand := and(word, 0xFFFFFF) } diff --git a/src/lib/integrity/LibIntegrityCheck.sol b/src/lib/integrity/LibIntegrityCheck.sol index 0af4d9749..599ffe7c6 100644 --- a/src/lib/integrity/LibIntegrityCheck.sol +++ b/src/lib/integrity/LibIntegrityCheck.sol @@ -14,6 +14,7 @@ import { import {BadOpInputsLength, BadOpOutputsLength} from "rain.interpreter.interface/error/ErrIntegrity.sol"; import {LibBytecode} from "rain.interpreter.interface/lib/bytecode/LibBytecode.sol"; import {OperandV2} from "rain.interpreter.interface/interface/IInterpreterV4.sol"; +import {OPCODE_FUNCTION_POINTER_SHIFT} from "../eval/LibEval.sol"; /// @notice Tracks the state of the integrity check walk over a single source. /// @param stackIndex Current logical stack depth. Increases with opcode @@ -162,7 +163,7 @@ library LibIntegrityCheck { revert OpcodeOutOfRange(state.opIndex, opcodeIndex, fsCount); } assembly ("memory-safe") { - f := shr(0xf0, mload(add(fPointersStart, mul(opcodeIndex, 2)))) + f := shr(OPCODE_FUNCTION_POINTER_SHIFT, mload(add(fPointersStart, mul(opcodeIndex, 2)))) } (uint256 calcOpInputs, uint256 calcOpOutputs) = f(state, operand); if (calcOpInputs != bytecodeOpInputs) { diff --git a/src/lib/parse/LibParse.sol b/src/lib/parse/LibParse.sol index 2b3b6fce8..ef29d80d5 100644 --- a/src/lib/parse/LibParse.sol +++ b/src/lib/parse/LibParse.sol @@ -44,7 +44,8 @@ import { FSM_DEFAULT, FSM_ACTIVE_SOURCE_MASK, FSM_WORD_END_MASK, - PARSE_STATE_PAREN_TRACKER0_OFFSET + PARSE_STATE_PAREN_TRACKER0_OFFSET, + PAREN_POINTER_SHIFT } from "./LibParseState.sol"; import {LibParsePragma} from "./LibParsePragma.sol"; import {LibParseInterstitial} from "./LibParseInterstitial.sol"; @@ -380,7 +381,7 @@ library LibParse { // Add 1 to sandwich the inputs byte between // the opcode index byte and the operand low // bytes. - add(1, shr(0xf0, mload(add(add(stateOffset, 2), parenOffset)))), + add(1, shr(PAREN_POINTER_SHIFT, mload(add(add(stateOffset, 2), parenOffset)))), // Store the input counter, which is 2 bytes // after the operand write pointer. byte(0, mload(add(add(stateOffset, 4), parenOffset))) diff --git a/src/lib/parse/LibParseInterstitial.sol b/src/lib/parse/LibParseInterstitial.sol index f8a8cfcf6..47d07c631 100644 --- a/src/lib/parse/LibParseInterstitial.sol +++ b/src/lib/parse/LibParseInterstitial.sol @@ -14,6 +14,10 @@ import {MalformedCommentStart, UnclosedComment} from "../../error/ErrParse.sol"; import {LibParseError} from "./LibParseError.sol"; import {LibParseChar} from "rain.string/lib/parse/LibParseChar.sol"; +/// @dev Shift to extract a packed 2-byte comment delimiter sequence from the +/// high bits of a 256-bit mload. +uint256 constant COMMENT_SEQUENCE_SHIFT = 0xf0; + /// @title LibParseInterstitial /// @notice Handles whitespace and comment skipping between meaningful tokens /// during parsing. @@ -46,7 +50,7 @@ library LibParseInterstitial { // First check the comment opening sequence is not malformed. uint256 startSequence; assembly ("memory-safe") { - startSequence := shr(0xf0, mload(cursor)) + startSequence := shr(COMMENT_SEQUENCE_SHIFT, mload(cursor)) } if (startSequence != COMMENT_START_SEQUENCE) { revert MalformedCommentStart(state.parseErrorOffset(cursor)); @@ -68,7 +72,7 @@ library LibParseInterstitial { // Check the sequence. uint256 endSequence; assembly ("memory-safe") { - endSequence := shr(0xf0, mload(sub(cursor, 1))) + endSequence := shr(COMMENT_SEQUENCE_SHIFT, mload(sub(cursor, 1))) } if (endSequence == COMMENT_END_SEQUENCE) { // We found the end of the comment. diff --git a/src/lib/parse/LibParseState.sol b/src/lib/parse/LibParseState.sol index 6139479fa..d3204aea5 100644 --- a/src/lib/parse/LibParseState.sol +++ b/src/lib/parse/LibParseState.sol @@ -37,6 +37,14 @@ uint256 constant SUB_PARSER_POINTER_SHIFT = 0xA0; /// 0x20. uint256 constant EMPTY_ACTIVE_SOURCE = 0x20; +/// @dev Shift to extract a packed 2-byte active source pointer from the high +/// bits of a 256-bit mload during parse-time source traversal. +uint256 constant ACTIVE_SOURCE_POINTER_SHIFT = 0xf0; + +/// @dev Shift to extract a packed 2-byte paren tracking pointer from the high +/// bits of a 256-bit mload during operand processing. +uint256 constant PAREN_POINTER_SHIFT = 0xf0; + /// @dev Bit 0 of the FSM. When set, the parser is in "yang" state (building /// an RHS word). When clear, the parser is in "yin" state (between words). uint256 constant FSM_YANG_MASK = 1; @@ -496,7 +504,7 @@ library LibParseState { // is handled on allocation in `newActiveSourcePointer`. if (itemSourceHead % 0x20 == 0x1c) { assembly ("memory-safe") { - itemSourceHead := shr(0xf0, mload(itemSourceHead)) + itemSourceHead := shr(ACTIVE_SOURCE_POINTER_SHIFT, mload(itemSourceHead)) } } uint256 opInputs; @@ -1002,7 +1010,7 @@ library LibParseState { let relativePointer := and(mload(add(bytecode, add(3, mul(i, 2)))), 0xFFFF) targetPointer := add(sourcesStart, relativePointer) let tmpPrefix := mload(targetPointer) - sourcePointer := add(0x20, shr(0xf0, tmpPrefix)) + sourcePointer := add(0x20, shr(ACTIVE_SOURCE_POINTER_SHIFT, tmpPrefix)) length := and(shr(0xe0, tmpPrefix), 0xFFFF) } LibMemCpy.unsafeCopyBytesTo(sourcePointer, targetPointer, length); diff --git a/src/lib/state/LibInterpreterStateDataContract.sol b/src/lib/state/LibInterpreterStateDataContract.sol index 680be075e..74e75faca 100644 --- a/src/lib/state/LibInterpreterStateDataContract.sol +++ b/src/lib/state/LibInterpreterStateDataContract.sol @@ -10,6 +10,7 @@ import {FullyQualifiedNamespace} from "rain.interpreter.interface/interface/IInt import {IInterpreterStoreV3} from "rain.interpreter.interface/interface/IInterpreterStoreV3.sol"; import {InterpreterState} from "./LibInterpreterState.sol"; +import {SOURCE_OFFSET_SHIFT} from "../eval/LibEval.sol"; library LibInterpreterStateDataContract { using LibBytes for bytes; @@ -118,7 +119,7 @@ library LibInterpreterStateDataContract { } { // The stack size is in the prefix of the source data, which // is behind a relative pointer in the bytecode prefix. - let sourcePointer := add(sourcesStart, shr(0xf0, mload(cursor))) + let sourcePointer := add(sourcesStart, shr(SOURCE_OFFSET_SHIFT, mload(cursor))) // Stack size is the second byte of the source prefix. let stackSize := byte(1, mload(sourcePointer))