Skip to content

Evaluate substring operands once - #577

Merged
nunoplopes merged 4 commits into
Cpp2Rust:masterfrom
perfloop-oss:perfloop-pr-open-dja7ka9vby
Nov 1, 2026
Merged

nunoplopes merged 4 commits into
Cpp2Rust:masterfrom
perfloop-oss:perfloop-pr-open-dja7ka9vby

Conversation

@perfloop-agent

@perfloop-agent perfloop-agent commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Make substring calls evaluate their receiver and arguments once in both output models. Duplicating an operand during inline substitution can change both the selected bytes and caller state; this handles the substring case of #575.

The side-effect cases in tests/unit/string.cpp compare substring bytes and final counters with C++ for position, count and receiver expressions.

  • Capture the receiver and arguments in a tuple match, which also keeps temporary receivers alive until the substring has been copied.
  • Borrow the source after evaluating the arguments, so argument expressions can modify it before the slice is read.
  • Materialize temporary receivers before taking their address in the unsafe converter.

The result remains an independent, null-terminated copy of the selected slice. No whole-source clone is introduced; reference-counted receivers must now be addressable. Existing range clipping and overflow handling are unchanged.

Testing

  • lit on tests/unit/string.cpp and tests/unit/string_substr.cpp (Linux, Release build): pass. Both fail without this change.
  • Full suite (unit, ub and multi-file) on Linux at ce0915c: 1142 pass / 14 xfail / 0 fail before; 1144 pass / 14 xfail / 0 fail with this change. The two new passes are the tests above. On macOS, one expected output in string_substr.cpp hard-codes the libstdc++ std::string size; a fix for that follows.
  • clang-format --dry-run --Werror on the changed C++ files, rustfmt --check on the changed rule files, and cargo clippy --all-targets --all-features -- -Dwarnings on rules/: clean.

tests/unit/string_substr.cpp checks receiver-before-argument ordering, source mutations in either argument, independent result storage, and temporary, pointer, field and container-element receivers. The generated Rust expectations cover both models.

Related work: #576 changes vector proxy handling in the converter and binds repeated operands in vector rules; this change handles substring expansion.

Case and evidence


Generated by Perfloop. Human sponsor: Tomás Senart.

Signed-off-by: Perfloop Agent <agent@perfloop.ai>
Signed-off-by: Tomás Senart <ts@perfloop.ai>
Signed-off-by: Perfloop Agent <agent@perfloop.ai>
Signed-off-by: Tomás Senart <ts@perfloop.ai>
Signed-off-by: Perfloop Agent <agent@perfloop.ai>
Signed-off-by: Tomás Senart <ts@perfloop.ai>
@perfloop-agent

Copy link
Copy Markdown
Contributor Author

One tradeoff to flag before you review. To keep C++17 order (the arguments can modify the receiver, e.g. s.substr((s[0] = 'j', 0), 2)), the refcount rule now takes the receiver as Ptr<Vec<i8>>. So a string local that calls substr becomes Rc<RefCell<..>>, the same way append, replace, at and assign already work. In a release microbenchmark of 30M substr calls on a local string, that costs about 20% (0.85 s to 1.02 s). Temporary receivers get faster, because they are no longer built twice. The unsafe model is unchanged.

If you'd rather avoid the box, a plain local receiver could be read in place after the arguments are evaluated, and only receivers with side effects would keep the evaluate-once capture. That adds a second code shape for the rule, so I left it out. Happy to switch to it, or to match the let __a0 = a0; style from #576, if you prefer either.

The format failure is in tests/unit/vector_bool_reference.cpp from master (f349223), not in this change.

The field-receiver case kept a std::string by value in a struct, so the
refcount expected output recorded libstdc++'s 32-byte layout and failed
on libc++, where std::string is 24 bytes. Reach the string through a
reference member instead: substr still takes its receiver from a struct
field, and the emitted layout is only the pointer-sized field.

Signed-off-by: Perfloop Agent <agent@perfloop.ai>
Signed-off-by: Tomás Senart <ts@perfloop.ai>
@nunoplopes
nunoplopes merged commit 7cab049 into Cpp2Rust:master Nov 1, 2026
8 of 9 checks passed
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.

2 participants