Skip to content

[experiment] Fold String.Concat - #129659

Closed
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fold-string-concat-3
Closed

[experiment] Fold String.Concat#129659
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fold-string-concat-3

Conversation

@EgorBo

@EgorBoEgorBo commented Jun 20, 2026

Copy link
Copy Markdown
Member

Folds string.Concat(...) for jit-time known constant strings (including static readonly fields holding non-FOH strings).

just to see diffs in MihuBot -nuget if it's any popular in 3rd parties..

Diffs are promising

CopilotAI review requested due to automatic review settings June 20, 2026 16:25
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 20, 2026
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@MihaZupan

MihaZupan commented Jun 20, 2026

Copy link
Copy Markdown
Member

It'll blow up on diffing CoreLib atm, seeing if #129658 is enough to make it work again

@EgorBo

Copy link
Copy Markdown
MemberAuthor

It'll blow up on diffing CoreLib atm, seeing if #2022 is enough to make it work again

um.. #2022 ?

@MihaZupan

Copy link
Copy Markdown
Member

Oops, fixed.
2022 is the runtime-utils issue :) MihuBot/runtime-utils#2022

CopilotAI 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 experiments with enabling JIT-time folding of fixed-arity stringString.Concat calls into a single frozen string object when the inputs are compile-time readable, by introducing a new JIT↔EE interface API to allocate such frozen strings.

Changes:

  • Marks String.Concat(string?, ...) overloads as [Intrinsic] and adds a JIT importer expansion to constant-fold eligible calls.
  • Adds a new JIT↔EE interface method tryCreateStringObject and wires it through CoreCLR, SuperPMI record/replay, and NativeAOT/R2R wrappers.
  • Adjusts inline-observation scanning to avoid treating String.Concat’s intrinsic marking as an inlining heuristic signal.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/String.Manipulation.csMarks fixed-arity Concat(string?,...) overloads as intrinsic.
src/coreclr/vm/jitinterface.cppImplements CEEInfo::tryCreateStringObject to allocate/fill a frozen string.
src/coreclr/tools/superpmi/superpmi/icorjitinfo.cppPlumbs tryCreateStringObject through SuperPMI replay interface.
src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cppAdds shim passthrough for the new JIT↔EE API.
src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cppAdds shim passthrough + call counting for the new API.
src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cppRecords/replays tryCreateStringObject in the collector shim.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.hDeclares record/replay support + packet ID for the new API.
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cppImplements record/replay logic for tryCreateStringObject.
src/coreclr/tools/superpmi/superpmi-shared/lwmlist.hAdds a new LightWeightMap slot for TryCreateStringObject.
src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txtAdds the new API to the thunk generator input.
src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.csAdds managed callback plumbing for tryCreateStringObject.
src/coreclr/tools/aot/jitinterface/jitinterface_generated.hUpdates NativeAOT JIT interface wrapper for the new API.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csReturns null for tryCreateStringObject during AOT compilation.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csReturns null for tryCreateStringObject during R2R compilation.
src/coreclr/jit/namedintrinsiclist.hAdds NI_System_String_Concat.
src/coreclr/jit/importercalls.cppImplements impStringConcat folding and dispatches it from impIntrinsic.
src/coreclr/jit/ICorJitInfo_wrapper_generated.hppAdds wrapper logging/dispatch for tryCreateStringObject.
src/coreclr/jit/ICorJitInfo_names_generated.hAdds the API name for wrapper logging.
src/coreclr/jit/fgbasic.cppPrevents String.Concat intrinsic marking from affecting inliner heuristics.
src/coreclr/jit/compiler.hDeclares impStringConcat.
src/coreclr/inc/jiteeversionguid.hBumps the JIT↔EE interface GUID.
src/coreclr/inc/icorjitinfoimpl_generated.hAdds the new interface method declaration (generated).
src/coreclr/inc/corinfo.hAdds the new ICorStaticInfo::tryCreateStringObject API.

Comment threadsrc/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Outdated
Comment threadsrc/coreclr/inc/corinfo.h Outdated
Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
@MihaZupan

Copy link
Copy Markdown
Member

@MihuBot -nuget -dependsOn https://github.com/MihaZupan/runtime/tree/revert-test-3

CopilotAI 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

Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/inc/corinfo.h Outdated
Comment threadsrc/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Outdated
Comment threadsrc/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp Outdated
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot -nuget -dependsOn https://github.com/MihaZupan/runtime/tree/revert-test-3

@MihaZupan

Copy link
Copy Markdown
Member

@MihuBot -nuget

@EgorBo

Copy link
Copy Markdown
MemberAuthor

isRunRarely check swept quite a few hits. Anyway, I think it should be done differently and recognize FastAllocateStrings + memmoves in a late phase.

