Skip to content

Commit

Permalink
Merge pull request #240 from rainlanguage/2024-05-26-pointers-gen
Browse files Browse the repository at this point in the history
2024 05 26 pointers gen
  • Loading branch information
thedavidmeister authored May 27, 2024
2 parents 91a4186 + 54b94ab commit 58fddba
Show file tree
Hide file tree
Showing 16 changed files with 376 additions and 65 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/git-clean.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Git is clean
on: [push]

jobs:
git-clean:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main

- run: nix develop -c i9r-prelude

- run: nix develop -c forge script ./script/BuildPointers.sol

- run: git diff --exit-code
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cbor_metadata = false

fs_permissions = [
{ access = "read-write", path = "./meta" },
{ access = "read-write", path = "src/generated" },
{ access = "write", path = "./deployments/latest/RainterpreterParserNPE2" },
{ access = "write", path = "./deployments/latest/RainterpreterStoreNPE2" },
{ access = "write", path = "./deployments/latest/RainterpreterNPE2" },
Expand Down
232 changes: 232 additions & 0 deletions script/BuildPointers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {Script} from "forge-std/Script.sol";
import {RainterpreterNPE2} from "src/concrete/RainterpreterNPE2.sol";
import {RainterpreterStoreNPE2} from "src/concrete/RainterpreterStoreNPE2.sol";
import {IInterpreterV2} from "rain.interpreter.interface/interface/IInterpreterV2.sol";
import {RainterpreterParserNPE2, PARSE_META_BUILD_DEPTH} from "src/concrete/RainterpreterParserNPE2.sol";
import {
RainterpreterExpressionDeployerNPE2,
RainterpreterExpressionDeployerNPE2ConstructionConfigV2
} from "src/concrete/RainterpreterExpressionDeployerNPE2.sol";
import {LibAllStandardOpsNP, AuthoringMetaV2} from "src/lib/op/LibAllStandardOpsNP.sol";
import {LibParseMeta} from "src/lib/parse/LibParseMeta.sol";
import {EXPRESSION_DEPLOYER_NP_META_PATH} from "src/lib/constants/ExpressionDeployerNPConstants.sol";

contract BuildPointers is Script {
function filePrefix() internal pure returns (string memory) {
return string.concat(
"// THIS FILE IS AUTOGENERATED BY ./script/BuildPointers.sol\n\n",
"// This file is committed to the repository because there is a circular\n"
"// dependency between the contract and its pointers file. The contract\n"
"// needs the pointers file to exist so that it can compile, and the pointers\n"
"// file needs the contract to exist so that it can be compiled.\n\n",
"// SPDX-License-Identifier: CAL\n",
"pragma solidity =0.8.25;\n"
);
}

function pathForContract(string memory contractName) internal pure returns (string memory) {
return string.concat("src/generated/", contractName, ".pointers.sol");
}

function bytesToHex(bytes memory data) internal pure returns (string memory) {
string memory hexString = vm.toString(data);
assembly ("memory-safe") {
// Remove the leading 0x
let newHexString := add(hexString, 2)
mstore(newHexString, sub(mload(hexString), 2))
hexString := newHexString
}
return hexString;
}

function bytecodeHashConstantString(address instance) internal view returns (string memory) {
bytes32 bytecodeHash;
assembly {
bytecodeHash := extcodehash(instance)
}
return string.concat(
"\n",
"/// @dev Hash of the known bytecode.\n",
"bytes32 constant BYTECODE_HASH = bytes32(",
vm.toString(bytecodeHash),
");\n"
);
}

function interpreterFunctionPointersConstantString(IInterpreterV2 interpreter)
internal
view
returns (string memory)
{
return string.concat(
"\n",
"/// @dev The function pointers known to the interpreter for dynamic dispatch.\n",
"/// By setting these as a constant they can be inlined into the interpreter\n",
"/// and loaded at eval time for very low gas (~100) due to the compiler\n",
"/// optimising it to a single `codecopy` to build the in memory bytes array.\n",
"bytes constant OPCODE_FUNCTION_POINTERS =\n",
" hex\"",
bytesToHex(interpreter.functionPointers()),
"\";\n"
);
}

function literalParserFunctionPointersConstantString(RainterpreterParserNPE2 instance)
internal
pure
returns (string memory)
{
return string.concat(
"\n",
"/// @dev Every two bytes is a function pointer for a literal parser.\n",
"/// Literal dispatches are determined by the first byte(s) of the literal\n",
"/// rather than a full word lookup, and are done with simple conditional\n",
"/// jumps as the possibilities are limited compared to the number of words we\n" "/// have.\n",
"bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex\"",
bytesToHex(instance.buildLiteralParserFunctionPointers()),
"\";\n"
);
}

function operandHandlerFunctionPointersConstantString(RainterpreterParserNPE2 instance)
internal
pure
returns (string memory)
{
return string.concat(
"\n",
"/// @dev Every two bytes is a function pointer for an operand handler.\n",
"/// These positional indexes all map to the same indexes looked up in the parse\n",
"/// meta.\n",
"bytes constant OPERAND_HANDLER_FUNCTION_POINTERS =\n",
" hex\"",
bytesToHex(instance.buildOperandHandlerFunctionPointers()),
"\";\n"
);
}

function parseMetaConstantString(bytes memory authoringMetaBytes) internal pure returns (string memory) {
AuthoringMetaV2[] memory authoringMeta = abi.decode(authoringMetaBytes, (AuthoringMetaV2[]));
bytes memory parseMeta = LibParseMeta.buildParseMetaV2(authoringMeta, PARSE_META_BUILD_DEPTH);
return string.concat(
"\n",
"/// @dev Encodes the parser meta that is used to lookup word definitions.\n",
"/// The structure of the parser meta is:\n",
"/// - 1 byte: The depth of the bloom filters\n",
"/// - 1 byte: The hashing seed\n",
"/// - The bloom filters, each is 32 bytes long, one for each build depth.\n",
"/// - All the items for each word, each is 4 bytes long. Each item's first byte\n",
"/// is its opcode index, the remaining 3 bytes are the word fingerprint.\n",
"/// To do a lookup, the word is hashed with the seed, then the first byte of the\n",
"/// hash is compared against the bloom filter. If there is a hit then we count\n",
"/// the number of 1 bits in the bloom filter up to this item's 1 bit. We then\n",
"/// treat this a the index of the item in the items array. We then compare the\n",
"/// word fingerprint against the fingerprint of the item at this index. If the\n",
"/// fingerprints equal then we have a match, else we increment the seed and try\n",
"/// again with the next bloom filter, offsetting all the indexes by the total\n",
"/// bit count of the previous bloom filter. If we reach the end of the bloom\n",
"/// filters then we have a miss.\n",
"bytes constant PARSE_META =\n",
" hex\"",
bytesToHex(parseMeta),
"\";\n\n",
"/// @dev The build depth of the parser meta.\n",
"uint8 constant PARSE_META_BUILD_DEPTH = ",
vm.toString(PARSE_META_BUILD_DEPTH),
";\n"
);
}

function integrityFunctionPointersConstantString(RainterpreterExpressionDeployerNPE2 deployer)
internal
view
returns (string memory)
{
return string.concat(
"\n",
"/// @dev The function pointers for the integrity check fns.\n",
"bytes constant INTEGRITY_FUNCTION_POINTERS =\n",
" hex\"",
bytesToHex(deployer.integrityFunctionPointers()),
"\";\n"
);
}

function describedByMetaHashConstantString(bytes memory describedByMeta) internal pure returns (string memory) {
return string.concat(
"\n",
"/// @dev The hash of the meta that describes the contract.\n",
"bytes32 constant DESCRIBED_BY_META_HASH = bytes32(",
vm.toString(keccak256(describedByMeta)),
");\n"
);
}

function buildFileForContract(address instance, string memory contractName, string memory body) internal {
string memory path = pathForContract(contractName);

if (vm.exists(path)) {
vm.removeFile(path);
}
vm.writeFile(path, string.concat(filePrefix(), bytecodeHashConstantString(instance), body));
}

function buildRainterpreterNPE2Pointers() internal {
RainterpreterNPE2 interpreter = new RainterpreterNPE2();

buildFileForContract(
address(interpreter), "RainterpreterNPE2", interpreterFunctionPointersConstantString(interpreter)
);
}

function buildRainterpreterStoreNPE2Pointers() internal {
RainterpreterStoreNPE2 store = new RainterpreterStoreNPE2();

buildFileForContract(address(store), "RainterpreterStoreNPE2", "");
}

function buildRainterpreterParserNPE2Pointers() internal {
RainterpreterParserNPE2 parser = new RainterpreterParserNPE2();

buildFileForContract(
address(parser),
"RainterpreterParserNPE2",
string.concat(
parseMetaConstantString(LibAllStandardOpsNP.authoringMetaV2()),
operandHandlerFunctionPointersConstantString(parser),
literalParserFunctionPointersConstantString(parser)
)
);
}

function buildRainterpreterExpressionDeployerNPE2Pointers() internal {
RainterpreterNPE2 interpreter = new RainterpreterNPE2();
RainterpreterStoreNPE2 store = new RainterpreterStoreNPE2();
RainterpreterParserNPE2 parser = new RainterpreterParserNPE2();

RainterpreterExpressionDeployerNPE2 deployer = new RainterpreterExpressionDeployerNPE2(
RainterpreterExpressionDeployerNPE2ConstructionConfigV2(
address(interpreter), address(store), address(parser)
)
);

buildFileForContract(
address(deployer),
"RainterpreterExpressionDeployerNPE2",
string.concat(
describedByMetaHashConstantString(vm.readFileBinary(EXPRESSION_DEPLOYER_NP_META_PATH)),
integrityFunctionPointersConstantString(deployer)
)
);
}

function run() external {
buildRainterpreterNPE2Pointers();
buildRainterpreterStoreNPE2Pointers();
buildRainterpreterParserNPE2Pointers();
buildRainterpreterExpressionDeployerNPE2Pointers();
}
}
11 changes: 4 additions & 7 deletions src/concrete/RainterpreterExpressionDeployerNPE2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ import {LibParse, LibParseMeta} from "../lib/parse/LibParse.sol";
import {RainterpreterNPE2, INTERPRETER_BYTECODE_HASH} from "./RainterpreterNPE2.sol";
import {PARSER_BYTECODE_HASH} from "./RainterpreterParserNPE2.sol";
import {STORE_BYTECODE_HASH} from "./RainterpreterStoreNPE2.sol";

/// @dev The function pointers for the integrity check fns.
bytes constant INTEGRITY_FUNCTION_POINTERS =
hex"0e780ef60f5b10d510df10df10e910f2110d11b311b3120f1289129610df10e910df10df10e910d510d510d510d512a012c512df10df12a010df10df129610e910df10df1296129612e912e912e912e910df10e912e910e910e910e910e910df10e910e910e910e910e912e912e912e912e910df10e910e910df10e910e910df10df10e912e912e910e912df";

/// @dev Hash of the metadata that describes the deployer (parsing).
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x89148873ea148e1c312c3c6cffbc5fb012194570f04ed1177db6002901e7bf38);
import {
INTEGRITY_FUNCTION_POINTERS,
DESCRIBED_BY_META_HASH
} from "../generated/RainterpreterExpressionDeployerNPE2.pointers.sol";

