Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
kind: Changed
body: |-
**Table-codec messages with 255 or more fields keep the dense lookup for low field numbers.** Fields below 64 now use the same direct lookup as smaller messages instead of falling back to binary search. Encoded bytes and decode semantics are unchanged.
time: 2026-10-02T20:59:00+02:00
14 changes: 7 additions & 7 deletions buffa-codegen/src/table_codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ pub(crate) fn generate_table_impl(
}

/// `dense[n]` is one plus the index of the field numbered `n`, or `0`, for
/// the numbers up to the highest field number below 64. Empty, so that the
/// table searches instead, for a message with 255 fields or more, because
/// `Table::new` accepts a dense array only when every index fits below 255.
/// the numbers up to the highest field number below 64.
///
/// Entries are sorted by field number, and protobuf field numbers start at
/// one, so a field numbered below 64 is necessarily among the first 63
/// entries. Its one-based index therefore fits in `u8` regardless of the
/// total number of fields.
fn dense_lookup(fields: &[TableField<'_>]) -> Vec<u8> {
if fields.len() >= 255 {
return Vec::new();
}
let top = fields
.iter()
.map(|f| f.number)
Expand All @@ -158,7 +158,7 @@ fn dense_lookup(fields: &[TableField<'_>]) -> Vec<u8> {
let mut dense = vec![0u8; top + 1];
for (i, f) in fields.iter().enumerate() {
if let Some(slot) = dense.get_mut(f.number as usize) {
// At most 254 fields, so this fits.
// A field numbered below 64 is among the first 63 sorted entries.
*slot = (i + 1) as u8;
}
}
Expand Down
19 changes: 19 additions & 0 deletions buffa-codegen/src/tests/table_codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,25 @@ fn field_numbers_index_the_dense_array_and_missing_ones_are_zero() {
assert!(plain.contains("dense=&[0u8,1u8,0u8,2u8]"), "{plain}");
}

#[test]
fn wide_messages_keep_dense_lookup_for_low_field_numbers() {
let mut file = schema();
file.message_type[0].field = (1..=300)
.map(|n| scalar(&format!("f{n}"), n, Type::TYPE_INT32))
.collect();
let (files, _) = generate_with_diagnostics(
&[file],
&["t.proto".to_string()],
&table_config(CodecStrategy::Table),
)
.unwrap();
let code = squashed(&joined(&files));
let plain = code.split("static__BUFFA_TABLE_Plain").nth(1).unwrap();
let table = plain.split("impl::buffa::Message").next().unwrap();
assert!(table.contains("dense=&[0u8,1u8,2u8,3u8"), "{table}");
assert!(table.contains(",63u8]"), "{table}");
}

#[test]
fn the_warning_texts_say_what_to_do() {
let rule = CodeGenWarning::CodecStrategyRuleMatchedNothing {
Expand Down
3 changes: 1 addition & 2 deletions buffa-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ fn read_proto(file: &str) -> String {
}

/// A message with 300 fields, of which 280 are messages, so that a table needs
/// more than 255 entries (which turns off its dense array) and more than 255
/// field descriptors.
/// more than 255 entries and more than 255 field descriptors.
fn wide_proto() -> String {
let mut proto = String::from(
"syntax = \"proto3\";\npackage wide;\nmessage Leaf { int32 x = 1; }\nmessage Wide {\n",
Expand Down
8 changes: 2 additions & 6 deletions buffa/src/table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,8 @@ impl<M> Table<M> {
///
/// `abi` is the [`ABI`] the caller was generated for. `entries` are the
/// fields sorted by number; `dense` is the lookup array described on
/// [`MessageTable`], which must be empty if there are 255 entries or more;
/// `aux` holds the descriptors that entries index by their `aux` value;
/// [`MessageTable`]; `aux` holds the descriptors that entries index by
/// their `aux` value;
/// `unknown` is the offset of the message's `UnknownFields`, if it keeps
/// any.
///
Expand Down Expand Up @@ -530,10 +530,6 @@ impl<M> Table<M> {
}
i += 1;
}
assert!(
dense.is_empty() || entries.len() < 255,
"buffa table: the dense lookup needs fewer than 255 entries"
);
// Every dense slot names the entry with its number, and every entry
// below the dense range has a slot, so the two agree.
let mut named = 0;
Expand Down
Loading