Skip to content

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@MichalPetryka
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Build vararg support only where it is reachable by jkoritzinsky · Pull Request #131655 · dotnet/runtime · GitHub
Skip to content

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@MichalPetryka
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Build vararg support only where it is reachable by jkoritzinsky · Pull Request #131655 · dotnet/runtime · GitHub
Skip to content

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

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

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

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

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@MichalPetryka
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Build vararg support only where it is reachable by jkoritzinsky · Pull Request #131655 · dotnet/runtime · GitHub
Skip to content

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@MichalPetryka
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Build vararg support only where it is reachable by jkoritzinsky · Pull Request #131655 · dotnet/runtime · GitHub
Skip to content

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

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

Build vararg support only where it is reachable - #131655

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only
Aug 28, 2026
Merged

Build vararg support only where it is reachable#131655
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/varargs-windows-only

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

Build vararg support only where it is reachable

Stacked on #131654 — targets dev/jkoritzinsky/calli-pinvoke, not main.

What and why

The vararg calling convention is only supported on Windows: ConvToJitSig rejects both IMAGE_CEE_CS_CALLCONV_VARARG and IMAGE_CEE_CS_CALLCONV_NATIVEVARARG everywhere else. The supporting infrastructure was nonetheless compiled on every platform — assembly stubs, cookie allocation, GC reporting, marshalling helpers and their managed surface.

This introduces FEATURE_VARARGS and builds that machinery only where it can be reached. No behavior change on Windows.

ConvToJitSig now keys off the same feature instead of spelling out TARGET_UNIX || TARGET_ARM, so there is a single definition of "varargs are supported" rather than two that have to be kept in agreement. That is not a behavior change: browser and wasi both set CLR_CMAKE_TARGET_UNIX (configureplatform.cmake lines 434 and 439), so every target that threw before still throws.

The feature is declared twice, deliberately

# clrdefinitions.cmake — nativeif (CLR_CMAKE_TARGET_WIN32)
<!-- clr.featuredefines.props — managed, alongside FeatureIjw / FeatureCominterop -->
<PropertyGroupCondition="'$(TargetsWindows)' == 'true'">

Both are required, and the reason is worth writing down because the failure mode is silent.

vm/corelib.h is not only a C++ header. It is also parsed by the CreateRuntimeRootILLinkDescriptorFile MSBuild task (System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets) to generate the ILLink root descriptor for System.Private.CoreLib — and that task receives the managed $(DefineConstants), not the native CMake defines.

So a native-only #ifdef around a DEFINE_METHOD there still compiles, but the ILLink root quietly disappears, the trimmer removes a method the runtime resolves by name, and you get:

Assert failure: pMD != NULL && ("EE expects method to exist")
CoreLibBinder::LookupMethodLocal
ILCodeStream::EmitCALL
ILArgIteratorMarshaler::EmitConvertSpaceAndContentsCLRToNative

which points at the marshaller and gives no hint that a build-system conditional is the cause. The existing #ifdef FEATURE_COMINTEROP in that header is not a counter-example: FEATURE_COMINTEROP is emitted on both sides by clr.featuredefines.props, so its root is dropped exactly when the managed code is also absent. Declaring FeatureVarargs in the same place gives FEATURE_VARARGS that same symmetry, which is what makes gating corelib.h safe here.

Deleted outright

Unix VarargPInvokeStub implementations in amd64, arm, arm64, loongarch64 and riscv64pinvokestubs.S and in i386/asmhelpers.S; the wasm PORTABILITY_ASSERT placeholders in vm/wasm/helpers.cpp; and the VASigCookie offsets in the arm, loongarch64 and riscv64asmconstants.h. None of those files is deletable whole — each also holds JIT_PInvokeBegin/End/InitPInvokeFrame.

Gated — native

The cookie machinery (struct VASigCookie and its cDAC data descriptor, VASigCookieBlock, Module::GetVASigCookie/GetVASigCookieWorker, VASigCookieEx), VarargPInvokeStubWorker, GetILStubForVarargPInvoke, TheVarargPInvokeStub, InteropDispatchStubManager (plus its vptr_list.h entry and Init()), clrvarargs.cpp and varargsnative.cpp, the ArgIteratorva_list marshaller and the StubHelpers entrypoints behind it, TransitionFrame::GetVASigCookie, and the vararg GC reporting in eetwain.cpp and gc_unwind_x86.inl.