/// All config required to construct a `RainterpreterNPE2`.
/// @param interpreter The `IInterpreterV2` to use for evaluation. MUST match
Expand Down
14 changes: 4 additions & 10 deletions src/concrete/RainterpreterNPE2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,10 @@ import {
IInterpreterStoreV2
} from "rain.interpreter.interface/interface/IInterpreterV2.sol";
import {IInterpreterV3} from "rain.interpreter.interface/interface/unstable/IInterpreterV3.sol";

/// @dev Hash of the known interpreter bytecode.
bytes32 constant INTERPRETER_BYTECODE_HASH = bytes32(0x082edcc97843fd74ff6dc51867110b8633b0e419bac46f53765f5a833f36d024);

/// @dev The function pointers known to the interpreter for dynamic dispatch.
/// By setting these as a constant they can be inlined into the interpreter
/// and loaded at eval time for very low gas (~100) due to the compiler
/// optimising it to a single `codecopy` to build the in memory bytes array.
bytes constant OPCODE_FUNCTION_POINTERS =
hex"0e060e570e991065114c115e1170119311d512271238124912eb132813e6149613e6151a15bc1634166d16a616f5172e1793186718ba18ce1927193b1950196a19751989199e19d619fd1a7d1acb1b191b671b7f1b981be61bf41c021c1d1c321c4a1c631c711c7f1c8d1c9b1ce91d371d851dd31deb1deb1e021e301e301e471e761ecb1ed91ed91f7d2064";
import {
BYTECODE_HASH as INTERPRETER_BYTECODE_HASH,
OPCODE_FUNCTION_POINTERS
} from "../generated/RainterpreterNPE2.pointers.sol";

/// @title RainterpreterNPE2
/// @notice Implementation of a Rainlang interpreter that is compatible with
Expand Down
44 changes: 7 additions & 37 deletions src/concrete/RainterpreterParserNPE2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,13 @@ import {LibParseLiteral} from "../lib/parse/literal/LibParseLiteral.sol";
import {LibAllStandardOpsNP} from "../lib/op/LibAllStandardOpsNP.sol";
import {LibBytes, Pointer} from "rain.solmem/lib/LibBytes.sol";
import {LibParseInterstitial} from "../lib/parse/LibParseInterstitial.sol";

/// @dev The known hash of the parser bytecode. This is used by the deployer to
/// check that it is deploying a parser that is compatible with the interpreter.
bytes32 constant PARSER_BYTECODE_HASH = bytes32(0x458b319779ec46db91b9a23ad6cef12479a4e05fa05f5c0094fa3211b1c937cd);

/// @dev Encodes the parser meta that is used to lookup word definitions.
/// The structure of the parser meta is:
/// - 1 byte: The depth of the bloom filters
/// - 1 byte: The hashing seed
/// - The bloom filters, each is 32 bytes long, one for each build depth.
/// - All the items for each word, each is 4 bytes long. Each item's first byte
/// is its opcode index, the remaining 3 bytes are the word fingerprint.
/// To do a lookup, the word is hashed with the seed, then the first byte of the
/// hash is compared against the bloom filter. If there is a hit then we count
/// the number of 1 bits in the bloom filter up to this item's 1 bit. We then
/// treat this a the index of the item in the items array. We then compare the
/// word fingerprint against the fingerprint of the item at this index. If the
/// fingerprints equal then we have a match, else we increment the seed and try
/// again with the next bloom filter, offsetting all the indexes by the total
/// bit count of the previous bloom filter. If we reach the end of the bloom
/// filters then we have a miss.
bytes constant PARSE_META =
hex"027d02482901b41410193601380a408092011324604290a201223062960011040a8900000000000000000800000008000000100000000000000000000000000000000037af1e5831ee31ff1a4c426226d999cd14d454a62287204a17ca041510bfc04d05382451258fb9fe30e745fd28dbc3c529415aff38530a92368e9cbd2f4c3a173b402c5804ea67600a2aa235164d56371805a7653edd323d0cd5a68e1e3f22703f2237031f80073412d4a5b3119bd3ec068483ae402e554c35f6dc5d23f0c0a632fef45a2da6feff1c9677b92edb58d43c742e374178613501d4fa88030cae020885d59f2b766a3e158413022a67b453194070aa2040ab0f245a53d84392737c335bcbd00ff7e3283d5a200713686f5c39bd3f5f024641b909f14a300b1ec71b27a38323349552b91d792a60425d96b7457b8cf22153f8d14433bccc0d403ded0791b7eb002ddffc0e1abd702ce98c713ac04abd1b4507bb";

/// @dev The build depth of the parser meta.
uint8 constant PARSE_META_BUILD_DEPTH = 2;

/// @dev Every two bytes is a function pointer for an operand handler. These
/// positional indexes all map to the same indexes looked up in the parse meta.
bytes constant OPERAND_HANDLER_FUNCTION_POINTERS =
hex"18d818d818d8193d19b619b619b6193d193d18d818d818d819b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619b619fb19b61acd19fb19b61acd19b619b618d81b3619b619b6";

/// @dev Every two bytes is a function pointer for a literal parser. Literal
/// dispatches are determined by the first byte(s) of the literal rather than a
/// full word lookup, and are done with simple conditional jumps as the
/// possibilities are limited compared to the number of words we have.
bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"0f4e1216161d16f7";
import {
BYTECODE_HASH as PARSER_BYTECODE_HASH,
LITERAL_PARSER_FUNCTION_POINTERS,
OPERAND_HANDLER_FUNCTION_POINTERS,
PARSE_META,
PARSE_META_BUILD_DEPTH
} from "../generated/RainterpreterParserNPE2.pointers.sol";

/// @title RainterpreterParserNPE2
/// @dev The parser implementation.
Expand Down
13 changes: 7 additions & 6 deletions src/concrete/RainterpreterStoreNPE2.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import {ERC165} from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";

import "rain.interpreter.interface/interface/IInterpreterStoreV2.sol";
import "rain.interpreter.interface/lib/ns/LibNamespace.sol";
import {IInterpreterStoreV2} from "rain.interpreter.interface/interface/IInterpreterStoreV2.sol";
import {
LibNamespace, FullyQualifiedNamespace, StateNamespace
} from "rain.interpreter.interface/lib/ns/LibNamespace.sol";

import {BYTECODE_HASH as STORE_BYTECODE_HASH} from "../generated/RainterpreterStoreNPE2.pointers.sol";

/// Thrown when a `set` call is made with an odd number of arguments.
error OddSetLength(uint256 length);

/// @dev Hash of the known store bytecode.
bytes32 constant STORE_BYTECODE_HASH = bytes32(0x2a4559222e2f3600b2d393715de8af57620439684463f745059c653bbfe3727f);

/// @title RainterpreterStore
/// @notice Simplest possible `IInterpreterStoreV2` that could work.
/// Takes key/value pairings from the input array and stores each in an internal
Expand Down
19 changes: 19 additions & 0 deletions src/generated/RainterpreterExpressionDeployerNPE2.pointers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// THIS FILE IS AUTOGENERATED BY ./script/BuildPointers.sol

// This file is committed to the repository because there is a circular
// dependency between the contract and its pointers file. The contract
// needs the pointers file to exist so that it can compile, and the pointers
// file needs the contract to exist so that it can be compiled.

// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x1f3ab430cd5a8ad892a5974e85b001b59df9f2a9f9a2ccf562c518b4a6550b5a);

/// @dev The hash of the meta that describes the contract.
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x89148873ea148e1c312c3c6cffbc5fb012194570f04ed1177db6002901e7bf38);

/// @dev The function pointers for the integrity check fns.
bytes constant INTEGRITY_FUNCTION_POINTERS =
hex"0e780ef60f5b10d510df10df10e910f2110d11b311b3120f1289129610df10e910df10df10e910d510d510d510d512a012c512df10df12a010df10df129610e910df10df1296129612e912e912e912e910df10e912e910e910e910e910e910df10e910e910e910e910e912e912e912e912e910df10e910e910df10e910e910df10df10e912e912e910e912df";
Loading

0 comments on commit 58fddba

Please sign in to comment.