Skip to content

Handle non-trivially-copyable types in Loop/Scan output concatenation - #29397

Merged
yuslepukhin merged 2 commits into
mainfrom
yuslepukhin/loop_operator_gaps
Jun 30, 2026
Merged

Handle non-trivially-copyable types in Loop/Scan output concatenation#29397
yuslepukhin merged 2 commits into
mainfrom
yuslepukhin/loop_operator_gaps

Conversation

@yuslepukhin

Copy link
Copy Markdown
Contributor

This pull request improves the handling of string tensors in the CPU implementation of the Loop operator, ensuring correct memory management and copy semantics for non-trivially-copyable types like std::string. It also adds comprehensive unit tests to verify these behaviors, especially for cases involving string scan outputs and loop-carried variables.

Enhancements for string tensor support:

  • Updated ConcatenateCpuOutput in loop.cc to properly detect string tensors and use std::copy for concatenation, ensuring correct handling of heap-allocated string payloads and avoiding unsafe byte-wise copying. [1] [2]
  • Modified OutputIterator::ZeroOutCurrent in scan_utils.h to skip zeroing for string tensors, as their elements are already default-constructed and cannot be safely set with memset.

New and extended unit tests for string tensor scenarios:

  • Added tests to loop_test.cc covering:
    • String scan outputs, including long strings that exceed the small-string-optimization threshold and multi-element outputs.
    • Loop-carried string variables to ensure they are copied correctly.
    • Zero-iteration cases to confirm empty string scan outputs are handled without errors.

Copilot AI 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.

Pull request overview

This PR fixes CPU Loop scan-output concatenation for non-trivially-copyable element types (notably std::string) by avoiding byte-wise copying/zeroing that can corrupt string objects, and adds unit tests to validate correct behavior for string scan outputs and loop-carried variables.

Changes:

  • Update Loop CPU scan-output concatenation to use std::copy for tensor(string) outputs instead of byte-wise copying.
  • Update Scan OutputIterator::ZeroOutCurrent to skip zeroing for string tensors (since memset is unsafe for std::string).
  • Add Loop CPU provider tests covering string scan outputs (single/multi-element), loop-carried strings, and zero-iteration behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
onnxruntime/core/providers/cpu/controlflow/loop.cc Use proper element-wise copy semantics when concatenating string scan outputs.
onnxruntime/core/providers/cpu/controlflow/scan_utils.h Avoid memset-style zeroing for string tensors in Scan output iteration.
onnxruntime/test/providers/cpu/controlflow/loop_test.cc Add unit tests validating Loop behavior with string scan outputs and loop-carried strings (including zero-iteration cases).

Comment thread onnxruntime/core/providers/cpu/controlflow/loop.cc
ConcatenateCpuOutput byte-copied tensor data via gsl::copy over a
span<const std::byte> cast, which is incorrect for std::string elements
(not trivially copyable). Add an IsDataTypeString() branch that uses
DataAsSpan and std::copy with proper copy-assignment semantics.

Similarly, OutputIterator::ZeroOutCurrent used memset(0) which would
corrupt std::string objects. Since the strings are already default-
constructed (empty) from placement-new, simply return early.

Add tests exercising string scan outputs (single and multi-element),
string loop-carried variables, and zero-iteration edge case.
@yuslepukhin
yuslepukhin force-pushed the yuslepukhin/loop_operator_gaps branch from 62c7529 to 4cadf9d Compare June 29, 2026 22:22

@tianleiwu tianleiwu 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.

Review Summary

The change correctly fixes a real correctness bug: Loop/Scan output concatenation previously byte-copied tensor payloads unconditionally, which is undefined behavior for non-trivially-copyable element types like std::string (it would copy raw pointers/SSO bytes and lead to double-frees or corruption). Routing string tensors through std::copy over DataAsSpan<std::string>() and skipping the memset-based ZeroOutCurrent for strings are both correct.

Strengths

  • Correct handling of heap-allocated string payloads (SSO-exceeding strings are explicitly exercised in tests).
  • output_span is now constructed once outside the loop for the non-string path, which also resolves the earlier reviewer note about per-iteration span re-creation.
  • The ZeroOutCurrent skip is safe because string tensor buffers are placement-new default-constructed to empty strings; the comment documents this clearly.
  • Strong test coverage: single/multi-element string scan outputs, loop-carried string variable, and the zero-iteration empty-output edge case.

Non-blocking suggestions (see inline)

  • Consider std::move instead of std::copy for the per-iteration string payloads, since per_iteration_output is discarded after concatenation.

Overall this looks good to merge. Only minor, optional nits below.

Comment thread onnxruntime/core/providers/cpu/controlflow/loop.cc Outdated
per_iteration_output is discarded after concatenation, so use std::move
to transfer string payloads without re-allocation. Hoist Shape().Size()
as elements_per_iteration above the loop.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@yuslepukhin
yuslepukhin merged commit 152d01f into main Jun 30, 2026
99 of 103 checks passed
@yuslepukhin
yuslepukhin deleted the yuslepukhin/loop_operator_gaps branch June 30, 2026 21:12
feich-ms pushed a commit that referenced this pull request Jul 3, 2026
…#29397)

This pull request improves the handling of string tensors in the CPU
implementation of the Loop operator, ensuring correct memory management
and copy semantics for non-trivially-copyable types like `std::string`.
It also adds comprehensive unit tests to verify these behaviors,
especially for cases involving string scan outputs and loop-carried
variables.

**Enhancements for string tensor support:**

* Updated `ConcatenateCpuOutput` in `loop.cc` to properly detect string
tensors and use `std::copy` for concatenation, ensuring correct handling
of heap-allocated string payloads and avoiding unsafe byte-wise copying.
[[1]](diffhunk://#diff-2c8478657254a53c4ce09684960c925593395336e00c43ef672d9427722e3ff7R276-L282)
[[2]](diffhunk://#diff-2c8478657254a53c4ce09684960c925593395336e00c43ef672d9427722e3ff7R289-R304)
* Modified `OutputIterator::ZeroOutCurrent` in `scan_utils.h` to skip
zeroing for string tensors, as their elements are already
default-constructed and cannot be safely set with `memset`.

**New and extended unit tests for string tensor scenarios:**

* Added tests to `loop_test.cc` covering:
- String scan outputs, including long strings that exceed the
small-string-optimization threshold and multi-element outputs.
  - Loop-carried string variables to ensure they are copied correctly.
- Zero-iteration cases to confirm empty string scan outputs are handled
without errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants