Skip to content

GH-50658: [C++][Dev] Add RunEndEncoded support to gdb_arrow.py - #50768

Open
fenfeng9 wants to merge 3 commits into
apache:mainfrom
fenfeng9:gh-50658-run-end-encoded-gdb
Open

fenfeng9 wants to merge 3 commits into
apache:mainfrom
fenfeng9:gh-50658-run-end-encoded-gdb

Conversation

@fenfeng9

@fenfeng9 fenfeng9 commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Rationale for this change

The Arrow GDB pretty-printers do not support run-end encoded data types.

What changes are included in this PR?

Add GDB pretty-printer support and tests for run-end encoded types, scalars, and arrays.

Are these changes tested?

Yes.

Are there any user-facing changes?

No.

@fenfeng9

fenfeng9 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Here are some examples from a local GDB session.

RunEndEncodedType

RunEndEncodedType run_end_encoded_type(int32(), utf8());
auto heap_run_end_encoded_type =
    run_end_encoded(int32(), utf8());

(gdb) p run_end_encoded_type
$1 = arrow::run_end_encoded(arrow::int32(), arrow::utf8())

(gdb) p *heap_run_end_encoded_type
$2 = (std::__shared_ptr_access<arrow::DataType, (__gnu_cxx::_Lock_policy)2, false, false>::element_type &) @0x555555f52f10:
     arrow::run_end_encoded(arrow::int32(), arrow::utf8())

RunEndEncodedScalar

auto run_end_encoded_scalar_type =
    run_end_encoded(int32(), utf8());

RunEndEncodedScalar run_end_encoded_scalar{
    MakeScalar("foo"), run_end_encoded_scalar_type};

RunEndEncodedScalar run_end_encoded_scalar_null{
    run_end_encoded_scalar_type};

(gdb) p run_end_encoded_scalar
$3 = arrow::RunEndEncodedScalar of value
     arrow::StringScalar of size 3, value "foo"

(gdb) p run_end_encoded_scalar_null
$4 = arrow::RunEndEncodedScalar of type
     arrow::run_end_encoded(arrow::int32(), arrow::utf8()),
     null value

RunEndEncodedArray

// Encodes ["foo", "foo", null, null, null].
auto run_end_encoded_run_ends =
    SliceArrayFromJSON(int32(), "[2, 5]");
auto run_end_encoded_values =
    SliceArrayFromJSON(utf8(), R"(["foo", null])");

auto heap_run_end_encoded_array =
    *RunEndEncodedArray::Make(
        5, run_end_encoded_run_ends,
        run_end_encoded_values);

auto heap_run_end_encoded_array_sliced =
    heap_run_end_encoded_array->Slice(1, 3);

(gdb) p *heap_run_end_encoded_array
$5 = (std::__shared_ptr_access<arrow::Array, (__gnu_cxx::_Lock_policy)2, false, false>::element_type &) @0x555555fab440:
     arrow::RunEndEncodedArray of type
     arrow::run_end_encoded(arrow::int32(), arrow::utf8()),
     length 5, offset 0, null count 0

(gdb) p *heap_run_end_encoded_array_sliced
$6 = (std::__shared_ptr_access<arrow::Array, (__gnu_cxx::_Lock_policy)2, false, false>::element_type &) @0x555555f7dd60:
     arrow::RunEndEncodedArray of type
     arrow::run_end_encoded(arrow::int32(), arrow::utf8()),
     length 3, offset 1, null count 0

@fenfeng9

fenfeng9 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

The null count 0 output is expected here and follows the existing ArrayData behavior. It counts only nulls in the top-level array. Run-end encoded arrays store nulls in the values child array, so this example decodes to three nulls even though the printer shows 0.

@pitrou pitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really sorry for the delay @fenfeng9 , I had forgotten about this PR. Here are some comments.

Comment thread cpp/gdb_arrow.py Outdated
class RunEndEncodedTypeClass(DataTypeClass):
is_parametric = True
type_printer = RunEndEncodedTypePrinter
scalar_printer = BaseListScalarPrinter

@pitrou pitrou Sep 22, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (using BaseListScalarPrinter) might work by chance, but I don't think this is conceptually right. Run-end-encoded is not a list type, and a run-end-encoded scalar represents a single child value, not an entire run of values.

(as the tests show, by the way)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, defined a RunEndEncodedScalarPrinter.

Comment thread cpp/gdb_arrow.py
Comment thread cpp/gdb_arrow.py Outdated
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 22, 2026
@fenfeng9

Copy link
Copy Markdown
Contributor Author

Really sorry for the delay @fenfeng9 , I had forgotten about this PR. Here are some comments.

No worries, @pitrou! Thanks for the review — I'll address these shortly.

@fenfeng9
fenfeng9 force-pushed the gh-50658-run-end-encoded-gdb branch from c9a6c64 to 36d4c88 Compare September 27, 2026 20:06
@fenfeng9
fenfeng9 force-pushed the gh-50658-run-end-encoded-gdb branch from 36d4c88 to 326e0ce Compare September 27, 2026 20:26
Comment thread cpp/gdb_arrow.py Outdated
Comment on lines +1889 to +1896
for i in range(self.length):
target = bisect_right(run_ends, self.offset + i)
while physical_index <= target:
value = next(values, None)
if value is None:
return
physical_index += 1
yield self._valid_child(i, value[1])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bisecting each logical value is inefficient while we can just loop over physical values and run ends in order. Something like:

Suggested change
for i in range(self.length):
target = bisect_right(run_ends, self.offset + i)
while physical_index <= target:
value = next(values, None)
if value is None:
return
physical_index += 1
yield self._valid_child(i, value[1])
run_start = run_ends[self.offset - 1] if self.offset > 0 else 0
for i in range(self.length):
run_end = run_ends[self.offset + i]
value = next(values, None)
if value is None:
return
for logical_index in range(run_start, run_end):
yield self._valid_child(logical_index, value[1])
run_start = run_end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I now use one bisect_right() call, then iterate through the runs in order.

Comment thread cpp/gdb_arrow.py
Comment on lines +775 to +776
# E.g. offset=3, length=6 selects bits 3..8 and needs 2 bytes.
byte_length = math.ceil((bit_offset + length) / 8)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this fixes a bug, can you add a test that exercises it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I will address these two comments later today.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing BooleanArray slice tests already cover this case:

auto heap_bool_array_sliced_1_9 =
    SliceArrayFromJSON(boolean(), json_bool_array, 1, 9);
auto heap_bool_array_sliced_2_6 =
    SliceArrayFromJSON(boolean(), json_bool_array, 2, 6);

They exercise Bitmap.from_buffer() with non-byte-aligned offsets and ranges that cross byte boundaries.

Before this change, Buffer.bytes_view() ignored the requested length:

if length is None:
    length = self.size
mem = gdb.selected_inferior().read_memory(
    self.val['data_'] + offset, self.size)

It always read self.size bytes. Now it reads the requested length:

mem = gdb.selected_inferior().read_memory(
    self.val['data_'] + offset, length)

This previously masked the incorrect byte-length calculation in Bitmap.from_buffer(). Since the existing tests already cover this path, I did not add another test case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants