Skip to content

GH-50994: [C++][Compute] Implement casting from ListView to List with zero-copy fast-path - #50976

Open
Jay846 wants to merge 11 commits into
apache:mainfrom
Jay846:feature/list-view-to-list-cast
Open

GH-50994: [C++][Compute] Implement casting from ListView to List with zero-copy fast-path#50976
Jay846 wants to merge 11 commits into
apache:mainfrom
Jay846:feature/list-view-to-list-cast

Conversation

@Jay846

@Jay846 Jay846 commented Aug 24, 2026

Copy link
Copy Markdown

Rationale for this change

This PR implements missing type-casting compute kernels to convert ListViewType and LargeListViewType arrays to standard ListType and LargeListType arrays.

Previously, these casts routed to CastList, which ignored the sizes buffer and read offsets out-of-bounds, resulting in corrupted output arrays. This PR introduces a dedicated CastListView functor to perform correct conversions.

What changes are included in this PR?

To maximize performance and optimize memory layouts, a dual-execution path was implemented in the CastListView execution functor inside scalar_cast_nested.cc:

  1. Contiguous Zero-Copy Fast-Path: Triggered when the input list-view elements are contiguous and adjacent (i.e. offsets[i] + sizes[i] == offsets[i+1]). It avoids copying the child values array entirely, allocating the new output offset buffer, shifting offsets relative to the start, and slicing the child array directly to preserve zero-copy pointer semantics.
  2. Non-Contiguous Fallback Path: Triggered for gapped, overlapping, or out-of-order layouts. It dynamically tracks target offsets, builds integer index mappings using an Int64Builder, and invokes Arrow's internal take compute kernel to reconstruct a new contiguous child values array.
  3. Kernel Registration: Formally wired into Arrow's casting system for standard and large variants of both nested types, replacing the incorrect routing.

Are these changes tested?

Yes, added comprehensive unit test suites in scalar_cast_test.cc passing all cases. Tests explicitly cover:

  • Contiguous Arrays: Validated zero-copy memory behavior programmatically by ensuring the source and destination child buffers share the exact same physical memory address.
  • Gapped & Overlapping Arrays: Verified correct offset tracking and reconstruction when offsets intersect or contain unused slots.
  • Null Propagation: Checked that null validity maps propagate correctly at both the parent and child levels.
  • Generic/Nested Types: Validated casting on non-primitive data types (e.g. utf8 strings) and recursive child type promotions (e.g., ListView<int16> to List<int32>).
  • Boundary Slices: Verified that taking slices of non-contiguous arrays computes correct offsets and avoids out-of-bound errors.

Are there any user-facing changes?

No public API contracts were broken. This adds correct, declarative casting support natively to the existing internal compute framework.

@Jay846
Jay846 requested a review from pitrou as a code owner August 24, 2026 20:21
@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

This pull request has been automatically converted to a draft because its title doesn't match Arrow's required format.

If this is not a minor PR. Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose

Opening GitHub issues ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename the pull request title in the following format?

GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

After updating the title, you can mark the pull request as ready for review.

See also:

@github-actions
github-actions Bot marked this pull request as draft August 24, 2026 20:21

@Reranko05 Reranko05 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you use the Arrow PR title template:-

GH-<Issue Number>: [<Component>] <Title>

@Jay846 Jay846 changed the title feat(compute): implement casting from list_view to list [C++][Compute] Implement casting from ListView to List with zero-copy fast-path Aug 24, 2026
@Jay846 Jay846 changed the title [C++][Compute] Implement casting from ListView to List with zero-copy fast-path GH-50976: [C++][Compute] Implement casting from ListView to List with zero-copy fast-path Aug 24, 2026
@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #50976 has been automatically assigned in GitHub to PR creator.

@Jay846
Jay846 marked this pull request as ready for review August 24, 2026 22:54
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Aug 25, 2026
@HuaHuaY

HuaHuaY commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

You should use issue id but not the PR's id. If there isn't an existed issue, you can create a new one.

@Jay846 Jay846 changed the title GH-50976: [C++][Compute] Implement casting from ListView to List with zero-copy fast-path GH-50994: [C++][Compute] Implement casting from ListView to List with zero-copy fast-path Aug 25, 2026
@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #50994 has been automatically assigned in GitHub to PR creator.

@Jay846

Jay846 commented Aug 26, 2026

Copy link
Copy Markdown
Author

Hi @pitrou, I've updated the PR title to match the tracking issue (GH-50994), removed the unused compile variables, and applied clang-format styling. The failures in macOS GLib and Windows/Conda seem to be flaky Flight and S3FS/MinIO test failures. Could we please trigger a re-run of the checks? Thanks!

@HuaHuaY

HuaHuaY commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

The failures in macOS GLib and Windows/Conda seem to be flaky Flight and S3FS/MinIO test failures.

Some CI tests are currently failing to run, but this is unrelated to the changes in this PR. Don't worry.

@HuaHuaY
HuaHuaY self-requested a review August 26, 2026 05:04
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_test.cc
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_test.cc
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
@Jay846

