Skip to content

Commit

Permalink
implemented ram abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
adr1anh committed Mar 3, 2023
1 parent 09db0be commit 378a2a3
Show file tree
Hide file tree
Showing 9 changed files with 958 additions and 179 deletions.
2 changes: 2 additions & 0 deletions cpp/src/aztec/plonk/composer/composer_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ template <size_t program_width, bool with_tags> void ComposerBase::compute_sigma
}
if (last_node) {
sigma_mappings[current_column][current_row].is_tag = true;

// TODO: yikes, std::maps are expensive. Can we find a way to get rid of this?
sigma_mappings[current_column][current_row].subgroup_index = tau.at(real_variable_tags[i]);
}
}
Expand Down
547 changes: 498 additions & 49 deletions cpp/src/aztec/plonk/composer/ultra_composer.cpp

Large diffs are not rendered by default.

228 changes: 172 additions & 56 deletions cpp/src/aztec/plonk/composer/ultra_composer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ class UltraComposer : public ComposerBase {
NON_NATIVE_FIELD_1,
NON_NATIVE_FIELD_2,
NON_NATIVE_FIELD_3,
CONSISTENT_SORTED_MEMORY_READ,
SORTED_MEMORY_READ,
MEMORY_TIMESTAMP_CORRECTNESS,
MEMORY_READ,
RAM_CONSISTENCY_CHECK,
ROM_CONSISTENCY_CHECK,
RAM_TIMESTAMP_CHECK,
ROM_READ,
RAM_READ,
RAM_WRITE,
};

struct RangeList {
Expand All @@ -57,40 +59,84 @@ class UltraComposer : public ComposerBase {
};

/**
* @brief A memory record that can be ordered
* @brief A ROM memory record that can be ordered
*
*
*/
struct MemoryRecord {
uint32_t index_witness;
uint32_t timestamp_witness;
uint32_t value_witness;
uint32_t index;
uint32_t timestamp;
uint32_t record_witness;
size_t gate_index;
bool operator<(const MemoryRecord& other) const
struct RomRecord {
uint32_t index_witness = 0;
uint32_t value_column1_witness = 0;
uint32_t value_column2_witness = 0;
uint32_t index = 0;
uint32_t record_witness = 0;
size_t gate_index = 0;
bool operator<(const RomRecord& other) const { return index < other.index; }
};

/**
* @brief A RAM memory record that can be ordered
*
*
*/
struct RamRecord {
enum AccessType {
READ,
WRITE,
};
uint32_t index_witness = 0;
uint32_t timestamp_witness = 0;
uint32_t value_witness = 0;
uint32_t index = 0;
uint32_t timestamp = 0;
AccessType access_type = AccessType::READ; // read or write?
uint32_t record_witness = 0;
size_t gate_index = 0;
bool operator<(const RamRecord& other) const
{
bool index_test = (index) < (other.index);
return index_test || (index == other.index && timestamp < other.timestamp);
}
};

/**
* @brief Each ram array is an instance of memory transcript. It saves values and indexes for a particular memory
* array
*
*
*/
struct RamTranscript {
// Represents the current state of the array. Elements are variable indices.
// Every update requires a new entry in the `records` vector below.
std::vector<uint32_t> state;

// A vector of records, each of which contains:
// - Witnesses for [index, timestamp, value, record]
// (record is initialized during the proof creation, and points to 0 until then)
// - Index of the element in the `state` vector
// - READ/WRITE flag
// - Real timestamp value, initialized to the current `access_count`
std::vector<RamRecord> records;

// used for RAM records, to compute the timestamp when performing a read/write
// Incremented at every init/read/write operation.
size_t access_count = 0;
};

/**
* @brief Each rom array is an instance of memory transcript. It saves values and indexes for a particular memory
* array
*
*
*/
struct MemoryTranscript {
struct RomTranscript {
// Contains the value of each index of the array
std::vector<std::array<uint32_t, 2>> state;

// A vector of records, each of which contains:
// + The constant witness with the index
// + The value in the memory slot
// + The actual index value
std::vector<MemoryRecord> records;
std::vector<RomRecord> records;
};

enum UltraSelectors { QM, QC, Q1, Q2, Q3, Q4, QARITH, QFIXED, QSORT, QELLIPTIC, QAUX, QLOOKUPTYPE, NUM };
Expand Down Expand Up @@ -183,17 +229,22 @@ class UltraComposer : public ComposerBase {
* 3) Number of Rom array-associated gates
* 4) NUmber of range-list associated gates
*
* @return size_t
*
* @param count return arument, number of existing gates
* @param rangecount return argument, extra gates due to range checks
* @param romcount return argument, extra gates due to rom reads
* @param ramcount return argument, extra gates due to ram read/writes
*/
virtual size_t get_num_gates() const override
void get_num_gates_split_into_components(size_t& count,
size_t& rangecount,
size_t& romcount,
size_t& ramcount) const
{
// if circuit finalised already added extra gates
if (circuit_finalised) {
return num_gates;
}
size_t count = num_gates;
size_t rangecount = 0;
size_t romcount = 0;
count = num_gates;
rangecount = 0;
romcount = 0;
ramcount = 0;
// each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set
for (size_t i = 0; i < rom_arrays.size(); ++i) {
for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) {
if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) {
Expand All @@ -205,6 +256,32 @@ class UltraComposer : public ComposerBase {
}

constexpr size_t gate_width = ultra_settings::program_width;
// each RAM gate adds +2 extra gates due to the ram reads being copied to a sorted list set,
// as well as an extra gate to validate timestamps
for (size_t i = 0; i < ram_arrays.size(); ++i) {
for (size_t j = 0; j < ram_arrays[i].state.size(); ++j) {
if (ram_arrays[i].state[j] == UNINITIALIZED_MEMORY_RECORD) {
ramcount += 2;
}
}
ramcount += (ram_arrays[i].records.size() * 2);
ramcount += 1; // we add an addition gate after procesing a ram array

// there will be 'max_timestamp' number of range checks, need to calculate.
const auto max_timestamp = ram_arrays[i].access_count - 1;

// TODO: if a range check of length `max_timestamp` already exists, this will be innacurate!
// TODO: fix this
size_t padding = (gate_width - (max_timestamp % gate_width)) % gate_width;
if (max_timestamp == gate_width)
padding += gate_width;
const size_t ram_range_check_list_size = max_timestamp + padding;

size_t ram_range_check_gate_count = (ram_range_check_list_size / gate_width);
ram_range_check_gate_count += 1; // we need to add 1 extra addition gates for every distinct range list

ramcount += ram_range_check_gate_count;
}
for (const auto& list : range_lists) {
auto list_size = list.second.variable_indices.size();
size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width;
Expand All @@ -214,49 +291,60 @@ class UltraComposer : public ComposerBase {
rangecount += (list_size / gate_width);
rangecount += 1; // we need to add 1 extra addition gates for every distinct range list
}
return count + romcount + rangecount;
}

/**
* @brief Get the final number of gates in a circuit, which consists of the sum of:
* 1) Current number number of actual gates
* 2) Number of public inputs, as we'll need to add a gate for each of them
* 3) Number of Rom array-associated gates
* 4) NUmber of range-list associated gates
*
* @return size_t
*/
virtual size_t get_num_gates() const override
{
// if circuit finalised already added extra gates
if (circuit_finalised) {
return num_gates;
}
size_t count = 0;
size_t rangecount = 0;
size_t romcount = 0;
size_t ramcount = 0;
get_num_gates_split_into_components(count, rangecount, romcount, ramcount);
return count + romcount + ramcount + rangecount;
}

virtual void print_num_gates() const override
{
size_t count = num_gates;

size_t count = 0;
size_t rangecount = 0;
size_t constant_rangecount = 0;
size_t romcount = 0;
size_t ramcount = 0;
size_t constant_rangecount = 0;

size_t plookupcount = 0;
for (auto& table : lookup_tables) {

get_num_gates_split_into_components(count, rangecount, romcount, ramcount);

for (const auto& table : lookup_tables) {
plookupcount += table.lookup_gates.size();
count -= table.lookup_gates.size();
}
for (size_t i = 0; i < rom_arrays.size(); ++i) {
for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) {
if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) {
romcount += 2;
}
}
romcount += (rom_arrays[i].records.size());
romcount += 1; // we add an addition gate after procesing a rom array
}

constexpr size_t gate_width = ultra_settings::program_width;
for (const auto& list : range_lists) {
auto list_size = list.second.variable_indices.size();
size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width;
if (list.second.variable_indices.size() == gate_width)
padding += gate_width;
list_size += padding;
rangecount += (list_size / gate_width);
rangecount += 1; // we need to add 1 extra addition gates for every distinct range list

// rough estimate
const size_t constant_cost = static_cast<size_t>(list.second.target_range / 6);
const auto constant_cost = static_cast<size_t>(list.second.target_range / 6);
constant_rangecount += constant_cost;
rangecount -= constant_cost;
}
size_t total = count + romcount + rangecount + constant_rangecount + plookupcount;
size_t total = count + romcount + rangecount;
std::cout << "gates = " << total << " (arith " << count << ", plookup " << plookupcount << ", rom " << romcount
<< ", range " << rangecount << ", range table init cost = " << constant_rangecount
<< "), pubinp = " << public_inputs.size() << std::endl;
<< ", ram " << ramcount << romcount << ", range " << rangecount
<< ", range table init cost = " << constant_rangecount << "), pubinp = " << public_inputs.size()
<< std::endl;
}

void assert_equal_constant(const uint32_t a_idx,
Expand Down Expand Up @@ -375,11 +463,22 @@ class UltraComposer : public ComposerBase {
const std::array<uint32_t, 2>& value_witnesses);
uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness);
std::array<uint32_t, 2> read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness);
void create_memory_gate(MemoryRecord& record);
void create_sorted_memory_gate(MemoryRecord& record, const bool is_ram_transition_or_rom = false);
void create_ROM_gate(RomRecord& record);
void create_sorted_ROM_gate(RomRecord& record);
void process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs);
void process_ROM_arrays(const size_t gate_offset_from_public_inputs);

void create_RAM_gate(RamRecord& record);
void create_sorted_RAM_gate(RamRecord& record);
void create_final_sorted_RAM_gate(RamRecord& record, const size_t ram_array_size);

size_t create_RAM_array(const size_t array_size);
void init_RAM_element(const size_t ram_id, const size_t index_value, const uint32_t value_witness);
uint32_t read_RAM_array(const size_t ram_id, const uint32_t index_witness);
void write_RAM_array(const size_t ram_id, const uint32_t index_witness, const uint32_t value_witness);
void process_RAM_array(const size_t ram_id, const size_t gate_offset_from_public_inputs);
void process_RAM_arrays(const size_t gate_offset_from_public_inputs);

/**
* Member Variables
**/
Expand All @@ -400,9 +499,26 @@ class UltraComposer : public ComposerBase {
std::vector<plookup::MultiTable> lookup_multi_tables;
std::map<uint64_t, RangeList> range_lists; // DOCTODO: explain this.

std::vector<MemoryTranscript> ram_arrays; // DOCTODO: explain this.
std::vector<MemoryTranscript> rom_arrays; // DOCTODO: explain this.
std::vector<uint32_t> memory_records; // Used for ROM.
/**
* @brief Each entry in ram_arrays represents an independent RAM table.
* RamTranscript tracks the current table state,
* as well as the 'records' produced by each read and write operation.
* Used in `compute_proving_key` to generate consistency check gates required to validate the RAM read/write history
*/
std::vector<RamTranscript> ram_arrays;

/**
* @brief Each entry in rom_arrays represents an independent ROM table.
* RomTranscript tracks the current table state,
* as well as the 'records' produced by each read operation.
* Used in `compute_proving_key` to generate consistency check gates required to validate the ROM read history
*/
std::vector<RomTranscript> rom_arrays;

// Stores gate index of ROM and RAM reads (required by proving key)
std::vector<uint32_t> memory_read_records;
// Stores gate index of RAM writes (required by proving key)
std::vector<uint32_t> memory_write_records;

std::vector<uint32_t> recursive_proof_public_input_indices;
bool contains_recursive_proof = false;
Expand Down
Loading

0 comments on commit 378a2a3

Please sign in to comment.