Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Two code heaps - #129369

Draft
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps
Draft

Two code heaps#129369
AndyAyersMS wants to merge 8 commits into
dotnet:mainfrom
AndyAyersMS:two-code-heaps

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

No description provided.

Squash-merge of amanasifkhalid:two-code-heaps#a676f29 onto current main.
Single WIP commit, 13 months stale.
Adds a per-LoaderAllocator separation between optimized (Tier1+) and
non-optimized (Tier0/MinOpts/Instrumented) code heaps:
* New CodeHeapRequestInfo::m_isOptimizedCode flag + IsOptimizedCode/
SetOptimizedCode accessors (PascalCase, matching HEAD's existing
accessor convention).
* Set via !pMD->IsJitOptimizationDisabled() in AllocCode (so Tier1,
fully-optimized non-tiered, etc. land in optimized heaps; Tier0,
Tier0-Instrumented, MinOpts in regular).
* RangeSection::RANGE_SECTION_OPTIMIZEDCODE flag (assigned 0x20 since
HEAD took 0x10 for VIRTUALIP); stamped on heaps in NewCodeHeap.
* New LoaderAllocator cache pointers m_pLastUsedOptimizedCodeHeap and
m_pLastUsedDynamicOptimizedCodeHeap; optimized-code allocations use
the new caches.
* CanUseCodeHeap rejects a heap up front if its OPTIMIZEDCODE flag
doesn't match the request (moved from aman's tail-of-function
retVal-based check to a top-of-function guard since HEAD's version
returns directly from each success branch rather than using retVal).
* m_isOptimizedCode initialized to false in the
CodeHeapRequestInfo(MethodDesc*, LoaderAllocator*, BYTE*, BYTE*)
ctor (the others delegate to it).
Aman-side fixups during merge:
* aman's lowercase getRequestSize/setRequestSize -> HEAD's PascalCase
GetRequestSize/SetRequestSize.
* aman's inline-defined ctors in codeman.h dropped; HEAD's separate
ctor declarations + definitions in codeman.cpp are used and a single
initializer added for m_isOptimizedCode.
* aman's pInfo->m_pAllocator-> direct field access rewritten as
pInfo->GetAllocator()->... (m_pAllocator is private in HEAD).
* aman's RANGE_SECTION_OPTIMIZEDCODE value 0x10 collides with HEAD's
new RANGE_SECTION_VIRTUALIP=0x10; moved to 0x20.
Status carryovers from the original WIP:
* The dynamic (LCG) optimized-heap path remains commented out -
m_pLastUsedDynamicOptimizedCodeHeap is declared and zero-initialized
but never read/written.
* CanUseCodeHeap does a FindCodeRange lookup per allocation; could be
optimized by storing the flag directly on HeapList.
* No tests, no telemetry, no policy knob.
Not yet built.
…he slot
LCG / DynamicMethod methods are not tiering-eligible: MethodDesc::
IsJitOptimizationDisabled() returns the same answer for every LCG method
within a single process (the per-method branch is gated on !IsNoMetadata()
which is false for LCG; the chunk-wide branch is process- or module-global).
There's no way to end up with mixed-optimization LCG code in one process,
so splitting the dynamic code heap by optimization level would create at
most one pool of each kind anyway.
Cleanup:
* In AllocCode, also gate SetOptimizedCode() on !IsDynamicDomain() so LCG
requests never get the optimized classification. The dynamic code heap
is therefore never tagged with RANGE_SECTION_OPTIMIZEDCODE.
* Drop the m_pLastUsedDynamicOptimizedCodeHeap field on LoaderAllocator
and its initialization, plus the two commented-out blocks in
AllocCodeWorker that would have used it.
* Strengthen the NewCodeHeap assertion: with the gating above, optimized
code is mutually exclusive with both interpreter and dynamic-domain.
Asserting both makes the invariant explicit.
* Add a TODO note in CanUseCodeHeap explaining why the optimized-flag
check is harmless for interpreter/LCG (they never carry the flag) and
pointing at the FindCodeRange-lookup-per-allocation optimization for
future work (caching the bit on HeapList itself).
Build: build.cmd -s clr.runtime+clr.corelib -c Checked -> 0 errors,
0 warnings (1m23s).
… off)
Adds a new INTERNAL retail config knob:
DOTNET_SeparateOptimizedCodeHeaps (default 0)
When set to a non-zero value, JIT'd methods that are not optimization-
disabled get their own per-LoaderAllocator code heap, separate from
Tier0/MinOpts/Tier0Instrumented code. When 0 (the default), all JIT'd
code shares a single heap per LoaderAllocator, matching pre-change
behavior. LCG and interpreter heaps are unaffected either way.
In AllocCode, SetOptimizedCode() is now gated on three conditions:
* !requestInfo.IsDynamicDomain() (LCG isn't tiering-eligible)
* !pMD->IsJitOptimizationDisabled() (the actual opt classification)
* DOTNET_SeparateOptimizedCodeHeaps != 0 (opt-in)
Build clean; smoke tests pass in both modes:
default -> ArrBoundBinaryOp PASSED, smoke exit=100
opt-in (=1) -> ArrBoundBinaryOp PASSED, smoke exit=100
Previously CanUseCodeHeap consulted ExecutionManager::FindCodeRange on
every cache check to read the RangeSection's RANGE_SECTION_OPTIMIZEDCODE
bit. The flag is set once at heap creation in NewCodeHeap and never
changes, so cache it directly on the HeapList instead.
* HeapList gets a new trailing bool isOptimizedCode field. Trailing
placement means FakeHeapList/cDAC layout assertions are undisturbed
(none of the mirrored consumers read this field).
* NewCodeHeap stamps the cached flag from the same expression that
builds the RangeSection flags.
* Both HeapList constructors (LoaderCodeHeap::CreateCodeHeap and
HostCodeHeap::InitializeHeapList) zero-initialize the field so
NewCodeHeap's subsequent assignment is always meaningful, even on
the path where the heap is created without the optimized flag.
* CanUseCodeHeap drops the FindCodeRange lookup and reads
pCodeHeap->isOptimizedCode directly.
Build clean; smoke + ArrBoundBinaryOp PASSED in both default and
DOTNET_SeparateOptimizedCodeHeaps=1 modes.
Predicate (!= 0 enables) unchanged; only the default value moves.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 introduces an “optimized code” classification for JIT code heap allocations and adds a separate per-LoaderAllocator “last used heap” cache intended to keep optimized and non-optimized JIT’d code in different heaps, gated by a new runtime config switch.

Changes:

  • Adds a new CodeHeapRequestInfo flag and a RangeSection flag (RANGE_SECTION_OPTIMIZEDCODE) plus a cached HeapList::isOptimizedCode bit to prevent heap/request mismatches.
  • Adds LoaderAllocator::m_pLastUsedOptimizedCodeHeap and updates AllocCodeWorker to use it when IsOptimizedCode() is set.
  • Adds a new runtime config knob SeparateOptimizedCodeHeaps.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/loaderallocator.hppAdds a new per-LoaderAllocator cache slot for “optimized” code heaps.
src/coreclr/vm/loaderallocator.cppInitializes the new optimized code heap cache pointer.
src/coreclr/vm/dynamicmethod.cppInitializes the new HeapList::isOptimizedCode field for host code heaps.
src/coreclr/vm/codeman.hAdds CodeHeapRequestInfo::m_isOptimizedCode, HeapList::isOptimizedCode, and the new RangeSection flag bit.
src/coreclr/vm/codeman.cppTags new code heaps with the optimized flag, selects the optimized heap cache in allocation, and gates “optimized heap” routing on a new config switch.
src/coreclr/inc/clrconfigvalues.hAdds the SeparateOptimizedCodeHeaps config entry.

Comment on lines 521 to 525
#ifdef FEATURE_PGO
RETAIL_CONFIG_STRING_INFO(INTERNAL_PGODataPath, W("PGODataPath"), "Read/Write PGO data from/to the indicated file.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ReadPGOData, W("ReadPGOData"), 0, "Read PGO data")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_WritePGOData, W("WritePGOData"), 0, "Write PGO data")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_TieredPGO, W("TieredPGO"), 1, "Instrument Tier0 code and make counts available to Tier1")
Comment on lines +3264 to +3266
if (!requestInfo.IsDynamicDomain()
&& !pMD->IsJitOptimizationDisabled()
&& CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SeparateOptimizedCodeHeaps) != 0)
Comment threadsrc/coreclr/vm/codeman.cpp Outdated
Comment on lines +3259 to +3263
// Optionally route optimized (Tier1+) code to its own per-LoaderAllocator
// heap. LCG (dynamic-domain) methods are excluded because they are not
// tiering-eligible: every LCG method within a single process uses the same
// JIT optimization level, so splitting their heap would create at most one
// pool of each kind anyway. Gated off by default (DOTNET_SeparateOptimizedCodeHeaps).
* Move INTERNAL_SeparateOptimizedCodeHeaps declaration outside the
#ifdef FEATURE_PGO block. Browser-wasm and iOS simulator builds
disable FEATURE_PGO, so the unconditional reference from codeman.cpp
was breaking those builds:
src/coreclr/vm/codeman.cpp:3266:49: error: no member named
'INTERNAL_SeparateOptimizedCodeHeaps' in 'CLRConfig'
* Exclude interpreted requests from SetOptimizedCode(). When
FEATURE_INTERPRETER is on, AllocCode<InterpreterCodeHeader> sets
requestInfo.SetInterpreted() earlier; the new gate then allowed
SetOptimizedCode() to also be set, which violates the mutual-exclusion
invariant asserted in NewCodeHeap (_ASSERTE(!pInfo->IsInterpreted())
inside the IsOptimizedCode() branch).
* Reword the comment: the split is keyed off
MethodDesc::IsJitOptimizationDisabled() (attributes / global debug
flags / minopts), not the current compilation tier. The previous
'Tier1+' wording was misleading.
Build clean.
Classify the active native code version by optimization tier, excluding instrumented and non-tiered optimized code. Reserve optimized heaps in 16 MiB increments and bypass the small initial loader block to improve Tier1 code locality.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6bd43e03-841f-47ee-80f4-4da985b32a67
Sign up for freeto 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

@AndyAyersMS