Jay846 commented Aug 26, 2026

Copy link
Copy Markdown
Author

Also pushed a quick style formatting update to satisfy the pre-commit linter check. Kindly approve for re-run. Thanks

@HuaHuaY

HuaHuaY commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

https://www.githubstatus.com/ shows that GitHub Actions is currently experiencing some issues. Let's wait for it to recover.

@HuaHuaY HuaHuaY left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left a few comments. I'm not sure about the check for in_array.length == 0. The rest looks good to me.

Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
Comment thread cpp/src/arrow/compute/kernels/scalar_cast_nested.cc Outdated
const ArraySpan& in_array = batch[0].array;
ArrayData* out_array = out->array_data().get();

if (in_array.length == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I may have a mistake during the previous review. I am not sure whether this if condition will always evaluate to false due to the check at cpp/src/arrow/compute/exec.cc:786; perhaps we can assume here that in_array.length is never 0. Let's wait for comments from a reviewer who is more familiar with Arrow Compute.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Got it, using if constexpr makes perfect sense here. I'll update those two checks.

For the length == 0 condition, I'll leave the check in place for now as a safeguard and wait for input from other maintainers on whether empty batches can reach this execution path. Thanks

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 think we can replace this with a DCHECK_NE(in_array.length, 0) and ensure that the tests exercise zero-length arrays and chunked arrays.

@Jay846

Jay846 commented Aug 27, 2026

Copy link
Copy Markdown
Author

Good morning, Is any more changes from my side needed, do respond whenever you have time. Thanks!

@Jay846

Jay846 commented Aug 31, 2026

Copy link
Copy Markdown
Author

Hi everyone, It is an follow up regarding previous message of any more changes needed. Thanks

@Jay846

Jay846 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Hi @pitrou, I would genuinely love to help incase of any error present from my side before merging it as currently according to my best knowledge all issues are addressed. Thank you

@pitrou

pitrou commented Sep 8, 2026

Copy link
Copy Markdown
Member

@Jay846 Sorry for the delay. I'll take a look when I have time, but please also read our policy on AI generated code and prose.

@Jay846

Jay846 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Sure @pitrou, I'll have a look into the policies. Thanks

@pitrou

pitrou commented Sep 10, 2026

Copy link
Copy Markdown
Member

Sure @pitrou, I'll have a look into the policies. Thanks

As you've probably read by now, these guidelines ask that you disclose usage of AI in your submissions, so that we understand what has been produced and/or vetted by a human. Can you please do so?

@Jay846

Jay846 commented Sep 10, 2026

Copy link
Copy Markdown
Author

Hi @pitrou, thanks for the reminder. I'll be transparent: I used an AI coding assistant (specifically Google's Antigravity) as a pair-programming tool throughout this PR where needed.

What the AI helped with: initial kernel structure and formatting in suggestions.

What I personally owned: understanding the bug (corrupted offsets from ignored sizes buffer), verifying the zero-copy fast-path logic and buffer pointer equality test, running and validating the unit tests locally after each change, and reviewing every iteration of the code before pushing.

I reviewed and understood every line before committing. Happy to answer any specific questions about the implementation.

@pitrou

pitrou commented Sep 10, 2026

Copy link
Copy Markdown
Member

Thank you @Jay846 !

