mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-28 01:20:14 +00:00
feat: snapshot wip
This commit is contained in:
@@ -15,8 +15,7 @@
|
||||
#define sfHookEmitCount ((1U << 16U) + 18U)
|
||||
#define sfHookExecutionIndex ((1U << 16U) + 19U)
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
#define sfHookStateCapacity ((1U << 16U) + 22U)
|
||||
#define sfHookStateCapacity ((1U << 16U) + 21U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
|
||||
@@ -354,7 +354,6 @@ extern SF_UINT16 const sfHookStateChangeCount;
|
||||
extern SF_UINT16 const sfHookEmitCount;
|
||||
extern SF_UINT16 const sfHookExecutionIndex;
|
||||
extern SF_UINT16 const sfHookApiVersion;
|
||||
extern SF_UINT16 const sfHookStateScale;
|
||||
extern SF_UINT16 const sfHookStateCapacity;
|
||||
|
||||
// 32-bit integers (common)
|
||||
|
||||
@@ -102,8 +102,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookStateChangeCount, "HookStateChangeCount", UINT16,
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, 18);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateScale, "HookStateScale", UINT16, 21);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateCapacity, "HookStateCapacity", UINT16, 22);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateCapacity, "HookStateCapacity", UINT16, 21);
|
||||
|
||||
// 32-bit integers (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1);
|
||||
|
||||
@@ -997,111 +997,6 @@ public:
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == (fixNS ? 1 : 2));
|
||||
BEAST_EXPECT(!(env.le("alice")->isFieldPresent(sfHookStateCount)));
|
||||
}
|
||||
|
||||
if (env.current()->rules().enabled(featureExtendedHookState))
|
||||
{
|
||||
// Test hook with scaled state data
|
||||
TestHook scaled_state_wasm = wasm[
|
||||
R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t
|
||||
read_len, int64_t error_code); extern int64_t rollback
|
||||
(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (uint32_t read_ptr, uint32_t
|
||||
read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_set(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t hook_account(uint32_t, uint32_t);
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
|
||||
#define SBUF(x) x, sizeof(x)
|
||||
#define TOO_BIG -3
|
||||
#define DOESNT_EXIST -5
|
||||
#define KEYLET_ACCOUNT 3
|
||||
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t hook_acc[20];
|
||||
ASSERT(hook_account(hook_acc, 20) == 20);
|
||||
uint8_t account_keylet[34];
|
||||
ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT,
|
||||
hook_acc, 20, 0,0,0,0) == 34);
|
||||
|
||||
ASSERT(slot_set(account_keylet, 34, 1) == 1);
|
||||
slot_subfield(1, sfHookStateScale, 2);
|
||||
int64_t scale = slot(0,0,2);
|
||||
|
||||
if (scale == 5) {
|
||||
ASSERT(state_set(0, 256, SBUF("test1")) == 256);
|
||||
ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2);
|
||||
ASSERT(state_set(0, 256*3, SBUF("test3")) == 256*3);
|
||||
ASSERT(state_set(0, 256*4, SBUF("test4")) == 256*4);
|
||||
ASSERT(state_set(0, 256*5, SBUF("test5")) == 256*5);
|
||||
ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG);
|
||||
accept(0,0,scale);
|
||||
}
|
||||
rollback(0,0,scale);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
HASH_WASM(scaled_state);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
// Install hook on carol
|
||||
Json::Value jv =
|
||||
ripple::test::jtx::hook(carol, {{hso(scaled_state_wasm)}}, 0);
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookOn] =
|
||||
to_string(UINT256_BIT[ttACCOUNT_SET]);
|
||||
env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
{
|
||||
// HookStateScale => 5
|
||||
Json::Value jv = noop(carol);
|
||||
jv[sfHookStateScale.fieldName] = 5;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
Json::Value invoke = invoke::invoke(carol);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 26);
|
||||
BEAST_EXPECT((*env.le(carol))[sfHookStateCount] == 5);
|
||||
}
|
||||
|
||||
// Delete namespace to clean up state
|
||||
Json::Value iv;
|
||||
iv[jss::Flags] = hsfNSDELETE;
|
||||
iv[jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook] = iv;
|
||||
env(jv, M("Delete namespace"), HSFEE);
|
||||
env.close();
|
||||
|
||||
// Verify state cleanup
|
||||
BEAST_EXPECT(
|
||||
(*env.le(carol))[sfOwnerCount] == features[fixNSDelete] ? 1
|
||||
: 26);
|
||||
BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -9234,8 +9129,9 @@ public:
|
||||
params[0U][jss::HookParameter][jss::HookParameterName] =
|
||||
strHex(std::string("SIZE"));
|
||||
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
||||
features[featureExtendedHookState] ? "0108" /* 2049 */
|
||||
: "0101" /* 257 */;
|
||||
features[featureExtendedHookState]
|
||||
? "0110" /* 4097 - exceeds 4096 max */
|
||||
: "0101" /* 257 */;
|
||||
payJv1[jss::HookParameters] = params;
|
||||
}
|
||||
env(payJv1,
|
||||
@@ -9259,8 +9155,9 @@ public:
|
||||
params[0U][jss::HookParameter][jss::HookParameterName] =
|
||||
strHex(std::string("SIZE"));
|
||||
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
||||
features[featureExtendedHookState] ? "0108" /* 2049 */
|
||||
: "0101" /* 257 */;
|
||||
features[featureExtendedHookState]
|
||||
? "0110" /* 4097 - exceeds 4096 max */
|
||||
: "0101" /* 257 */;
|
||||
payJv2[jss::HookParameters] = params;
|
||||
}
|
||||
env(payJv2, M("test state_set 1"), fee(XRP(1)));
|
||||
@@ -9905,210 +9802,6 @@ public:
|
||||
BEAST_EXPECT((*env.le("frank"))[sfOwnerCount] == 260);
|
||||
}
|
||||
|
||||
if (env.current()->rules().enabled(featureExtendedHookState))
|
||||
{
|
||||
// Test hook with scaled state data
|
||||
TestHook scaled_state_wasm = wasm[
|
||||
R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t
|
||||
read_len, int64_t error_code); extern int64_t rollback
|
||||
(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (uint32_t read_ptr, uint32_t
|
||||
read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_set(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t slot(uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t hook_account(uint32_t, uint32_t);
|
||||
extern int64_t util_keylet(uint32_t, uint32_t, uint32_t,
|
||||
uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
|
||||
#define SBUF(x) x, sizeof(x)
|
||||
#define TOO_BIG -3
|
||||
#define DOESNT_EXIST -5
|
||||
#define KEYLET_ACCOUNT 3
|
||||
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
uint8_t hook_acc[20];
|
||||
ASSERT(hook_account(hook_acc, 20) == 20);
|
||||
uint8_t account_keylet[34];
|
||||
ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT,
|
||||
hook_acc, 20, 0,0,0,0) == 34);
|
||||
|
||||
ASSERT(slot_set(account_keylet, 34, 1) == 1);
|
||||
slot_subfield(1, sfHookStateScale, 2);
|
||||
int64_t scale = slot(0,0,2);
|
||||
|
||||
if (scale == DOESNT_EXIST) {
|
||||
ASSERT(state_set(0, 256, SBUF("test0")) == 256);
|
||||
ASSERT(state_set(0, 257, SBUF("test")) == TOO_BIG);
|
||||
accept(0,0,scale);
|
||||
}
|
||||
if (scale == 2) {
|
||||
ASSERT(state_set(0, 256, SBUF("test1")) == 256);
|
||||
ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2);
|
||||
ASSERT(state_set(0, 256*2+1, SBUF("test")) ==
|
||||
TOO_BIG); accept(0,0,scale);
|
||||
}
|
||||
if (scale == 5) {
|
||||
ASSERT(state_set(0, 256, SBUF("test3")) == 256);
|
||||
ASSERT(state_set(0, 256*5, SBUF("test4")) == 256*5);
|
||||
ASSERT(state_set(0, 256*5+1, SBUF("test")) ==
|
||||
TOO_BIG); accept(0,0,scale);
|
||||
}
|
||||
rollback(0,0,scale);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
HASH_WASM(scaled_state);
|
||||
BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
// Install hook on carol
|
||||
Json::Value jv =
|
||||
ripple::test::jtx::hook(gary, {{hso(scaled_state_wasm)}}, 0);
|
||||
// jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str;
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookOn] =
|
||||
to_string(UINT256_BIT[ttACCOUNT_SET]);
|
||||
env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1);
|
||||
BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount));
|
||||
|
||||
{
|
||||
// no HookStateScale
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 2);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1);
|
||||
}
|
||||
|
||||
{
|
||||
// HookStateScale => 2
|
||||
Json::Value jv = noop(gary);
|
||||
jv[sfHookStateScale.fieldName] = 2;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 3);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1);
|
||||
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 7);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3);
|
||||
}
|
||||
{
|
||||
// HookStateScale => 5
|
||||
Json::Value jv = noop(gary);
|
||||
jv[sfHookStateScale.fieldName] = 5;
|
||||
env(jv, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 16);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3);
|
||||
|
||||
Json::Value invoke = invoke::invoke(gary);
|
||||
env(invoke, HSFEE);
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 26);
|
||||
BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 5);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool extHookStateEnabled = features[featureExtendedHookState];
|
||||
// tests for set_state_cache
|
||||
if (extHookStateEnabled)
|
||||
{
|
||||
TestHook extended_state_reserve_hook = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t state_set (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len
|
||||
);
|
||||
extern int64_t state_foreign_set (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len
|
||||
);
|
||||
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
#define RESERVE_INSUFFICIENT -38
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define ASSERT_EQUAL(x, y)\
|
||||
if (!(x == y))\
|
||||
rollback((uint32_t)#x, sizeof(#x), x);
|
||||
int64_t hook(uint32_t reserved)
|
||||
{
|
||||
_g(1,1);
|
||||
{
|
||||
// 1. first account for StateMap
|
||||
ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT);
|
||||
// 2. first namespace for StateMap
|
||||
ASSERT_EQUAL(state_foreign_set(0, 1, "1", 1, "1", 32, 0, 0), RESERVE_INSUFFICIENT);
|
||||
// 3. first statekey for StateMap
|
||||
ASSERT_EQUAL(state_set(0, 1, "2", 1), RESERVE_INSUFFICIENT);
|
||||
// 4. existing statedata
|
||||
ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT);
|
||||
}
|
||||
accept(0,0,0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
// install the hook on gary
|
||||
Json::Value jv = hso(extended_state_reserve_hook, overrideFlag);
|
||||
jv[jss::HookOn] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // only invoke high
|
||||
env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
Json::Value jv1 = noop(hank);
|
||||
jv1[sfHookStateScale.fieldName] = 8;
|
||||
env(jv1, HSFEE);
|
||||
env.close();
|
||||
|
||||
auto const caller = Account{"caller"};
|
||||
env.fund(XRP(10000), caller);
|
||||
env.close();
|
||||
auto const payAmount = env.balance(hank) -
|
||||
(env.current()->fees().accountReserve(1 + 8)) -
|
||||
drops(1); // 8 + Hook
|
||||
// reduce hank's balance
|
||||
env(pay(hank, Account{"master"}, payAmount), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// invoke the hook from alice
|
||||
Json::Value invokeJv5 = invoke::invoke(caller, hank, "");
|
||||
env(invokeJv5, M("test state_set 15"), fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
// RH TODO:
|
||||
// check state can be set on emit callback
|
||||
// check namespacing provides for non-collision of same key
|
||||
@@ -13969,14 +13662,21 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeatures(FeatureBitset features)
|
||||
testWithFeaturesFast(FeatureBitset features)
|
||||
{
|
||||
test_high_water_mark_capacity(features);
|
||||
test_state(features); //
|
||||
test_state_foreign(features); //
|
||||
test_state_foreign_set(features); //
|
||||
test_state_foreign_set_max(features); //
|
||||
test_state_set(features); //
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeaturesX(FeatureBitset features)
|
||||
testWithFeatures(FeatureBitset features)
|
||||
{
|
||||
test_high_water_mark_capacity(features);
|
||||
|
||||
testHooksOwnerDir(features);
|
||||
testHooksDisabled(features);
|
||||
testTxStructure(features);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
@@ -329,36 +330,68 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
|
||||
#endif
|
||||
"""
|
||||
|
||||
def __init__(self, logger: logging.Logger, output_file: Path):
|
||||
def __init__(self, logger: logging.Logger, output_file: Path, cache_dir: Path):
|
||||
self.logger = logger
|
||||
self.output_file = output_file
|
||||
self.cache_dir = cache_dir
|
||||
|
||||
def write_block(self, out, counter: int, source: str, bytecode: bytes) -> None:
|
||||
"""Write a single compiled block."""
|
||||
out.write(f'/* ==== WASM: {counter} ==== */\n')
|
||||
out.write('{ R"[test.hook](')
|
||||
out.write(source)
|
||||
out.write(')[test.hook]",\n{\n')
|
||||
out.write(OutputFormatter.bytes_to_cpp_array(bytecode))
|
||||
out.write('\n}},\n\n')
|
||||
def _get_clang_format_cache_file(self, content_hash: str) -> Path:
|
||||
"""Get cache file path for formatted output."""
|
||||
return self.cache_dir / f"formatted_{content_hash}.h"
|
||||
|
||||
def write(self, compiled_blocks: Dict[int, Tuple[str, bytes]]) -> None:
|
||||
"""Write all compiled blocks to output file."""
|
||||
def _format_content(self, unformatted_content: str) -> str:
|
||||
"""Format content using clang-format via temp file."""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.h', delete=False) as tmp:
|
||||
tmp.write(unformatted_content)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
subprocess.run(['clang-format', '-i', tmp_path], check=True)
|
||||
with open(tmp_path, 'r') as f:
|
||||
return f.read()
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
def write(self, compiled_blocks: Dict[int, Tuple[str, bytes]], force_write: bool = False) -> None:
|
||||
"""Write all compiled blocks to output file, only if changed."""
|
||||
# Build unformatted content
|
||||
unformatted = []
|
||||
unformatted.append(self.HEADER)
|
||||
for counter in sorted(compiled_blocks.keys()):
|
||||
source, bytecode = compiled_blocks[counter]
|
||||
unformatted.append(f'/* ==== WASM: {counter} ==== */\n')
|
||||
unformatted.append('{ R"[test.hook](')
|
||||
unformatted.append(source)
|
||||
unformatted.append(')[test.hook]",\n{\n')
|
||||
unformatted.append(OutputFormatter.bytes_to_cpp_array(bytecode))
|
||||
unformatted.append('\n}},\n\n')
|
||||
unformatted.append(self.FOOTER)
|
||||
unformatted_content = ''.join(unformatted)
|
||||
|
||||
# Hash the unformatted content
|
||||
content_hash = hashlib.sha256(unformatted_content.encode('utf-8')).hexdigest()
|
||||
cache_file = self._get_clang_format_cache_file(content_hash)
|
||||
|
||||
# Get formatted content (from cache or by formatting)
|
||||
if cache_file.exists():
|
||||
self.logger.info("Using cached clang-format output")
|
||||
formatted_content = cache_file.read_text()
|
||||
else:
|
||||
self.logger.info("Formatting with clang-format")
|
||||
formatted_content = self._format_content(unformatted_content)
|
||||
cache_file.write_text(formatted_content)
|
||||
self.logger.debug(f"Cached formatted output: {content_hash[:16]}...")
|
||||
|
||||
# Check if we need to write (compare with existing file)
|
||||
if not force_write and self.output_file.exists():
|
||||
existing_content = self.output_file.read_text()
|
||||
if existing_content == formatted_content:
|
||||
self.logger.info(f"Output unchanged, skipping write to avoid triggering rebuild")
|
||||
return
|
||||
|
||||
# Write the file
|
||||
self.logger.info(f"Writing {self.output_file}")
|
||||
|
||||
with open(self.output_file, 'w') as out:
|
||||
out.write(self.HEADER)
|
||||
|
||||
for counter in sorted(compiled_blocks.keys()):
|
||||
source, bytecode = compiled_blocks[counter]
|
||||
self.write_block(out, counter, source, bytecode)
|
||||
|
||||
out.write(self.FOOTER)
|
||||
|
||||
def format_with_clang(self) -> None:
|
||||
"""Format output file with clang-format."""
|
||||
self.logger.info("Formatting with clang-format")
|
||||
subprocess.run(['clang-format', '-i', str(self.output_file)], check=True)
|
||||
self.output_file.write_text(formatted_content)
|
||||
|
||||
|
||||
class TestHookBuilder:
|
||||
@@ -376,7 +409,7 @@ class TestHookBuilder:
|
||||
self.cache = CompilationCache(self.logger)
|
||||
self.compiler = WasmCompiler(self.logger, self.wasm_dir, self.cache)
|
||||
self.extractor = SourceExtractor(self.logger, self.input_file)
|
||||
self.writer = OutputWriter(self.logger, self.output_file)
|
||||
self.writer = OutputWriter(self.logger, self.output_file, self.cache.cache_dir)
|
||||
|
||||
def _setup_logging(self) -> logging.Logger:
|
||||
"""Setup logging with specified level."""
|
||||
@@ -463,6 +496,7 @@ class TestHookBuilder:
|
||||
self.logger.info("Configuration:")
|
||||
self.logger.info(f" Workers: {workers} (CPU count: {os.cpu_count()})")
|
||||
self.logger.info(f" Log level: {self.args.log_level.upper()}")
|
||||
self.logger.info(f" Force write: {self.args.force_write}")
|
||||
self.logger.info(f" Input: {self.input_file}")
|
||||
self.logger.info(f" Output: {self.output_file}")
|
||||
self.logger.info(f" Cache: {self.cache.cache_dir}")
|
||||
@@ -477,8 +511,7 @@ class TestHookBuilder:
|
||||
|
||||
blocks = self.extractor.extract()
|
||||
compiled = self.compile_all_blocks(blocks)
|
||||
self.writer.write(compiled)
|
||||
self.writer.format_with_clang()
|
||||
self.writer.write(compiled, force_write=self.args.force_write)
|
||||
|
||||
self.logger.info(f"Successfully generated {self.output_file}")
|
||||
|
||||
@@ -494,6 +527,7 @@ Examples:
|
||||
%(prog)s --log-level=debug # Build with DEBUG logging
|
||||
%(prog)s -j 4 # Build with 4 workers
|
||||
%(prog)s -j 1 # Build sequentially
|
||||
%(prog)s --force-write # Always write output (trigger rebuild)
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -512,6 +546,12 @@ Examples:
|
||||
help='Parallel workers (default: CPU count)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--force-write',
|
||||
action='store_true',
|
||||
help='Always write output file even if unchanged (triggers rebuild)'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user