diff --git a/aggregator/src/aggregation/decoder.rs b/aggregator/src/aggregation/decoder.rs index 5ff0d2615f..64e7785f1a 100644 --- a/aggregator/src/aggregation/decoder.rs +++ b/aggregator/src/aggregation/decoder.rs @@ -187,6 +187,16 @@ struct BlockConfig { is_block: Column, /// Number of sequences decoded from the sequences section header in the block. num_sequences: Column, + /// For sequence decoding, the tag=ZstdBlockSequenceHeader bytes tell us the Compression_Mode + /// utilised for Literals Lengths, Match Offsets and Match Lengths. We expect only 2 + /// possibilities: + /// 1. Predefined_Mode (value=0) + /// 2. Fse_Compressed_Mode (value=2) + /// + /// Which means a single boolean flag is sufficient to take note of which compression mode is + /// utilised for each of the above purposes. The boolean flag will be set if we utilise the + /// Fse_Compressed_Mode. + compression_modes: [Column; 3], } impl BlockConfig { @@ -197,10 +207,58 @@ impl BlockConfig { is_last_block: meta.advice_column(), is_block: meta.advice_column(), num_sequences: meta.advice_column(), + compression_modes: [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ], } } } +impl BlockConfig { + fn is_predefined_llt(&self, meta: &mut VirtualCells, rotation: Rotation) -> Expression { + not::expr(meta.query_advice(self.compression_modes[0], rotation)) + } + + fn is_predefined_mot(&self, meta: &mut VirtualCells, rotation: Rotation) -> Expression { + not::expr(meta.query_advice(self.compression_modes[1], rotation)) + } + + fn is_predefined_mlt(&self, meta: &mut VirtualCells, rotation: Rotation) -> Expression { + not::expr(meta.query_advice(self.compression_modes[2], rotation)) + } + + fn are_predefined_all( + &self, + meta: &mut VirtualCells, + rotation: Rotation, + ) -> Expression { + and::expr([ + self.is_predefined_llt(meta, rotation), + self.is_predefined_mot(meta, rotation), + self.is_predefined_mlt(meta, rotation), + ]) + } + + fn is_predefined( + &self, + meta: &mut VirtualCells, + fse_decoder: &FseDecoder, + rotation: Rotation, + ) -> Expression { + select::expr( + fse_decoder.is_llt(meta, rotation), + self.is_predefined_llt(meta, rotation), + select::expr( + fse_decoder.is_mlt(meta, rotation), + self.is_predefined_mlt(meta, rotation), + self.is_predefined_mot(meta, rotation), + ), + ) + } +} + #[derive(Clone, Debug)] struct SequencesHeaderDecoder { /// Helper gadget to evaluate byte0 < 128. @@ -1687,6 +1745,15 @@ impl DecoderConfig { meta.query_advice(config.block_config.num_sequences, Rotation::prev()), ); + // the compression modes are remembered throughout the block's context. + for column in config.block_config.compression_modes { + cb.require_equal( + "compression_modes::cur == compression_modes::prev (during block)", + meta.query_advice(column, Rotation::cur()), + meta.query_advice(column, Rotation::prev()), + ); + } + cb.gate(condition) }); @@ -1845,24 +1912,46 @@ impl DecoderConfig { ); // The compression modes for literals length, match length and offsets are expected to - // be FSE, i.e. compression mode == 2, i.e. bit0 == 0 and bit1 == 1. + // be either Predefined_Mode or Fse_Compressed_Mode, i.e. compression mode==0 or + // compression_mode==2. i.e. bit0==0. cb.require_zero("ll: bit0 == 0", decoded_sequences_header.comp_mode_bit0_ll); cb.require_zero("om: bit0 == 0", decoded_sequences_header.comp_mode_bit0_om); cb.require_zero("ml: bit0 == 0", decoded_sequences_header.comp_mode_bit0_ml); + + // Depending on bit1==0 or bit1==1 we know whether the compression mode is + // Predefined_Mode or Fse_Compressed_Mode. The compression_modes flag is set when + // Fse_Compressed_Mode is utilised. cb.require_equal( - "ll: bit1 == 1", + "block_config: compression_modes llt", + meta.query_advice(config.block_config.compression_modes[0], Rotation::cur()), decoded_sequences_header.comp_mode_bit1_ll, - 1.expr(), ); cb.require_equal( - "om: bit1 == 1", + "block_config: compression_modes mot", + meta.query_advice(config.block_config.compression_modes[1], Rotation::cur()), decoded_sequences_header.comp_mode_bit1_om, - 1.expr(), ); cb.require_equal( - "ml: bit1 == 1", + "block_config: compression_modes mlt", + meta.query_advice(config.block_config.compression_modes[2], Rotation::cur()), decoded_sequences_header.comp_mode_bit1_ml, - 1.expr(), + ); + + // If all the three LLT, MOT and MLT use the Predefined_Mode, we have no FSE tables to + // decode in the sequences section. And the tag=ZstdBlockSequenceHeader will + // immediately be followed by tag=ZstdBlockSequenceData. + let no_fse_tables = config + .block_config + .are_predefined_all(meta, Rotation::cur()); + cb.require_equal( + "SequenceHeader: tag_next=FseCode or tag_next=SequencesData", + meta.query_advice(config.tag_config.tag_next, Rotation::cur()), + select::expr( + no_fse_tables, + // TODO: replace with SequencesData once witgen code is merged. + ZstdTag::ZstdBlockHuffmanCode.expr(), + ZstdTag::ZstdBlockFseCode.expr(), + ), ); cb.gate(condition) @@ -1959,6 +2048,9 @@ impl DecoderConfig { ]); [ + meta.query_advice(config.block_config.compression_modes[0], Rotation::cur()), + meta.query_advice(config.block_config.compression_modes[1], Rotation::cur()), + meta.query_advice(config.block_config.compression_modes[2], Rotation::cur()), meta.query_advice(config.tag_config.tag, Rotation::prev()), // tag_prev meta.query_advice(config.tag_config.tag, Rotation::cur()), // tag_cur meta.query_advice(config.tag_config.tag_next, Rotation::cur()), // tag_next @@ -2224,6 +2316,7 @@ impl DecoderConfig { block_idx, fse_table_kind, fse_table_size, + 0.expr(), // is_predefined fse_symbol, norm_prob.expr(), norm_prob.expr(), @@ -2715,6 +2808,10 @@ impl DecoderConfig { meta.query_advice(config.fse_decoder.table_kind, Rotation::cur()), meta.query_advice(config.fse_decoder.table_size, Rotation::cur()), ); + let is_predefined_mode = + config + .block_config + .is_predefined(meta, &config.fse_decoder, Rotation::cur()); [ 0.expr(), // q_first @@ -2722,7 +2819,8 @@ impl DecoderConfig { block_idx, table_kind, table_size, - 0.expr(), // is_padding + is_predefined_mode, // is_predefined + 0.expr(), // is_padding ] .into_iter() .zip_eq(config.fse_table.table_exprs_metadata(meta)) @@ -2801,12 +2899,17 @@ impl DecoderConfig { .bitstream_decoder .bitstring_len(meta, Rotation::cur()), ); + let is_predefined_mode = + config + .block_config + .is_predefined(meta, &config.fse_decoder, Rotation::cur()); [ 0.expr(), // q_first block_idx, table_kind, table_size, + is_predefined_mode, // is_predefined state, symbol, baseline, diff --git a/aggregator/src/aggregation/decoder/tables.rs b/aggregator/src/aggregation/decoder/tables.rs index dee0f9141d..01a0a0f6f2 100644 --- a/aggregator/src/aggregation/decoder/tables.rs +++ b/aggregator/src/aggregation/decoder/tables.rs @@ -14,7 +14,10 @@ pub use literals_header::LiteralsHeaderTable; /// Validate the assignment of FSE table kind while decoding FSE tables in the sequences section. mod rom_fse_order; -pub use rom_fse_order::{FseTableKind, RomFseOrderTable, RomSequencesDataInterleavedOrder}; +pub use rom_fse_order::{ + predefined_table, predefined_table_values, FsePredefinedTable, FseTableKind, RomFseOrderTable, + RomSequencesDataInterleavedOrder, +}; /// The fixed code to Baseline/NumBits for Literal Length. mod rom_sequence_codes; diff --git a/aggregator/src/aggregation/decoder/tables/fse.rs b/aggregator/src/aggregation/decoder/tables/fse.rs index e5e3ef3a8c..a00897590b 100644 --- a/aggregator/src/aggregation/decoder/tables/fse.rs +++ b/aggregator/src/aggregation/decoder/tables/fse.rs @@ -13,7 +13,9 @@ use zkevm_circuits::{ table::{BitwiseOp, BitwiseOpTable, LookupTable, Pow2Table, RangeTable, U8Table}, }; -use crate::aggregation::decoder::tables::rom_fse_order::{FseTableKind, RomFseTableTransition}; +use crate::aggregation::decoder::tables::rom_fse_order::{ + FseTableKind, RomFsePredefinedTable, RomFseTableTransition, +}; /// The FSE table verifies that given the symbols and the states allocated to those symbols, the /// baseline and number of bits (nb) are assigned correctly to them. @@ -80,7 +82,7 @@ use crate::aggregation::decoder::tables::rom_fse_order::{FseTableKind, RomFseTab #[derive(Clone, Debug)] pub struct FseTable { /// The helper table to validate that the (baseline, nb) were assigned correctly to each state. - fse_sorted_states_table: FseSortedStatesTable, + sorted_table: FseSortedStatesTable, /// A boolean to mark whether this row represents a symbol with probability "less than 1". is_prob_less_than1: Column, /// Boolean column to mark whether the row is a padded row. @@ -119,6 +121,8 @@ pub struct FseTable { nb: Column, /// ROM table for verifying FSE table kind and block_idx transition. rom_fse_transition: RomFseTableTransition, + /// ROM table for FSE predefined tables. + rom_fse_predefined: RomFsePredefinedTable, } impl FseTable { @@ -132,13 +136,14 @@ impl FseTable { ) -> Self { // Fixed table to check the transition of table kinds and block idx. let rom_fse_transition = RomFseTableTransition::construct(meta); + let rom_fse_predefined = RomFsePredefinedTable::construct(meta); // Auxiliary table to validate that (baseline, nb) were assigned correctly to the states // allocated to a symbol. - let fse_sorted_states_table = FseSortedStatesTable::configure(meta, pow2_table, u8_table); + let sorted_table = FseSortedStatesTable::configure(meta, pow2_table, u8_table); let config = Self { - fse_sorted_states_table, + sorted_table, is_prob_less_than1: meta.advice_column(), is_padding: meta.advice_column(), table_size_rs_1: meta.advice_column(), @@ -153,18 +158,18 @@ impl FseTable { baseline: meta.advice_column(), nb: meta.advice_column(), rom_fse_transition, + rom_fse_predefined, }; // Check that on the starting row of each FSE table, i.e. q_start=true: // - table_size_rs_3 == table_size >> 3. meta.lookup("FseTable: table_size >> 3", |meta| { let condition = and::expr([ - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), + meta.query_fixed(config.sorted_table.q_start, Rotation::cur()), not::expr(meta.query_advice(config.is_padding, Rotation::cur())), ]); - let range_value = meta - .query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()) + let range_value = meta.query_advice(config.sorted_table.table_size, Rotation::cur()) - (meta.query_advice(config.table_size_rs_3, Rotation::cur()) * 8.expr()); vec![(condition * range_value, range8_table.into())] @@ -180,8 +185,7 @@ impl FseTable { // The first row of the FseTable layout, i.e. q_first=true. meta.create_gate("FseTable: first row", |meta| { - let condition = - meta.query_fixed(config.fse_sorted_states_table.q_first, Rotation::cur()); + let condition = meta.query_fixed(config.sorted_table.q_first, Rotation::cur()); let mut cb = BaseConstraintBuilder::default(); @@ -189,14 +193,14 @@ impl FseTable { // to make sure the first FSE table belongs to block_idx=1. cb.require_equal( "block_idx == 1 for the first FSE table", - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::next()), + meta.query_advice(config.sorted_table.block_idx, Rotation::next()), 1.expr(), ); // The first FSE table described should be the LLT table. cb.require_equal( "table_kind == LLT for the first FSE table", - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::next()), + meta.query_advice(config.sorted_table.table_kind, Rotation::next()), FseTableKind::LLT.expr(), ); @@ -213,15 +217,15 @@ impl FseTable { "FseSortedStatesTable: start row (ROM block_idx and table_kind transition)", |meta| { let condition = and::expr([ - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), + meta.query_fixed(config.sorted_table.q_start, Rotation::cur()), not::expr(meta.query_advice(config.is_padding, Rotation::cur())), ]); let (block_idx_prev, block_idx_curr, table_kind_prev, table_kind_curr) = ( - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::prev()), - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::prev()), - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::cur()), + meta.query_advice(config.sorted_table.block_idx, Rotation::prev()), + meta.query_advice(config.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(config.sorted_table.table_kind, Rotation::prev()), + meta.query_advice(config.sorted_table.table_kind, Rotation::cur()), ); [ @@ -240,7 +244,7 @@ impl FseTable { // The starting row of every FSE table, i.e. q_start=true. meta.create_gate("FseTable: start row", |meta| { let condition = and::expr([ - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), + meta.query_fixed(config.sorted_table.q_start, Rotation::cur()), not::expr(meta.query_advice(config.is_padding, Rotation::cur())), ]); @@ -254,8 +258,7 @@ impl FseTable { cb.require_equal( "prob=-1: state inits at table_size - 1", meta.query_advice(config.state, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()) - - 1.expr(), + meta.query_advice(config.sorted_table.table_size, Rotation::cur()) - 1.expr(), ); }); @@ -276,7 +279,7 @@ impl FseTable { // table_size_rs_1 == table_size >> 1. cb.require_boolean( "table_size >> 1", - meta.query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()) + meta.query_advice(config.sorted_table.table_size, Rotation::cur()) - (meta.query_advice(config.table_size_rs_1, Rotation::cur()) * 2.expr()), ); @@ -298,7 +301,7 @@ impl FseTable { // read nb=AL bits, i.e. 1 << nb == table_size. [ meta.query_advice(config.nb, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()), + meta.query_advice(config.sorted_table.table_size, Rotation::cur()), ] .into_iter() .zip_eq(pow2_table.table_exprs(meta)) @@ -345,9 +348,7 @@ impl FseTable { "FseTable: subsequent symbols with prob=-1 (symbol increasing)", |meta| { let condition = and::expr([ - not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ), + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())), meta.query_advice(config.is_prob_less_than1, Rotation::cur()), ]); @@ -373,9 +374,7 @@ impl FseTable { "FseTable: subsequent symbols with prob=-1 (state retreating)", |meta| { let condition = and::expr([ - not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ), + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())), meta.query_advice(config.is_prob_less_than1, Rotation::cur()), ]); @@ -398,9 +397,7 @@ impl FseTable { "FseTable: symbols with prob>=1 (symbol increasing)", |meta| { let condition = and::expr([ - not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ), + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())), not::expr(meta.query_advice(config.is_prob_less_than1, Rotation::prev())), meta.query_advice(config.is_new_symbol, Rotation::cur()), ]); @@ -424,9 +421,7 @@ impl FseTable { // Symbols with prob>=1 continue the same symbol if not a new symbol. meta.create_gate("FseTable: symbols with prob>=1", |meta| { let condition = and::expr([ - not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ), + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())), not::expr(meta.query_advice(config.is_prob_less_than1, Rotation::cur())), not::expr(meta.query_advice(config.is_padding, Rotation::cur())), ]); @@ -450,9 +445,8 @@ impl FseTable { // All rows in an instance of FSE table, except the starting row (q_start=true). meta.create_gate("FseTable: every FSE table (except q_start=1)", |meta| { - let condition = not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ); + let condition = + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())); let mut cb = BaseConstraintBuilder::default(); @@ -509,10 +503,7 @@ impl FseTable { cb.require_equal( "idx == table_size on the last state", meta.query_advice(config.idx, Rotation::prev()), - meta.query_advice( - config.fse_sorted_states_table.table_size, - Rotation::prev(), - ), + meta.query_advice(config.sorted_table.table_size, Rotation::prev()), ); }, ); @@ -528,15 +519,15 @@ impl FseTable { // check that there exists a row with the same block_idx, table_kind and the skipped // state with a prob=-1. let fse_table_exprs = [ - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::cur()), + meta.query_advice(config.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(config.sorted_table.table_kind, Rotation::cur()), meta.query_advice(config.state, Rotation::cur()), meta.query_advice(config.is_prob_less_than1, Rotation::cur()), ]; [ - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::cur()), + meta.query_advice(config.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(config.sorted_table.table_kind, Rotation::cur()), meta.query_advice(config.state, Rotation::cur()), 1.expr(), // prob=-1 ] @@ -558,9 +549,9 @@ impl FseTable { ]); let (block_idx, table_kind, table_size, state, symbol, symbol_count, baseline, nb) = ( - meta.query_advice(config.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_kind, Rotation::cur()), - meta.query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()), + meta.query_advice(config.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(config.sorted_table.table_kind, Rotation::cur()), + meta.query_advice(config.sorted_table.table_size, Rotation::cur()), meta.query_advice(config.state, Rotation::cur()), meta.query_advice(config.symbol, Rotation::cur()), meta.query_advice(config.symbol_count, Rotation::cur()), @@ -580,12 +571,37 @@ impl FseTable { 0.expr(), ] .into_iter() - .zip_eq(config.fse_sorted_states_table.table_exprs(meta)) + .zip_eq(config.sorted_table.table_exprs(meta)) .map(|(arg, table)| (condition.expr() * arg, table)) .collect() }, ); + // For predefined FSE tables, we must validate against the ROM predefined table fields for + // every state in the FSE table. + meta.lookup_any("FseTable: predefined table validation", |meta| { + let condition = and::expr([ + meta.query_advice(config.sorted_table.is_predefined, Rotation::cur()), + not::expr(meta.query_advice(config.is_skipped_state, Rotation::cur())), + not::expr(meta.query_advice(config.is_padding, Rotation::cur())), + ]); + + let (table_kind, table_size, state, symbol, baseline, nb) = ( + meta.query_advice(config.sorted_table.table_kind, Rotation::cur()), + meta.query_advice(config.sorted_table.table_size, Rotation::cur()), + meta.query_advice(config.state, Rotation::cur()), + meta.query_advice(config.symbol, Rotation::cur()), + meta.query_advice(config.baseline, Rotation::cur()), + meta.query_advice(config.nb, Rotation::cur()), + ); + + [table_kind, table_size, state, symbol, baseline, nb] + .into_iter() + .zip_eq(config.rom_fse_predefined.table_exprs(meta)) + .map(|(arg, table)| (condition.expr() * arg, table)) + .collect() + }); + // For every new symbol detected. meta.create_gate("FseTable: new symbol", |meta| { let condition = and::expr([ @@ -657,9 +673,7 @@ impl FseTable { // - state'' == state + (table_size >> 3) + (table_size >> 1) + 3 meta.lookup_any("FseTable: state transition", |meta| { let condition = and::expr([ - not::expr( - meta.query_fixed(config.fse_sorted_states_table.q_start, Rotation::cur()), - ), + not::expr(meta.query_fixed(config.sorted_table.q_start, Rotation::cur())), not::expr(meta.query_advice(config.is_prob_less_than1, Rotation::cur())), not::expr(meta.query_advice(config.is_padding, Rotation::cur())), ]); @@ -669,9 +683,8 @@ impl FseTable { + meta.query_advice(config.table_size_rs_3, Rotation::cur()) + meta.query_advice(config.table_size_rs_1, Rotation::cur()) + 3.expr(); - let table_size_minus_one = meta - .query_advice(config.fse_sorted_states_table.table_size, Rotation::cur()) - - 1.expr(); + let table_size_minus_one = + meta.query_advice(config.sorted_table.table_size, Rotation::cur()) - 1.expr(); [ BitwiseOp::AND.expr(), // op @@ -697,10 +710,11 @@ impl FseTable { /// This check can be done on any row within the FSE table. pub fn table_exprs_by_state(&self, meta: &mut VirtualCells) -> Vec> { vec![ - meta.query_fixed(self.fse_sorted_states_table.q_first, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_kind, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_size, Rotation::cur()), + meta.query_fixed(self.sorted_table.q_first, Rotation::cur()), + meta.query_advice(self.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(self.sorted_table.table_kind, Rotation::cur()), + meta.query_advice(self.sorted_table.table_size, Rotation::cur()), + meta.query_advice(self.sorted_table.is_predefined, Rotation::cur()), meta.query_advice(self.state, Rotation::cur()), meta.query_advice(self.symbol, Rotation::cur()), meta.query_advice(self.baseline, Rotation::cur()), @@ -716,10 +730,11 @@ impl FseTable { /// - symbol_count == symbol_count_acc pub fn table_exprs_by_symbol(&self, meta: &mut VirtualCells) -> Vec> { vec![ - meta.query_fixed(self.fse_sorted_states_table.q_first, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_kind, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_size, Rotation::cur()), + meta.query_fixed(self.sorted_table.q_first, Rotation::cur()), + meta.query_advice(self.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(self.sorted_table.table_kind, Rotation::cur()), + meta.query_advice(self.sorted_table.table_size, Rotation::cur()), + meta.query_advice(self.sorted_table.is_predefined, Rotation::cur()), meta.query_advice(self.symbol, Rotation::cur()), meta.query_advice(self.symbol_count, Rotation::cur()), meta.query_advice(self.symbol_count_acc, Rotation::cur()), @@ -732,11 +747,12 @@ impl FseTable { /// were correctly populated even at the "init-state" stage. pub fn table_exprs_metadata(&self, meta: &mut VirtualCells) -> Vec> { vec![ - meta.query_fixed(self.fse_sorted_states_table.q_first, Rotation::cur()), - meta.query_fixed(self.fse_sorted_states_table.q_start, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.block_idx, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_kind, Rotation::cur()), - meta.query_advice(self.fse_sorted_states_table.table_size, Rotation::cur()), + meta.query_fixed(self.sorted_table.q_first, Rotation::cur()), + meta.query_fixed(self.sorted_table.q_start, Rotation::cur()), + meta.query_advice(self.sorted_table.block_idx, Rotation::cur()), + meta.query_advice(self.sorted_table.table_kind, Rotation::cur()), + meta.query_advice(self.sorted_table.table_size, Rotation::cur()), + meta.query_advice(self.sorted_table.is_predefined, Rotation::cur()), meta.query_advice(self.is_padding, Rotation::cur()), ] } @@ -791,6 +807,14 @@ struct FseSortedStatesTable { table_kind: Column, /// The number of states in the FSE table, i.e. 1 << AL. table_size: Column, + /// A boolean to indicate whether the FSE table is the one constructed from predefined default + /// distributions. + /// For more information, refer the [default distributions][doclink1] and [predefined FSE + /// tables][doclink2] + /// + /// [doclink1]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#default-distributions + /// [doclink2]: https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#appendix-a---decoding-tables-for-predefined-codes + is_predefined: Column, /// The FSE symbol, starting at the first symbol with prob>=1. symbol: Column, /// Boolean column to mark if we are moving to the next symbol. @@ -839,6 +863,7 @@ impl FseSortedStatesTable { block_idx: meta.advice_column(), table_kind: meta.advice_column(), table_size: meta.advice_column(), + is_predefined: meta.advice_column(), symbol: meta.advice_column(), is_new_symbol: meta.advice_column(), symbol_count: meta.advice_column(), @@ -914,6 +939,11 @@ impl FseSortedStatesTable { 1.expr(), ); + cb.require_boolean( + "is_predefined is boolean", + meta.query_advice(config.is_predefined, Rotation::cur()), + ); + cb.gate(condition) }); @@ -1000,7 +1030,12 @@ impl FseSortedStatesTable { let mut cb = BaseConstraintBuilder::default(); // FSE table's columns that remain unchanged. - for column in [config.block_idx, config.table_kind, config.table_size] { + for column in [ + config.block_idx, + config.table_kind, + config.table_size, + config.is_predefined, + ] { cb.require_equal( "FseSortedStatesTable: columns that remain unchanged", meta.query_advice(column, Rotation::cur()), diff --git a/aggregator/src/aggregation/decoder/tables/rom_fse_order.rs b/aggregator/src/aggregation/decoder/tables/rom_fse_order.rs index 01654f2025..d9b7698013 100644 --- a/aggregator/src/aggregation/decoder/tables/rom_fse_order.rs +++ b/aggregator/src/aggregation/decoder/tables/rom_fse_order.rs @@ -5,6 +5,8 @@ use halo2_proofs::{ halo2curves::bn256::Fr, plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, }; +use itertools::Itertools; +use once_cell::sync::Lazy; use zkevm_circuits::table::LookupTable; use crate::aggregation::decoder::witgen::ZstdTag::{ @@ -31,11 +33,32 @@ impl_expr!(FseTableKind); /// Read-only table that allows us to check the correct assignment of FSE table kind. /// /// The possible orders are: -/// - [ZstdBlockSequenceHeader, ZstdBlockSequenceFseCode, ZstdBlockSequenceFseCode, LLT] -/// - [ZstdBlockSequenceFseCode, ZstdBlockSequenceFseCode, ZstdBlockSequenceFseCode, MOT] -/// - [ZstdBlockSequenceFseCode, ZstdBlockSequenceFseCode, ZstdBlockSequenceData, MLT] +/// +/// - (1, 1, 1): +/// - SequenceHeader > FseCode > FseCode (LLT) +/// - FseCode > FseCode > FseCode (MOT) +/// - FseCode > FseCode > SequenceData (MLT) +/// - (1, 1, 0): +/// - SequenceHeader > FseCode > FseCode (LLT) +/// - FseCode > FseCode > SequenceData (MOT) +/// - (1, 0, 1): +/// - SequenceHeader > FseCode > FseCode (LLT) +/// - FseCode > FseCode > SequenceData (MLT) +/// - (0, 1, 1): +/// - SequenceHeader > FseCode > FseCode (MOT) +/// - FseCode > FseCode > SequenceData (MLT) +/// - (1, 0, 0): +/// - SequenceHeader > FseCode > SequenceData (LLT) +/// - (0, 1, 0): +/// - SequenceHeader > FseCode > SequenceData (MOT) +/// - (0, 0, 1): +/// - SequenceHeader > FseCode > SequenceData (MLT) #[derive(Clone, Debug)] pub struct RomFseOrderTable { + /// Compression mode boolean flags for LLT, MOT and MLT respectively. + /// - Predefined_Mode > 0 + /// - Fse_Compressed_Mode > 1 + compression_modes: [Column; 3], /// The tag that occurred previously. tag_prev: Column, /// The current tag, expected to be ZstdBlockSequenceFseCode. @@ -49,6 +72,11 @@ pub struct RomFseOrderTable { impl RomFseOrderTable { pub fn construct(meta: &mut ConstraintSystem) -> Self { Self { + compression_modes: [ + meta.fixed_column(), + meta.fixed_column(), + meta.fixed_column(), + ], tag_prev: meta.fixed_column(), tag_cur: meta.fixed_column(), tag_next: meta.fixed_column(), @@ -58,55 +86,80 @@ impl RomFseOrderTable { /// Load the FSE order ROM table. pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + use crate::aggregation::decoder::witgen::ZstdTag::{ + ZstdBlockFseCode as FseCode, ZstdBlockLstream as SeqData, + ZstdBlockSequenceHeader as SeqHeader, + }; + use FseTableKind::{LLT, MLT, MOT}; + layouter.assign_region( || "(ROM): FSE order table", |mut region| { for (offset, row) in [ - ( - ZstdBlockSequenceHeader, - ZstdBlockSequenceFseCode, - ZstdBlockSequenceFseCode, - FseTableKind::LLT, - ), - ( - ZstdBlockSequenceFseCode, - ZstdBlockSequenceFseCode, - ZstdBlockSequenceFseCode, - FseTableKind::MOT, - ), - ( - ZstdBlockSequenceFseCode, - ZstdBlockSequenceFseCode, - ZstdBlockSequenceData, - FseTableKind::MLT, - ), + // (1, 1, 1) + (1, 1, 1, SeqHeader, FseCode, FseCode, LLT), + (1, 1, 1, FseCode, FseCode, FseCode, MOT), + (1, 1, 1, FseCode, FseCode, SeqData, MLT), + // (1, 1, 0) + (1, 1, 0, SeqHeader, FseCode, FseCode, LLT), + (1, 1, 0, FseCode, FseCode, SeqData, MOT), + // (1, 0, 1) + (1, 0, 1, SeqHeader, FseCode, FseCode, LLT), + (1, 0, 1, FseCode, FseCode, SeqData, MLT), + // (0, 1, 1) + (0, 1, 1, SeqHeader, FseCode, FseCode, MOT), + (0, 1, 1, FseCode, FseCode, SeqData, MLT), + // (1, 0, 0) + (1, 0, 0, SeqHeader, FseCode, SeqData, LLT), + // (0, 1, 0) + (0, 1, 0, SeqHeader, FseCode, SeqData, MOT), + // (0, 0, 1) + (0, 0, 1, SeqHeader, FseCode, SeqData, MLT), ] .iter() .enumerate() { + region.assign_fixed( + || format!("llt:compr_mode at offset={offset}"), + self.compression_modes[0], + offset, + || Value::known(Fr::from(row.0 as u64)), + )?; + region.assign_fixed( + || format!("mot:compr_mode at offset={offset}"), + self.compression_modes[1], + offset, + || Value::known(Fr::from(row.1 as u64)), + )?; + region.assign_fixed( + || format!("mlt:compr_mode at offset={offset}"), + self.compression_modes[2], + offset, + || Value::known(Fr::from(row.2 as u64)), + )?; region.assign_fixed( || format!("tag_prev at offset={offset}"), self.tag_prev, offset, - || Value::known(Fr::from(row.0 as u64)), + || Value::known(Fr::from(row.3 as u64)), )?; region.assign_fixed( || format!("tag_cur at offset={offset}"), self.tag_cur, offset, - || Value::known(Fr::from(row.1 as u64)), + || Value::known(Fr::from(row.4 as u64)), )?; region.assign_fixed( || format!("tag_next at offset={offset}"), self.tag_next, offset, - || Value::known(Fr::from(row.2 as u64)), + || Value::known(Fr::from(row.5 as u64)), )?; region.assign_fixed( || format!("table_kind at offset={offset}"), self.table_kind, offset, - || Value::known(Fr::from(row.3 as u64)), + || Value::known(Fr::from(row.6 as u64)), )?; } @@ -119,6 +172,9 @@ impl RomFseOrderTable { impl LookupTable for RomFseOrderTable { fn columns(&self) -> Vec> { vec![ + self.compression_modes[0].into(), + self.compression_modes[1].into(), + self.compression_modes[2].into(), self.tag_prev.into(), self.tag_cur.into(), self.tag_next.into(), @@ -128,6 +184,9 @@ impl LookupTable for RomFseOrderTable { fn annotations(&self) -> Vec { vec![ + String::from("llt:compression_mode"), + String::from("mot:compression_mode"), + String::from("mlt:compression_mode"), String::from("tag_prev"), String::from("tag_cur"), String::from("tag_next"), @@ -366,3 +425,377 @@ impl LookupTable for RomSequencesDataInterleavedOrder { ] } } + +pub trait FsePredefinedTable { + /// Get the accuracy log of the predefined table. + fn accuracy_log(&self) -> u8; + /// Get the number of states in the FSE table. + fn table_size(&self) -> u64 { + 1 << self.accuracy_log() + } + /// Get the symbol in the FSE table for the given state. + fn symbol(&self, state: u64) -> u64; + /// Get the baseline in the FSE table for the given state. + fn baseline(&self, state: u64) -> u64; + /// Get the number of bits (nb) to read from bitstream in the FSE table for the given state. + fn nb(&self, state: u64) -> u64; +} + +impl FsePredefinedTable for FseTableKind { + fn accuracy_log(&self) -> u8 { + match self { + Self::LLT => 6, + Self::MOT => 5, + Self::MLT => 6, + } + } + + fn symbol(&self, state: u64) -> u64 { + match self { + Self::LLT => match state { + 0..=1 => 0, + 2 => 1, + 3 => 3, + 4 => 4, + 5 => 6, + 6 => 7, + 7 => 9, + 8 => 10, + 9 => 12, + 10 => 14, + 11 => 16, + 12 => 18, + 13 => 19, + 14 => 21, + 15 => 22, + 16 => 24, + 17 => 25, + 18 => 26, + 19 => 27, + 20 => 29, + 21 => 31, + 22 => 0, + 23 => 1, + 24 => 2, + 25 => 4, + 26 => 5, + 27 => 7, + 28 => 8, + 29 => 10, + 30 => 11, + 31 => 13, + 32 => 16, + 33 => 17, + 34 => 19, + 35 => 20, + 36 => 22, + 37 => 23, + 38 => 25, + 39 => 25, + 40 => 26, + 41 => 28, + 42 => 30, + 43 => 0, + 44 => 1, + 45 => 2, + 46 => 3, + 47 => 5, + 48 => 6, + 49 => 8, + 50 => 9, + 51 => 11, + 52 => 12, + 53 => 15, + 54 => 17, + 55 => 18, + 56 => 20, + 57 => 21, + 58 => 23, + 59 => 24, + 60 => 35, + 61 => 34, + 62 => 33, + 63 => 32, + _ => unreachable!(), + }, + Self::MOT => match state { + 0 => 0, + 1 => 6, + 2 => 9, + 3 => 15, + 4 => 21, + 5 => 3, + 6 => 7, + 7 => 12, + 8 => 18, + 9 => 23, + 10 => 5, + 11 => 8, + 12 => 14, + 13 => 20, + 14 => 2, + 15 => 7, + 16 => 11, + 17 => 17, + 18 => 22, + 19 => 4, + 20 => 8, + 21 => 13, + 22 => 19, + 23 => 1, + 24 => 6, + 25 => 10, + 26 => 16, + 27 => 28, + 28 => 27, + 29 => 26, + 30 => 25, + 31 => 24, + _ => unreachable!(), + }, + Self::MLT => match state { + 0..=3 => state, + 4..=5 => state + 1, + 6 => 8, + 7 => 10, + 8 => 13, + 9 => 16, + 10 => 19, + 11 => 22, + 12 => 25, + 13 => 28, + 14 => 31, + 15 => 33, + 16 => 35, + 17 => 37, + 18 => 39, + 19 => 41, + 20 => 43, + 21 => 45, + 22..=25 => state - 21, + 26..=27 => state - 20, + 28 => 9, + 29 => 12, + 30 => 15, + 31 => 18, + 32 => 21, + 33 => 24, + 34 => 27, + 35 => 30, + 36 => 32, + 37 => 34, + 38 => 36, + 39 => 38, + 40 => 40, + 41 => 42, + 42 => 44, + 43..=44 => 1, + 45 => 2, + 46..=47 => state - 42, + 48 => 7, + 49 => 8, + 50 => 11, + 51 => 14, + 52 => 17, + 53 => 20, + 54 => 23, + 55 => 26, + 56 => 29, + 57 => 52, + 58 => 51, + 59 => 50, + 60 => 49, + 61 => 48, + 62 => 47, + 63 => 46, + _ => unreachable!(), + }, + } + } + + fn baseline(&self, state: u64) -> u64 { + match self { + Self::LLT => match state { + 0 => 0, + 1 => 16, + 2 => 32, + 3..=16 => 0, + 17 => 32, + 18..=21 | 23..=24 => 0, + 22 | 25 | 27 | 29 | 32 | 34 | 36 | 40 => 32, + 26 | 28 | 30..=31 | 33 | 35 | 37..=38 | 41..=42 | 53 | 60..=63 => 0, + 39 | 44 => 16, + 43 => 48, + 45..=52 | 54..=59 => 32, + _ => unreachable!(), + }, + Self::MOT => match state { + 0..=14 | 16..=19 | 21..=23 | 25..=31 => 0, + 15 | 20 | 24 => 16, + _ => unreachable!(), + }, + Self::MLT => match state { + 0..=1 | 3..=21 | 23 | 25 | 27..=42 | 50..=63 => 0, + 2 | 24 | 26 | 43 | 46..=49 => 32, + 22 | 45 => 16, + 44 => 48, + _ => unreachable!(), + }, + } + } + + fn nb(&self, state: u64) -> u64 { + match self { + Self::LLT => match state { + 0..=1 | 22..=23 | 38..=39 | 43..=44 => 4, + 2..=9 | 11..=18 | 24..=30 | 32..=37 | 40 | 45..=52 | 54..=59 => 5, + 10 | 19..=21 | 31 | 41..=42 | 53 | 60..=63 => 6, + _ => unreachable!(), + }, + Self::MOT => match state { + 0 | 2..=5 | 7..=10 | 12..=14 | 16..=19 | 21..=23 | 25..=31 => 5, + 1 | 6 | 11 | 15 | 20 | 24 => 4, + _ => unreachable!(), + }, + Self::MLT => match state { + 0 | 7..=21 | 28..=42 | 50..=63 => 6, + 1 | 22..=23 | 43..=45 => 4, + 2..=6 | 24..=27 | 46..=49 => 5, + _ => unreachable!(), + }, + } + } +} + +pub fn predefined_table(table_kind: FseTableKind) -> Vec<(u64, u64, u64, u64)> { + let table_size = table_kind.table_size(); + (0..table_size) + .map(|state| { + ( + state, + table_kind.symbol(state), + table_kind.baseline(state), + table_kind.nb(state), + ) + }) + .collect() +} + +pub fn predefined_table_values(table_kind: FseTableKind) -> Vec<[Value; 6]> { + let table_size = table_kind.table_size(); + (0..table_size) + .map(|state| { + let symbol = table_kind.symbol(state); + let baseline = table_kind.baseline(state); + let nb = table_kind.nb(state); + [ + Value::known(Fr::from(table_kind as u64)), + Value::known(Fr::from(table_size)), + Value::known(Fr::from(state)), + Value::known(Fr::from(symbol)), + Value::known(Fr::from(baseline)), + Value::known(Fr::from(nb)), + ] + }) + .collect() +} + +/// The Predefined Literal Length FSE table, as per default distributions. +/// +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#literals-length +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#literal-length-code +pub static FSE_PREDEFINED_LLT: Lazy; 6]>> = + Lazy::new(|| predefined_table_values(FseTableKind::LLT)); + +/// The Predefined Match Length FSE table, as per default distributions. +/// +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#match-length +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#match-length-code +pub static FSE_PREDEFINED_MLT: Lazy; 6]>> = + Lazy::new(|| predefined_table_values(FseTableKind::MLT)); + +/// The Predefined Match Offset FSE table, as per default distributions. +/// +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#offset-codes-1 +/// - https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#offset-code +pub static FSE_PREDEFINED_MOT: Lazy; 6]>> = + Lazy::new(|| predefined_table_values(FseTableKind::MOT)); + +#[derive(Clone, Debug)] +pub struct RomFsePredefinedTable { + table_kind: Column, + table_size: Column, + state: Column, + symbol: Column, + baseline: Column, + nb: Column, +} + +impl RomFsePredefinedTable { + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + table_kind: meta.fixed_column(), + table_size: meta.fixed_column(), + state: meta.fixed_column(), + symbol: meta.fixed_column(), + baseline: meta.fixed_column(), + nb: meta.fixed_column(), + } + } + + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_region( + || "ROM: fse predefined", + |mut region| { + for (offset, row) in [ + FSE_PREDEFINED_LLT.as_slice(), + FSE_PREDEFINED_MLT.as_slice(), + FSE_PREDEFINED_MOT.as_slice(), + ] + .concat() + .iter() + .enumerate() + { + for ((&column, annotation), &value) in Self::fixed_columns(self) + .iter() + .zip_eq(Self::annotations(self)) + .zip_eq(row.iter()) + { + region.assign_fixed( + || format!("{annotation} at offset={offset}"), + column, + offset, + || value, + )?; + } + } + + Ok(()) + }, + ) + } +} + +impl LookupTable for RomFsePredefinedTable { + fn columns(&self) -> Vec> { + vec![ + self.table_kind.into(), + self.table_size.into(), + self.state.into(), + self.symbol.into(), + self.baseline.into(), + self.nb.into(), + ] + } + + fn annotations(&self) -> Vec { + vec![ + String::from("table_kind"), + String::from("table_size"), + String::from("state"), + String::from("symbol"), + String::from("baseline"), + String::from("nb"), + ] + } +} diff --git a/aggregator/src/aggregation/decoder/witgen/types.rs b/aggregator/src/aggregation/decoder/witgen/types.rs index 83dabe6a0b..fb7afd742d 100644 --- a/aggregator/src/aggregation/decoder/witgen/types.rs +++ b/aggregator/src/aggregation/decoder/witgen/types.rs @@ -580,7 +580,6 @@ type FseStateMapping = BTreeMap; type ReconstructedFse = (usize, Vec<(u32, u64)>, FseAuxiliaryTableData); impl FseAuxiliaryTableData { - #[allow(non_snake_case)] /// While we reconstruct an FSE table from a bitstream, we do not know before reconstruction /// how many exact bytes we would finally be reading. /// @@ -588,6 +587,7 @@ impl FseAuxiliaryTableData { /// with the reconstructed FSE table. After processing the entire bitstream to reconstruct the /// FSE table, if the read bitstream was not byte aligned, then we discard the 1..8 bits from /// the last byte that we read from. + #[allow(non_snake_case)] pub fn reconstruct( src: &[u8], block_idx: u64, @@ -687,9 +687,44 @@ impl FseAuxiliaryTableData { )); } + // sanity check: sum(probabilities) == table_size. + assert_eq!( + normalised_probs + .values() + .map(|&prob| if prob == -1 { 1u64 } else { prob as u64 }) + .sum::(), + table_size + ); + //////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////// Allocate States to Symbols /////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// + let (sym_to_states, sym_to_sorted_states) = + Self::transform_normalised_probs(&normalised_probs, accuracy_log); + + Ok(( + t, + bit_boundaries, + Self { + block_idx, + table_kind, + table_size, + sym_to_states, + sym_to_sorted_states, + }, + )) + } + + #[allow(non_snake_case)] + fn transform_normalised_probs( + normalised_probs: &BTreeMap, + accuracy_log: u8, + ) -> ( + BTreeMap>, + BTreeMap>, + ) { + let table_size = 1 << accuracy_log; + let mut sym_to_states = BTreeMap::new(); let mut sym_to_sorted_states = BTreeMap::new(); let mut state = 0; @@ -701,7 +736,7 @@ impl FseAuxiliaryTableData { .iter() .filter(|(_symbol, &prob)| prob == -1) { - allocated_states.insert(symbol, true); + allocated_states.insert(retreating_state, true); let fse_table_row = FseTableRow { state: retreating_state, num_bits: accuracy_log as u64, @@ -799,17 +834,7 @@ impl FseAuxiliaryTableData { ); } - Ok(( - t, - bit_boundaries, - Self { - block_idx, - table_kind, - table_size, - sym_to_states, - sym_to_sorted_states, - }, - )) + (sym_to_states, sym_to_sorted_states) } /// Convert an FseAuxiliaryTableData into a state-mapped representation. @@ -866,6 +891,8 @@ impl ZstdWitnessRow { #[cfg(test)] mod tests { + use crate::aggregation::decoder::tables::{predefined_table, FsePredefinedTable}; + use super::*; #[test] @@ -910,6 +937,82 @@ mod tests { Ok(()) } + #[test] + fn test_fse_reconstruction_predefined_tables() { + // Here we test whether we can actually reconstruct the FSE table for distributions that + // include prob=-1 cases, one such example is the Predefined FSE table as per + // specifications. + // + // short literalsLength_defaultDistribution[36] = + // { 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + // 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, + // -1,-1,-1,-1 }; + // + // short matchLengths_defaultDistribution[53] = + // { 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + // 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, + // -1,-1,-1,-1,-1 }; + // + // short offsetCodes_defaultDistribution[29] = + // { 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + // 1, 1, 1, 1, 1, 1, 1, 1,-1,-1,-1,-1,-1 }; + let default_distribution_llt = vec![ + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, + 1, 1, 1, -1, -1, -1, -1, + ]; + let default_distribution_mlt = vec![ + 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, + ]; + let default_distribution_mot = vec![ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, + -1, + ]; + + for (table_kind, default_distribution) in [ + (FseTableKind::LLT, default_distribution_llt), + (FseTableKind::MLT, default_distribution_mlt), + (FseTableKind::MOT, default_distribution_mot), + ] { + let normalised_probs = { + let mut normalised_probs = BTreeMap::new(); + for (i, &prob) in default_distribution.iter().enumerate() { + normalised_probs.insert(i as u64, prob); + } + normalised_probs + }; + let (sym_to_states, _sym_to_sorted_states) = + FseAuxiliaryTableData::transform_normalised_probs( + &normalised_probs, + table_kind.accuracy_log(), + ); + let expected_predefined_table = predefined_table(table_kind); + + let mut computed_predefined_table = sym_to_states + .values() + .flatten() + .filter(|row| !row.is_state_skipped) + .collect::>(); + computed_predefined_table.sort_by_key(|row| row.state); + + for (i, (expected, computed)) in expected_predefined_table + .iter() + .zip_eq(computed_predefined_table.iter()) + .enumerate() + { + assert_eq!(computed.state, expected.0, "state mismatch at i={}", i); + assert_eq!(computed.symbol, expected.1, "symbol mismatch at i={}", i); + assert_eq!( + computed.baseline, expected.2, + "baseline mismatch at i={}", + i + ); + assert_eq!(computed.num_bits, expected.3, "nb mismatch at i={}", i); + } + } + } + #[test] fn test_sequences_fse_reconstruction() -> std::io::Result<()> { let src = vec![