CopilotAI review requested due to automatic review settings June 21, 2026 02:21
@EgorBo
EgorBoforce-pushed the fold-string-concat-3 branch from af94797 to 83cbce6CompareJune 21, 2026 02:21

CopilotAI 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

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp Outdated
Comment threadsrc/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp
Comment threadsrc/coreclr/inc/corinfo.h
Comment threadsrc/coreclr/jit/assertionprop.cpp
Comment threadsrc/coreclr/jit/assertionprop.cpp
@EgorBo
EgorBoforce-pushed the fold-string-concat-3 branch from 83cbce6 to c494901CompareJune 21, 2026 02:42
CopilotAI review requested due to automatic review settings June 21, 2026 04:16
@EgorBo
EgorBoforce-pushed the fold-string-concat-3 branch from c494901 to c900896CompareJune 21, 2026 04:16

CopilotAI 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

Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.

Comment on lines 182 to 183
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Concat(string? str0, string? str1)
Comment on lines +12613 to +12617
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_PREEMPTIVE;
} CONTRACTL_END;
Comment on lines +12633 to +12635
bool isFrozen = false;
STRINGREF strObj = AllocateString((DWORD)length, /* preferFrozenHeap */ true, &isFrozen);

Comment on lines +3393 to +3396
virtual CORINFO_OBJECT_HANDLE tryCreateStringObject(
uint16_t* str,
int length
) = 0;
Comment on lines +2230 to +2234
// The input is a transient JIT-side buffer, so we key the record on a content hash of the
// requested string (length + characters) rather than on the pointer value.
static DWORDLONG HashStringObjectRequest(uint16_t* str, int length)
{
DWORDLONG hash = 0xCBF29CE484222325ULL ^ (DWORDLONG)(DWORD)length; // FNV-1a offset basis
Comment threadsrc/coreclr/jit/assertionprop.cpp Outdated
Comment on lines +2965 to +2969
// Ask the VM to materialize a frozen string object with the computed contents.
CORINFO_OBJECT_HANDLE strObj = info.compCompHnd->tryCreateStringObject(buffer, (int)strLength);
if (strObj == NO_OBJECT_HANDLE)
{
JITDUMP("...VM could not create a frozen string object - bail out.\n");
Comment on lines +2973 to +2977
// Success - remove the now-redundant initialization statements. Their only observable effect is
// writing into the string we are about to replace with a constant, and all the written data is
// constant, so they are safe to drop.
while (initStmts.Height() > 0)
{
@EgorBo
EgorBoforce-pushed the fold-string-concat-3 branch from c900896 to 81196bbCompareJune 21, 2026 05:06
Add a JIT-EE API `tryCreateStringObject` and assertion-prop optimizations that fold compile-time-constant strings into a single frozen string object:
* String.Concat(string, ...) fixed-arity overloads are marked [Intrinsic] and recognized as NI_System_String_Concat. The JIT keeps them as calls (impMarkInlineCandidate skips this intrinsic) instead of inlining their bodies, and folds the call directly when all arguments are constant strings.
* The same frozen-string materialization also folds a FastAllocateString followed by a sequence of constant memmoves (as produced by String.Substring).
The VM only creates the string on the frozen object heap and never for collectible/unloadable contexts (and never returns a non-frozen instance); NativeAOT/crossgen return null. NI_System_String_Concat is also excluded from the inliner's CALLEE_INTRINSIC heuristic so marking Concat [Intrinsic] does not perturb inlining decisions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 21, 2026 05:16
@EgorBo
EgorBoforce-pushed the fold-string-concat-3 branch from 81196bb to 1a557d2CompareJune 21, 2026 05:16

CopilotAI 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

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Comment on lines +3393 to +3396
virtual CORINFO_OBJECT_HANDLE tryCreateStringObject(
uint16_t* str,
int length
) = 0;
totalLength += strLen;
}

GenTree* con = optCreateConstStringNode(buffer, totalLength, call);
Comment on lines +8307 to +8313
// String.Concat is marked [Intrinsic] only so that it can be constant-folded during assertion
// propagation (NI_System_String_Concat). Keep it as a call rather than inlining its body so the
// fold has something to recognize, and to avoid the size bloat of inlining Concat everywhere.
if (call->IsSpecialIntrinsic(this, NI_System_String_Concat))
{
return;
}
Comment on lines +2874 to +2882
CORINFO_OBJECT_HANDLE strObj = info.compCompHnd->tryCreateStringObject(buffer, length);
if (strObj == NO_OBJECT_HANDLE)
{
JITDUMP("...VM could not create a frozen string object - bail out.\n");
return nullptr;
}

GenTree* con = gtNewIconEmbObjHndNode(strObj);
con->gtVNPair.SetBoth(vnStore->VNForHandle((ssize_t)strObj, GTF_ICON_OBJ_HDL));
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@EgorBo@MihaZupan