static bool IsContiguous(const ArraySpan& in_array) {
const auto* offsets = in_array.GetValues<src_offset_type>(1);
const auto* sizes = in_array.GetValues<src_offset_type>(2);
for (int64_t i = 0; i < in_array.length - 1; ++i) {

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.

You could perhaps use SetBitRunReader to speed up walking the validity bitmap (individual IsNull calls are more expensive), though that's not necessary either.

const ArraySpan& in_array = batch[0].array;
ArrayData* out_array = out->array_data().get();

if (in_array.length == 0) {

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 think we can replace this with a DCHECK_NE(in_array.length, 0) and ensure that the tests exercise zero-length arrays and chunked arrays.

DCHECK_OK(func->AddKernel(SrcType::type_id, std::move(kernel)));
}

template <typename SrcType, typename DestType>

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.

Let's add a comment summarizing this:

Suggested change
template <typename SrcType, typename DestType>
// (Large)ListView<T> -> (Large)List<U>
template <typename SrcType, typename DestType>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added // (Large)ListView -> (Large)List comment before the template

Comment on lines +199 to +203
// Zero-copy fast-path: shift offsets and slice child values
ARROW_ASSIGN_OR_RAISE(
out_array->buffers[1],
ctx->Allocate(sizeof(dest_offset_type) * (in_array.length + 1)));
auto* dest_offsets = out_array->GetMutableValues<dest_offset_type>(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.

This is the same as below and can be factored out of the if/else branch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Factored out offset buffer allocation above the if/else branch

if (in_array.IsNull(i) && sizes[i] != 0) {
return false;
}
if (offsets[i] + sizes[i] != offsets[i + 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.

If the entry is null or zero-sized, then the exact value of offsets[i] shouldn't matter and we can instead keep the value of the last non-null non-zero entry?

(this is not a bug of course, just an additional optimization opportunity)

Comment on lines +230 to +239
src_offset_type current_offset = 0;
dest_offsets[0] = 0;
for (int64_t i = 0; i < in_array.length; ++i) {
if (in_array.IsNull(i)) {
dest_offsets[i + 1] = static_cast<dest_offset_type>(current_offset);
} else {
current_offset += sizes[i];
dest_offsets[i + 1] = static_cast<dest_offset_type>(current_offset);
}
}

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 think you could simplify the implementation by having the same loop offsets for both branches. That loop would compute all destination offsets and compute whether the source entries are contiguous, all in one go.

The contiguity information is mostly useful to know how to compute values afterwards. It needn't affect the computation of destination offsets, which has roughly the same costs in both cases.

}

template <typename SrcType, typename DestType>
struct CastListView {

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.

Call this CallListViewToVarList to make sure it's not used for casting to another list-view type?

CheckCast(contiguous_src, large_contiguous_expected);
CheckCast(large_contiguous_src, contiguous_expected);

// 5. Null Propagation

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.

Can you add nulls in the examples above? This will probably stress more situations.

CheckCast(contiguous_src, contiguous_expected);

// Assert zero-copy for contiguous values
ASSERT_OK_AND_ASSIGN(auto cast_result, Cast(contiguous_src, list(int16())));

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.

Can you call ValidateFull on the cast result?

*null_val_src_values));
auto null_val_src_masked = MaskArrayWithNullsAt(null_val_src, {1});
auto null_val_expected = ArrayFromJSON(list(int16()), "[[10], null]");
CheckCast(null_val_src_masked, null_val_expected);

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.

Can you add a test with zero-length inputs?

@Jay846 Jay846 Sep 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the detailed review @pitrou!

I'll address all the mandatory items:

  • Replace the length == 0 check with DCHECK_NE and add zero-length test coverage
  • Add the template comment // (Large)ListView -> (Large)List
  • Rename the struct to CastListViewToVarList
  • Factor out the offset allocation above the if/else branch
  • Simplify the end_offset calculation and the slice call
  • Add ValidateFull and null entries to the tests
  • Added null entries to the contiguous test examples
  • Added a zero-length input test case

The SetBitRunReader optimization and unified loop are noted as future improvements. Will push the fixes very shortly!

@Jay846

Jay846 commented Sep 10, 2026

Copy link
Copy Markdown
Author

Hi @pitrou and @HuaHuaY,

As informed prior I have pushed the updates incorporating all of @pitrou's feedback in commit 6bd6303 (renamed struct to CastListViewToVarList, added template documentation, added DCHECK_NE, factored out offset allocation, simplified end_offset, and expanded test cases with nulls and zero-length arrays).

I noticed the Dev / Lint check failed due to a minor clang-format formatting preference. I can push a quick formatting-only commit to make Dev / Lint green whenever you'd like me to, along to that other 4 are Flaky / Infrastructure Failures as of my best knowledge. Please let me know if any other adjustments are needed and also correct me too if I am wrong anywhere!

@HuaHuaY

HuaHuaY commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

I noticed the Dev / Lint check failed due to a minor clang-format formatting preference. I can push a quick formatting-only commit to make Dev / Lint green whenever you'd like me to

Please push a commit to fix Dev / Lint failure. Any CI failures introduced by this PR needs to be fixed, excluding the existed CI failures.

@Jay846

Jay846 commented Sep 11, 2026

Copy link
Copy Markdown
Author

I've cleaned up the code formatting in the latest commit (5d7c759). All linter and code formatting checks are ready for final review. Thanks!

@HuaHuaY

HuaHuaY commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

The SetBitRunReader optimization and unified loop are noted as future improvements. Will push the fixes very shortly!

I think you might have forgotten to submit the commits related to these?

@Jay846

Jay846 commented Sep 11, 2026

Copy link
Copy Markdown
Author

Hi @HuaHuaY apologies for the confusion! Since mentioned in chat SetBitRunReader wasn't strictly necessary, I saved those optimizations for a separate follow-up PR to keep this one focused. But as mentioned now , I am doing it shortly.

@Jay846

Jay846 commented Sep 11, 2026

Copy link
Copy Markdown
Author

I've pushed commit b312f28 to address the Dev / Lint formatting check.

Summary of changes:

  • Linter Formatting (b312f28): Formatted line wrapping on ARROW_ASSIGN_OR_RAISE and wrapped comment lengths to strictly adhere to clang-format (90-char limit).
    All unit tests pass cleanly and all mandatory items are complete. Ready for final review. Thanks!

@Jay846

Jay846 commented Sep 11, 2026

Copy link
Copy Markdown
Author

Hi @pitrou and @HuaHuaY, As the errors are mostly fixed, for Glib & Ruby / AMD64 a re-run will fix it upto my knowledge, any inputs from your side. Thanks!

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants