Skip to content

Commit 7fa09a6

Browse files
committed
add cursor for ram/rom
1 parent c46e8ec commit 7fa09a6

6 files changed

Lines changed: 394 additions & 6 deletions

File tree

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ struct ConstraintProfile {
261261
std::vector<bb::fr> constants; // constant values to pre-register
262262
std::vector<uint64_t> range_list_targets; // range list target ranges to pre-create
263263
std::vector<plookup::BasicTableId> table_ids; // lookup tables to pre-create
264+
size_t num_rom_arrays_per_instance = 0; // ROM arrays created per constraint instance
265+
size_t num_ram_arrays_per_instance = 0; // RAM arrays created per constraint instance
266+
std::vector<size_t> rom_array_sizes; // sizes of ROM arrays created per instance
267+
std::vector<size_t> ram_array_sizes; // sizes of RAM arrays created per instance
264268
};
265269

266270
/**
@@ -283,10 +287,22 @@ ConstraintProfile profile_constraint_type(ConstraintType representative, Handler
283287

284288
// Second instance: measures steady-state cost
285289
auto before = throwaway.snapshot_block_sizes();
290+
size_t rom_before = throwaway.rom_ram_logic.rom_arrays.size();
291+
size_t ram_before = throwaway.rom_ram_logic.ram_arrays.size();
286292
handler(throwaway, representative);
287293
auto after = throwaway.snapshot_block_sizes();
288294
profile.block_sizes = UltraCircuitBuilder::delta(before, after);
289295

296+
// Extract ROM/RAM array counts per instance
297+
profile.num_rom_arrays_per_instance = throwaway.rom_ram_logic.rom_arrays.size() - rom_before;
298+
profile.num_ram_arrays_per_instance = throwaway.rom_ram_logic.ram_arrays.size() - ram_before;
299+
for (size_t i = rom_before; i < throwaway.rom_ram_logic.rom_arrays.size(); i++) {
300+
profile.rom_array_sizes.push_back(throwaway.rom_ram_logic.rom_arrays[i].state.size());
301+
}
302+
for (size_t i = ram_before; i < throwaway.rom_ram_logic.ram_arrays.size(); i++) {
303+
profile.ram_array_sizes.push_back(throwaway.rom_ram_logic.ram_arrays[i].state.size());
304+
}
305+
290306
// Extract constants
291307
for (const auto& [value, _] : throwaway.constant_variable_indices) {
292308
profile.constants.push_back(value);
@@ -350,17 +366,23 @@ void build_constraints_parallel(UltraCircuitBuilder& builder,
350366
std::vector<ConstraintProfile> profiles;
351367
std::vector<std::function<void(UltraCircuitBuilder&)>> tasks;
352368
std::vector<TaskBlockSizes> task_sizes;
369+
std::vector<size_t> task_profile_indices; // which profile each task belongs to
353370

354371
// Helper: profile a constraint type and register all its instances as tasks
355372
auto profile_and_collect = [&](auto& items, auto handler) {
356373
if (items.empty()) {
357374
return;
358375
}
359376
auto profile = profile_constraint_type(items[0], handler, num_witnesses);
377+
size_t profile_idx = profiles.size();
360378
profiles.push_back(profile);
379+
auto sizes = profile.block_sizes;
380+
sizes.num_rom_arrays = profile.num_rom_arrays_per_instance;
381+
sizes.num_ram_arrays = profile.num_ram_arrays_per_instance;
361382
for (size_t i = 0; i < items.size(); i++) {
362383
tasks.emplace_back([handler, &items, i](UltraCircuitBuilder& b) { handler(b, items[i]); });
363-
task_sizes.push_back(profile.block_sizes);
384+
task_sizes.push_back(sizes);
385+
task_profile_indices.push_back(profile_idx);
364386
}
365387
};
366388

@@ -404,7 +426,22 @@ void build_constraints_parallel(UltraCircuitBuilder& builder,
404426
// Phase 2: Prepare the builder's caches from profiles (no constraint execution).
405427
prepare_builder_from_profiles(builder, profiles);
406428

429+
// Phase 2b: Pre-create ROM/RAM arrays for all task instances in deterministic (sequential) order.
430+
// Each task instance creates a known number of ROM/RAM arrays (from profiling).
431+
// We pre-create them all now so that create_ROM_array/create_RAM_array can return
432+
// pre-assigned IDs via per-thread cursors without any races or nondeterminism.
433+
for (size_t t = 0; t < tasks.size(); t++) {
434+
const auto& profile = profiles[task_profile_indices[t]];
435+
for (size_t r = 0; r < profile.num_rom_arrays_per_instance; r++) {
436+
builder.rom_ram_logic.create_ROM_array(profile.rom_array_sizes[r]);
437+
}
438+
for (size_t r = 0; r < profile.num_ram_arrays_per_instance; r++) {
439+
builder.rom_ram_logic.create_RAM_array(profile.ram_array_sizes[r]);
440+
}
441+
}
442+
407443
// Phase 3: Execute ALL instances in parallel
444+
// execute_parallel will set up per-thread ROM/RAM cursors using the num_rom/ram_arrays in task_sizes
408445
if (!tasks.empty()) {
409446
builder.execute_parallel(tasks, task_sizes, num_threads);
410447
}

barretenberg/cpp/src/barretenberg/dsl/acir_format/per_block_gate_count.test.cpp

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
#include <gtest/gtest.h>
1414

1515
#include "acir_format.hpp"
16+
#include "acir_to_constraint_buf.hpp"
1617
#include "barretenberg/circuit_checker/circuit_checker.hpp"
18+
#include "barretenberg/common/get_bytecode.hpp"
1719
#include "barretenberg/crypto/poseidon2/poseidon2.hpp"
1820
#include "barretenberg/crypto/sha256/sha256.hpp"
1921
#include "barretenberg/dsl/acir_format/arithmetic_constraints.hpp"
@@ -25,6 +27,8 @@
2527
#include "barretenberg/dsl/acir_format/test_class.hpp"
2628
#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp"
2729

30+
#include <filesystem>
31+
2832
using namespace bb;
2933
using namespace acir_format;
3034

@@ -1447,3 +1451,272 @@ TEST_F(PerBlockGateCountTests, SequentialVsParallelSemanticEquivalence)
14471451
}
14481452
info("Wire-level differences (expected due to gate reordering): ", total_wire_diffs);
14491453
}
1454+
1455+
// Find the acir_tests directory relative to the source tree
1456+
std::filesystem::path find_acir_tests_dir()
1457+
{
1458+
// Walk up from the build dir to find the repo root
1459+
// The acir_tests are at barretenberg/acir_tests/acir_tests/
1460+
std::filesystem::path candidate = std::filesystem::current_path();
1461+
for (int i = 0; i < 10; i++) {
1462+
auto test_dir = candidate / "barretenberg" / "acir_tests" / "acir_tests";
1463+
if (std::filesystem::exists(test_dir)) {
1464+
return test_dir;
1465+
}
1466+
candidate = candidate.parent_path();
1467+
}
1468+
return {};
1469+
}
1470+
1471+
// Collect all acir_test directories that have compiled artifacts
1472+
std::vector<std::filesystem::path> collect_acir_test_programs()
1473+
{
1474+
auto acir_dir = find_acir_tests_dir();
1475+
if (acir_dir.empty()) {
1476+
return {};
1477+
}
1478+
std::vector<std::filesystem::path> programs;
1479+
for (const auto& entry : std::filesystem::directory_iterator(acir_dir)) {
1480+
if (!entry.is_directory())
1481+
continue;
1482+
auto program_json = entry.path() / "target" / "program.json";
1483+
auto witness_gz = entry.path() / "target" / "witness.gz";
1484+
if (std::filesystem::exists(program_json) && std::filesystem::exists(witness_gz)) {
1485+
programs.push_back(entry.path());
1486+
}
1487+
}
1488+
std::sort(programs.begin(), programs.end());
1489+
return programs;
1490+
}
1491+
1492+
// Check semantic equivalence between two builders: same block sizes, variable counts,
1493+
// copy cycle structure, constants, range lists, and lookup tables.
1494+
// Returns number of failures (0 = all invariants hold).
1495+
size_t check_semantic_equivalence(const std::string& label, const UltraCircuitBuilder& a, const UltraCircuitBuilder& b)
1496+
{
1497+
size_t failures = 0;
1498+
1499+
// Block sizes must match
1500+
auto a_blocks = a.blocks.get();
1501+
auto b_blocks = b.blocks.get();
1502+
for (size_t bl = 0; bl < UltraCircuitBuilder::ExecutionTrace::NUM_BLOCKS; bl++) {
1503+
if (a_blocks[bl].size() != b_blocks[bl].size()) {
1504+
info(label, ": block ", bl, " size mismatch: ", a_blocks[bl].size(), " vs ", b_blocks[bl].size());
1505+
failures++;
1506+
}
1507+
}
1508+
1509+
// Variable count
1510+
if (a.get_num_variables() != b.get_num_variables()) {
1511+
info(label, ": variable count mismatch: ", a.get_num_variables(), " vs ", b.get_num_variables());
1512+
failures++;
1513+
}
1514+
1515+
// Copy cycle roots (distinct real_variable_index values)
1516+
auto count_roots = [](const UltraCircuitBuilder& builder) -> size_t {
1517+
std::set<uint32_t> roots;
1518+
for (size_t i = 0; i < builder.get_num_variables(); i++) {
1519+
roots.insert(builder.real_variable_index[i]);
1520+
}
1521+
return roots.size();
1522+
};
1523+
size_t a_roots = count_roots(a);
1524+
size_t b_roots = count_roots(b);
1525+
if (a_roots != b_roots) {
1526+
info(label, ": copy cycle roots mismatch: ", a_roots, " vs ", b_roots);
1527+
failures++;
1528+
}
1529+
1530+
// Constant count
1531+
if (a.constant_variable_indices.size() != b.constant_variable_indices.size()) {
1532+
info(label,
1533+
": constant count mismatch: ",
1534+
a.constant_variable_indices.size(),
1535+
" vs ",
1536+
b.constant_variable_indices.size());
1537+
failures++;
1538+
}
1539+
1540+
// Range lists: same targets, same variable counts per target
1541+
if (a.range_lists.size() != b.range_lists.size()) {
1542+
info(label, ": range list count mismatch: ", a.range_lists.size(), " vs ", b.range_lists.size());
1543+
failures++;
1544+
}
1545+
for (const auto& [target, a_rl] : a.range_lists) {
1546+
auto it = b.range_lists.find(target);
1547+
if (it == b.range_lists.end()) {
1548+
info(label, ": range target ", target, " missing from second builder");
1549+
failures++;
1550+
} else if (a_rl.variable_indices.size() != it->second.variable_indices.size()) {
1551+
info(label,
1552+
": range target ",
1553+
target,
1554+
" variable count mismatch: ",
1555+
a_rl.variable_indices.size(),
1556+
" vs ",
1557+
it->second.variable_indices.size());
1558+
failures++;
1559+
}
1560+
}
1561+
1562+
// Lookup tables
1563+
if (a.get_lookup_tables().size() != b.get_lookup_tables().size()) {
1564+
info(label,
1565+
": lookup table count mismatch: ",
1566+
a.get_lookup_tables().size(),
1567+
" vs ",
1568+
b.get_lookup_tables().size());
1569+
failures++;
1570+
}
1571+
1572+
return failures;
1573+
}
1574+
1575+
// Check bit-identical circuits (every wire, selector, variable, and union-find entry must match).
1576+
// Returns number of mismatches (0 = identical).
1577+
size_t check_bit_identical(const std::string& label, UltraCircuitBuilder& a, UltraCircuitBuilder& b)
1578+
{
1579+
size_t mismatches = 0;
1580+
1581+
auto a_blocks = a.blocks.get();
1582+
auto b_blocks = b.blocks.get();
1583+
for (size_t bl = 0; bl < UltraCircuitBuilder::ExecutionTrace::NUM_BLOCKS; bl++) {
1584+
if (a_blocks[bl].size() != b_blocks[bl].size()) {
1585+
info(label, ": block ", bl, " size mismatch: ", a_blocks[bl].size(), " vs ", b_blocks[bl].size());
1586+
mismatches++;
1587+
continue;
1588+
}
1589+
size_t count = a_blocks[bl].size();
1590+
for (size_t w = 0; w < 4; w++) {
1591+
for (size_t i = 0; i < count; i++) {
1592+
if (a_blocks[bl].wires[w][i] != b_blocks[bl].wires[w][i])
1593+
mismatches++;
1594+
}
1595+
}
1596+
auto a_sels = a_blocks[bl].get_selectors();
1597+
auto b_sels = b_blocks[bl].get_selectors();
1598+
for (size_t s = 0; s < a_sels.size(); s++) {
1599+
for (size_t i = 0; i < count; i++) {
1600+
if (a_sels[s][i] != b_sels[s][i])
1601+
mismatches++;
1602+
}
1603+
}
1604+
}
1605+
1606+
if (a.get_num_variables() != b.get_num_variables()) {
1607+
info(label, ": variable count mismatch");
1608+
mismatches++;
1609+
} else {
1610+
for (size_t i = 0; i < a.get_num_variables(); i++) {
1611+
if (a.real_variable_index[i] != b.real_variable_index[i])
1612+
mismatches++;
1613+
}
1614+
}
1615+
1616+
return mismatches;
1617+
}
1618+
1619+
// Parameterized test that runs the 3-way comparison on every acir_test program.
1620+
class AcirTestParallelEquivalence : public ::testing::TestWithParam<std::filesystem::path> {
1621+
protected:
1622+
static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); }
1623+
};
1624+
1625+
TEST_P(AcirTestParallelEquivalence, SequentialN1N2)
1626+
{
1627+
auto test_dir = GetParam();
1628+
std::string test_name = test_dir.filename().string();
1629+
auto program_path = test_dir / "target" / "program.json";
1630+
auto witness_path = test_dir / "target" / "witness.gz";
1631+
1632+
// Load bytecode and witness
1633+
auto bytecode = get_bytecode(program_path.string());
1634+
AcirFormat constraints = circuit_buf_to_acir_format(std::move(bytecode));
1635+
auto witness_buf = gunzip(witness_path.string());
1636+
WitnessVector witness = witness_buf_to_witness_vector(std::move(witness_buf));
1637+
1638+
// Print constraint breakdown for diagnostics
1639+
info(" quad=",
1640+
constraints.quad_constraints.size(),
1641+
" big_quad=",
1642+
constraints.big_quad_constraints.size(),
1643+
" logic=",
1644+
constraints.logic_constraints.size(),
1645+
" range=",
1646+
constraints.range_constraints.size(),
1647+
" sha256=",
1648+
constraints.sha256_compression.size(),
1649+
" ecdsa_k1=",
1650+
constraints.ecdsa_k1_constraints.size(),
1651+
" ecdsa_r1=",
1652+
constraints.ecdsa_r1_constraints.size(),
1653+
" poseidon2=",
1654+
constraints.poseidon2_constraints.size(),
1655+
" block=",
1656+
constraints.block_constraints.size(),
1657+
" msm=",
1658+
constraints.multi_scalar_mul_constraints.size(),
1659+
" ec_add=",
1660+
constraints.ec_add_constraints.size(),
1661+
" aes128=",
1662+
constraints.aes128_constraints.size());
1663+
1664+
// 1. Build sequentially via create_circuit (uses build_constraints)
1665+
AcirProgram seq_program{ constraints, WitnessVector(witness) };
1666+
auto seq_builder = create_circuit<UltraCircuitBuilder>(seq_program, ProgramMetadata{});
1667+
1668+
// 2. Build via parallel path with N=1
1669+
AcirFormat n1_constraints = constraints;
1670+
UltraCircuitBuilder n1_builder{ WitnessVector(witness), n1_constraints.public_inputs, false };
1671+
build_constraints_parallel(n1_builder, n1_constraints, ProgramMetadata{}, /*num_threads=*/1);
1672+
1673+
// 3. Build via parallel path with N=2
1674+
AcirFormat n2_constraints = constraints;
1675+
UltraCircuitBuilder n2_builder{ WitnessVector(witness), n2_constraints.public_inputs, false };
1676+
build_constraints_parallel(n2_builder, n2_constraints, ProgramMetadata{}, /*num_threads=*/2);
1677+
1678+
// Print block sizes for all three builders
1679+
{
1680+
auto sb = seq_builder.blocks.get();
1681+
auto n1b = n1_builder.blocks.get();
1682+
auto n2b = n2_builder.blocks.get();
1683+
for (size_t bl = 0; bl < UltraCircuitBuilder::ExecutionTrace::NUM_BLOCKS; bl++) {
1684+
if (sb[bl].size() > 0 || n1b[bl].size() > 0 || n2b[bl].size() > 0) {
1685+
info(" block ", bl, ": seq=", sb[bl].size(), " n1=", n1b[bl].size(), " n2=", n2b[bl].size());
1686+
}
1687+
}
1688+
info(" vars: seq=",
1689+
seq_builder.get_num_variables(),
1690+
" n1=",
1691+
n1_builder.get_num_variables(),
1692+
" n2=",
1693+
n2_builder.get_num_variables());
1694+
}
1695+
1696+
// All three must pass circuit checker
1697+
bool seq_ok = CircuitChecker::check(seq_builder);
1698+
bool n1_ok = CircuitChecker::check(n1_builder);
1699+
bool n2_ok = CircuitChecker::check(n2_builder);
1700+
EXPECT_TRUE(seq_ok) << test_name << ": sequential CircuitChecker failed";
1701+
EXPECT_TRUE(n1_ok) << test_name << ": N=1 CircuitChecker failed";
1702+
EXPECT_TRUE(n2_ok) << test_name << ": N=2 CircuitChecker failed";
1703+
1704+
// Sequential vs N=1: semantic equivalence (same constraints, different order)
1705+
size_t seq_n1_failures = check_semantic_equivalence(test_name + " seq-vs-n1", seq_builder, n1_builder);
1706+
EXPECT_EQ(seq_n1_failures, 0) << test_name << ": sequential vs N=1 semantic equivalence failed";
1707+
1708+
// Sequential vs N=2: semantic equivalence
1709+
size_t seq_n2_failures = check_semantic_equivalence(test_name + " seq-vs-n2", seq_builder, n2_builder);
1710+
EXPECT_EQ(seq_n2_failures, 0) << test_name << ": sequential vs N=2 semantic equivalence failed";
1711+
1712+
// N=1 vs N=2: must be bit-identical
1713+
size_t n1_n2_mismatches = check_bit_identical(test_name + " n1-vs-n2", n1_builder, n2_builder);
1714+
EXPECT_EQ(n1_n2_mismatches, 0) << test_name << ": N=1 vs N=2 bit-identical check failed";
1715+
}
1716+
1717+
INSTANTIATE_TEST_SUITE_P(AcirTests,
1718+
AcirTestParallelEquivalence,
1719+
::testing::ValuesIn(collect_acir_test_programs()),
1720+
[](const ::testing::TestParamInfo<std::filesystem::path>& info) {
1721+
return info.param.filename().string();
1722+
});

0 commit comments

Comments
 (0)