Module::GetLoaderModuleForSignature is not gated — the unmanaged-calli stub path calls it on every platform.

The Windows .asm files are guarded rather than excluded. Strictly unnecessary, since every Windows arch enables the feature — but it buys a compile-time proxy: forcing FEATURE_VARARGS off on windows-x64 compiles the whole VM and DAC as a stand-in for the six targets that can't be built locally. That proxy earned its keep immediately by catching promoteVarArgs in eetwain.cpp, which takes PTR_VASigCookie and is compiled on every target — a real cross-platform break that inspection alone had missed.

Gated — managed

NativeAOT's throwing ArgIterator moves to the shared partition as System/ArgIterator.PlatformNotSupported.cs and is now used by CoreCLR too whenever the feature is off, so the throwing copy exists once instead of twice:

BuildArgIterator source
CoreCLR, varargs supportedits own ArgIterator.cs (real implementation)
CoreCLR, varargs unsupportedshared ArgIterator.PlatformNotSupported.cs
NativeAOTshared ArgIterator.PlatformNotSupported.cs
Monoits own real implementation, untouched

The Shared.projitems condition is '$(FeatureVarargs)' != 'true' and '$(FeatureMono)' != 'true'. The Mono clause matters: Mono's CoreLib also imports Shared.projitems, has its own real ArgIterator, and does not import clr.featuredefines.props — without it, Mono would pick up a duplicate System.ArgIterator.

Because CoreCLR's ArgIterator.cs is now included conditionally, its #if TARGET_WINDOWS split and the ~50-line duplicated throwing branch both go away; the file has no preprocessor directives left. The three StubHelpersva_list members that only the marshaller calls are gated the same way.

Deliberately unchanged

  • GCREFMAP_VASIG_COOKIE (= 5) — part of the ReadyToRun GC ref map format. Value kept; only its handling is gated.
  • DynamicMethodDesc::StubPInvokeVarArg (= 4) — the cDAC contract depends on the value.
  • x86 GC info varargs bit / FLIP_VARARGS — serialized format, and x86 is a supported vararg target. There is no unreachable x86 GC/unwind vararg code.
  • tools/Common/CallingConvention/ArgIterator.cs — crossgen2 cross-targets, so it cannot be gated at compile time.
  • inc/dacdbi.idl / debug/inc/dacdbiinterface.h — versioned interface shape untouched; only the GetVarArgSig implementation is gated.
  • getVarArgsHandle keeps its ICorJitInfo vtable slot and asserts instead of being removed. ICorJitInfo is not per-platform and varargs do work on Windows, so the JIT still needs the method there; this change does not reshape the interface.

GetStubForILStub now throws IDS_EE_VARARG_NOT_SUPPORTED for every target without the feature rather than only under FEATURE_PORTABLE_ENTRYPOINTS — a strict generalization, since that feature is wasm-only and already outside FEATURE_VARARGS.

cDAC

struct VASigCookie and its CDAC_TYPE descriptor are gated, so a target without the feature stops advertising a type it can never allocate. The managed cDAC is unchanged and reads the target's own descriptor, so older runtimes are unaffected; the lookup is lazy and only reachable from DacDbiImpl.GetVarArgSig on a vararg frame. Verified by inspecting the emitted contract descriptor in both configurations.

Renamed

PINVOKE_CALLI_SIGTOKEN_REGNUM/REGISTER on amd64 → PINVOKE_VARARG_SIGTOKEN_REGNUM/REGISTER. That register (r11) carries the VASigCookie* into VarargPInvokeStub; it outlived the unmanaged-calli helpers removed in the parent layer, so the old name referred to a caller that no longer exists.

Note for reviewers touching asmconstants.h

h2inc runs at CMake configure time and asmconstants.h is not registered as a configure dependency, so editing a constant's name or value does not regenerate AsmConstants.inc on an incremental build — the assembler keeps consuming the stale copy. The rename above surfaced this as error A2006: undefined symbol. Pre-existing infrastructure behavior, not changed here; a reconfigure picks it up. Worth knowing because a silently stale value would be far less obvious than a stale name.

Validation

TargetResult
windows-x64 clr+libs -rc Checkedbuilds clean
windows-x86builds clean (exercises i386/asmhelpers.asm + asmconstants)
windows-arm64builds clean (exercises arm64/PInvokeStubs.asm + asmconstants)
windows-x64, FEATURE_VARARGS forced off on both sidesbuilds clean, including the DAC
NativeAOT (clr.nativeaotlibs)builds clean, picks up the shared file
linux/osx x64, linux arm32/arm64, loongarch64, riscv64, browser-wasmnot built locally — covered by inspection plus the forced-off proxy

Tests (windows-x64 Checked):

  • JIT/Directed/arglist/vararg_TargetWindows — 241/241 passed
  • Interop — 333 total, 321 passed, 10 failed; the 10 are the known pre-existing out-of-proc COM and DisabledRuntimeMarshalling environment failures, unchanged from baseline. VarargsTest, CrossAssemblyVarargsTest and IJW NativeVarargsTest all pass.
  • JIT/Directed — 686 total, 679 passed, 0 failed, 7 skipped

Feature-off behavior was verified by inspecting build output rather than assuming: the contract descriptor drops VASigCookie (while MethodTable remains), and CoreLib drops ArgIterator_Init/CalcVaListSize while gaining PlatformNotSupported_ArgIterator.

Note

This pull request description was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
15 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkotas

Copy link
Copy Markdown
Member

This looks good to me. I do not understand the point of it being part of a stacked PR. Can this change be merged once the CI is greeen?

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

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

This is stacked because it depends on unmanaged calli moving away from using VASigCookie on all platforms. Right now VASigCookie is used by varargs and calli.

@jkotas

Copy link
Copy Markdown
Member

Ah ok, part of it depends on the change.

@MichalPetryka

Copy link
Copy Markdown
Contributor

Does this mean that #48796 won't happen?

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 a FEATURE_VARARGS build feature and uses it to compile vararg-related infrastructure only on platforms where vararg calling convention support is reachable (primarily Windows). The change centralizes “varargs supported” logic behind a single feature define and removes or gates otherwise-dead VM/JIT interop stubs, cookie machinery, marshalling helpers, and managed surface area on unsupported targets.

Changes:

  • Add FEATURE_VARARGS (native CMake + managed MSBuild) and use it to gate VM/JIT vararg support, including ConvToJitSig rejection logic.
  • Rework CoreLib’s ArgIterator inclusion: real implementation only when varargs are supported; otherwise use a shared PlatformNotSupported implementation (also used by NativeAOT).
  • Remove unreachable non-Windows vararg P/Invoke stub implementations / placeholders and gate related assembly constants, stub managers, QCalls, and cDAC descriptors.

Reviewed changes

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

Show a summary per file
FileDescription
src/libraries/System.Private.CoreLib/src/System/ArgIterator.PlatformNotSupported.csRemove unused using; keep PNSE ArgIterator implementation.
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitemsInclude PNSE ArgIterator only when varargs are off and not Mono.
src/coreclr/vm/wasm/helpers.cppRemove wasm VarargPInvokeStub* PORTABILITY_ASSERT placeholders.
src/coreclr/vm/stubmgr.hGate InteropDispatchStubManager declaration behind FEATURE_VARARGS.
src/coreclr/vm/stubmgr.cppGate vararg stub recognition/tracing + DAC enumeration behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.hGate vararg-related FCALL/QCALL declarations behind FEATURE_VARARGS.
src/coreclr/vm/stubhelpers.cppGate vararg-related FCALL/QCALL implementations behind FEATURE_VARARGS.
src/coreclr/vm/riscv64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/riscv64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/qcallentrypoints.cppGate ArgIterator_* and StubHelpers_*VaList QCall entries behind FEATURE_VARARGS.
src/coreclr/vm/prestub.cppGate TheVarargPInvokeStub behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/mtypes.hGate MARSHAL_TYPE_ARGITERATOR behind FEATURE_VARARGS.
src/coreclr/vm/mlinfo.cppGate ArgIterator marshaler detection behind FEATURE_VARARGS.
src/coreclr/vm/metasig.hGate vararg metasig entry behind FEATURE_VARARGS.
src/coreclr/vm/loongarch64/pinvokestubs.SRemove unreachable vararg P/Invoke stub generator macro and stubs.
src/coreclr/vm/loongarch64/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/jitinterface.cppUse FEATURE_VARARGS for vararg rejection; gate vararg class/cookie paths with asserts when off.
src/coreclr/vm/ilmarshalers.hGate ILArgIteratorMarshaler definition behind FEATURE_VARARGS.
src/coreclr/vm/ilmarshalers.cppGate ILArgIteratorMarshaler implementation behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmhelpers.SRemove unreachable non-Windows x86 vararg stub implementation.
src/coreclr/vm/i386/asmhelpers.asmGate Windows x86 vararg stub + worker extern behind FEATURE_VARARGS.
src/coreclr/vm/i386/asmconstants.hGate VASigCookie stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/gc_unwind_x86.inlAssert/PORTABILITY_ASSERT varargs GC unwind when varargs feature is off.
src/coreclr/vm/frames.hGate TransitionFrame::GetVASigCookie declaration behind FEATURE_VARARGS.
src/coreclr/vm/frames.cppGate GetVASigCookie; restructure caller stack promotion and GC refmap handling for varargs.
src/coreclr/vm/eetwain.cppGate promoteVarArgs + varargs GC reporting; assert unreachable when feature is off.
src/coreclr/vm/ecalllist.hGate StubHelpers::CalcVaListSize FCALL exposure behind FEATURE_VARARGS.
src/coreclr/vm/dllimport.cppGate vararg P/Invoke stub building and VarargPInvokeStubWorker; throw IDS_EE_VARARG_NOT_SUPPORTED when feature off.
src/coreclr/vm/datadescriptor/datadescriptor.incGate cDAC VASigCookie type descriptor behind FEATURE_VARARGS.
src/coreclr/vm/corelib.hGate CoreLib binder entries for ArgIterator/RuntimeArgumentHandle and va_list StubHelpers behind FEATURE_VARARGS.
src/coreclr/vm/CMakeLists.txtOnly compile clrvarargs.* / varargsnative.* sources when FEATURE_VARARGS is enabled.
src/coreclr/vm/class.hGate TheVarargPInvokeStub declaration behind FEATURE_VARARGS.
src/coreclr/vm/cgensys.hGate vararg stub worker/stub exports behind FEATURE_VARARGS; simplify ret-buff arch conditions.
src/coreclr/vm/ceeload.hGate VASigCookie / cookie block storage and APIs behind FEATURE_VARARGS.
src/coreclr/vm/ceeload.cppGate Module::GetVASigCookie*; gate clrvarargs.h include for asmconstant asserts.
src/coreclr/vm/arm64/pinvokestubs.SRemove unreachable unix arm64 vararg stub generator/stub.
src/coreclr/vm/arm64/PInvokeStubs.asmGate Windows arm64 vararg stub machinery behind FEATURE_VARARGS.
src/coreclr/vm/arm64/asmconstants.hGate VASigCookie IL stub offset constant behind FEATURE_VARARGS.
src/coreclr/vm/arm/pinvokestubs.SRemove unreachable unix arm32 vararg stubs and generator macro.
src/coreclr/vm/arm/asmconstants.hRemove VASigCookie IL stub offset constant on unsupported target.
src/coreclr/vm/appdomain.cppOnly initialize InteropDispatchStubManager when FEATURE_VARARGS is enabled.
src/coreclr/vm/amd64/pinvokestubs.SRemove unreachable unix amd64 vararg stubs.
src/coreclr/vm/amd64/PInvokeStubs.asmGate Windows amd64 vararg stubs behind FEATURE_VARARGS; rename sigtoken register macro.
src/coreclr/vm/amd64/asmconstants.hGate vararg sigtoken register and VASigCookie stub offset constants behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csprojCompile ArgIterator.cs only when FeatureVarargs is true.
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.csGate va_list marshalling imports and CalcVaListSize behind FEATURE_VARARGS.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.csRemove platform preprocessor split; keep only the real implementation (now conditionally compiled by csproj).
src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csprojStop compiling NativeAOT’s old System\\ArgIterator.cs (use shared PNSE implementation instead).
src/coreclr/inc/vptr_list.hGate InteropDispatchStubManager VPTR entry behind FEATURE_VARARGS.
src/coreclr/debug/daccess/dacdbiimpl.cppReturn E_NOTIMPL for GetVarArgSig when varargs are off (implementation gated).
src/coreclr/clrdefinitions.cmakeDefine FEATURE_VARARGS for Windows builds (native define).
src/coreclr/clr.featuredefines.propsDefine FeatureVarargs for Windows builds and propagate FEATURE_VARARGS to managed DefineConstants.

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Does this mean that #48796 won't happen?

That will likely use a different mechanism than CLR-native varargs. Either that or we'd enable FEATURE_VARARGS and do the work, but I don't think we'd implement it as such as managed varargs is not something we want to do.

@MichalPetryka

Copy link
Copy Markdown
Contributor

managed varargs is not something we want to do

Yeah I agree that those are not needed.

CopilotAI review requested due to automatic review settings August 1, 2026 04:39
@jkoritzinsky
jkoritzinskyforce-pushed the dev/jkoritzinsky/varargs-windows-only branch from 4b5c744 to ce5ca3dCompareAugust 1, 2026 04:39

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/coreclr/vm/jitinterface.cpp:6268

  • Similar to the CLASSID_ARGUMENT_HANDLE case: in retail builds this will just return NULL (and leave *ppIndirection NULL) if called, which risks later crashes rather than a clear "varargs not supported" failure.

Since this is a THROWS path already, it’s safer to throw IDS_EE_VARARG_NOT_SUPPORTED here when FEATURE_VARARGS is off.
src/coreclr/clrdefinitions.cmake:126

  • FEATURE_VARARGS is defined for all Win32 targets here. That’s broader than the actual varargs implementation (e.g., the old ConvToJitSig guard also excluded TARGET_ARM), and can accidentally enable varargs on Windows targets/arches that don’t have the supporting stubs/GC/reporting.

Consider scoping FEATURE_VARARGS to the same set of Windows architectures that actually support varargs (x86/x64/arm64), similar to FEATURE_INTEROP_DEBUGGING just above.

if (CLR_CMAKE_TARGET_WIN32)
set(FEATURE_VARARGS 1)
add_definitions(-DFEATURE_VARARGS)
endif (CLR_CMAKE_TARGET_WIN32)

src/coreclr/clr.featuredefines.props:54

  • FeatureVarargs is enabled for all Windows builds, but the implementation is only expected to exist on specific Windows architectures (x86/x64/arm64). Keeping this scoped avoids accidentally defining FEATURE_VARARGS in managed builds where the native support isn’t present, and keeps the managed/native definitions truly “in sync” as the comment suggests.
 <!-- The vararg calling convention is only supported on Windows.
Must stay in sync with FEATURE_VARARGS in clrdefinitions.cmake. -->
<FeatureVarargs>true</FeatureVarargs>
</PropertyGroup>

src/coreclr/vm/jitinterface.cpp:4098

  • In retail builds (where _ASSERTE is compiled out), this path returns a null CORINFO_CLASS_HANDLE for CLASSID_ARGUMENT_HANDLE. If the JIT ever does request it (e.g., due to unexpected IL), that would likely turn into a harder-to-diagnose null deref later.

Since ConvToJitSig uses IDS_EE_VARARG_NOT_SUPPORTED for unsupported varargs, consider throwing the same exception here as a defensive fail-fast.

This issue also appears on line 6264 of the same file.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 26, 2026 22:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Comment threadsrc/coreclr/clr.featuredefines.props Outdated
Comment threadsrc/coreclr/clrdefinitions.cmake Outdated
Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/vm/frames.cpp Outdated
jkoritzinskyand others added 3 commits August 27, 2026 14:05
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 544b2da7-b36a-4a7c-a2df-20e1487849af
CopilotAI review requested due to automatic review settings August 27, 2026 22:00

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

CopilotAI review requested due to automatic review settings August 27, 2026 22:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/debug/daccess/dacdbiimpl.cpp:5529

  • In the !FEATURE_VARARGS path, this method unconditionally dereferences pArgBase and pRetVal. Even if the call should be unreachable on non-varargs targets, this is a public DAC/DBI entrypoint and should not AV when passed null out-params. Please validate pointers (return E_INVALIDARG) before writing outputs, then return E_NOTIMPL.
#ifndef FEATURE_VARARGS
*pArgBase = (CORDB_ADDRESS)NULL;
*pRetVal = TargetBuffer();
return E_NOTIMPL;
#else // FEATURE_VARARGS

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g build failures correspond to the known helix test failures.

@jkoritzinsky
jkoritzinsky merged commit 398b6c5 into mainAug 28, 2026
147 of 152 checks passed
@jkoritzinsky
jkoritzinsky deleted the dev/jkoritzinsky/varargs-windows-only branch August 28, 2026 18:27
@dotnet-milestone-botdotnet-milestone-botBot added this to the 12.0-preview1 milestone Aug 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@MichalPetryka