diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 957891f0449174..15222d46e54fd1 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -593,6 +593,8 @@ enum CorInfoHelpFunc CORINFO_HELP_ALLOC_CONTINUATION_METHOD, CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_WASM_PROF_SAMPLEPOINT, // WASM only: cooperative EventPipe CPU-sampling samplepoint + CORINFO_HELP_COUNT, }; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 4efce6d0fd83c6..c17651341cbe46 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 406b139f-b910-4579-a9a8-91eb153ab024 */ - 0x406b139f, - 0xb910, - 0x4579, - {0xa9, 0xa8, 0x91, 0xeb, 0x15, 0x3a, 0xb0, 0x24} +constexpr GUID JITEEVersionIdentifier = { /* c957e556-a232-49bd-8fa4-f18fab00f111 */ + 0xc957e556, + 0xa232, + 0x49bd, + {0x8f, 0xa4, 0xf1, 0x8f, 0xab, 0x00, 0xf1, 0x11} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index c6d72981725409..a6fc4f7815b937 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -395,6 +395,14 @@ DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_METHOD) DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_CLASS) + // WASM EventPipe CPU-sampling samplepoint. Native helper (no managed fallback), auto-wrapped in a + // PortableEntryPoint by getHelperFtnAddr. Never emitted on other targets; NULL keeps enum/table aligned. +#ifdef TARGET_WASM + JITHELPER(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, JIT_WasmProfSamplepoint, METHOD__NIL) +#else + JITHELPER(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, NULL, METHOD__NIL) +#endif + #undef JITHELPER #undef DYNAMICJITHELPER #undef JITHELPER diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 63ef3b699aae3a..2b79bc440a1b6e 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -20,7 +20,7 @@ // If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION` // and handle pending work. #define READYTORUN_MAJOR_VERSION 32 -#define READYTORUN_MINOR_VERSION 0x0000 +#define READYTORUN_MINOR_VERSION 0x0001 #define MINIMUM_READYTORUN_MAJOR_VERSION 26 @@ -82,6 +82,7 @@ // method's ReadyToRun entry point and fixups. // R2R Version 31.1 adds READYTORUN_FLAG_SINGLE_THREADED, which records that the image lays out [ThreadStatic] fields as regular statics // for a runtime built without FEATURE_MULTITHREADING. +// R2R Version 32.1 adds READYTORUN_HELPER_WasmProfSamplepoint (used on WebAssembly) struct READYTORUN_CORE_HEADER { @@ -528,6 +529,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_InitClass = 0x116, READYTORUN_HELPER_InitInstClass = 0x117, READYTORUN_HELPER_R2RToInterpreter = 0x118, + READYTORUN_HELPER_WasmProfSamplepoint = 0x119, }; #include "readytoruninstructionset.h" diff --git a/src/coreclr/inc/readytorunhelpers.h b/src/coreclr/inc/readytorunhelpers.h index 17dde241e3fdfa..b5a07628f0be6a 100644 --- a/src/coreclr/inc/readytorunhelpers.h +++ b/src/coreclr/inc/readytorunhelpers.h @@ -119,6 +119,7 @@ HELPER(READYTORUN_HELPER_PInvokeBegin, CORINFO_HELP_JIT_PINVOKE_BEG HELPER(READYTORUN_HELPER_PInvokeEnd, CORINFO_HELP_JIT_PINVOKE_END, ) HELPER(READYTORUN_HELPER_ResumeAfterCatch, CORINFO_HELP_JIT_RESUME_AFTER_CATCH, ) HELPER(READYTORUN_HELPER_GCPoll, CORINFO_HELP_POLL_GC, ) +HELPER(READYTORUN_HELPER_WasmProfSamplepoint, CORINFO_HELP_WASM_PROF_SAMPLEPOINT, ) HELPER(READYTORUN_HELPER_ReversePInvokeEnter, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, ) HELPER(READYTORUN_HELPER_ReversePInvokeExit, CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, ) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 87cf3549ff7b34..7301d475f87cff 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -5018,6 +5018,10 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // Transform any strongly connected components into reducible flow. // DoPhase(this, PHASE_WASM_TRANSFORM_SCCS, &Compiler::fgWasmTransformSccs); + + // Insert EventPipe CPU-sampling samplepoints before lowering. + // + DoPhase(this, PHASE_WASM_PROF_INSTRUMENT, &Compiler::fgWasmProfInstrument); #endif // Assign registers to variables, etc. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 70e27942f0b5ae..ec0fd4999ec8d7 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7026,6 +7026,7 @@ class Compiler PhaseStatus fgWasmRepairTryEntries(); PhaseStatus fgWasmVirtualIP(); PhaseStatus fgWasmSpillRefs(); + PhaseStatus fgWasmProfInstrument(); #ifdef DEBUG void fgDumpWasmControlFlow(); void fgDumpWasmControlFlowDot(); diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 497608425b7d03..6c51c872925b10 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -134,6 +134,7 @@ CompPhaseNameMacro(PHASE_WASM_TRANSFORM_SCCS, "Wasm transform sccs", CompPhaseNameMacro(PHASE_WASM_CONTROL_FLOW, "Wasm control flow", false, -1, false) CompPhaseNameMacro(PHASE_WASM_SPILL_REFS, "Wasm spill refs", false, -1, false) CompPhaseNameMacro(PHASE_WASM_VIRTUAL_IP, "Wasm virtual IP", false, -1, false) +CompPhaseNameMacro(PHASE_WASM_PROF_INSTRUMENT, "Wasm profiler instrumentation", false, -1, false) CompPhaseNameMacro(PHASE_ASYNC, "Transform async", false, -1, true) CompPhaseNameMacro(PHASE_LCLVARLIVENESS, "Local var liveness", true, -1, false) diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 397734442f3673..119af0c4e3a08e 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -2466,7 +2466,7 @@ PhaseStatus Compiler::fgWasmSpillRefs() for (GenTree* tree : LIR::AsRange(block)) { - if (tree->IsCall()) + if (tree->IsCall() && IsPotentialGCSafePoint(tree)) { // If we have a call, consume the call's operands from the defs list BEFORE spilling // any live refs. This is because a GC-ref node which is only live INTO (and not across) another call @@ -3503,6 +3503,80 @@ void Compiler::fgWasmEhTransformTry(ArrayStack* catchRetBlocks, } } +//----------------------------------------------------------------------------- +// fgWasmProfInstrument: insert EventPipe CPU-sampling samplepoints +// +// Returns: +// suitable phase status +// +// Notes: +// Mirrors the interpreter's INTOP_PROF_SAMPLEPOINT placement (see +// src/coreclr/interpreter/compiler.cpp): one samplepoint at method entry and +// one on every loop back-edge. Emitted only for methods matching the +// WasmPerformanceInstrumentation MethodSet filter (same key the interpreter +// uses, so both engines share the filter and the runtime skip counter). +// +// Each samplepoint is a call to CORINFO_HELP_WASM_PROF_SAMPLEPOINT. The later +// fgWasmVirtualIP phase inserts its per-block Virtual IP store ahead of the +// samplepoint, giving the cooperative stack walk a correct Virtual IP. +// +PhaseStatus Compiler::fgWasmProfInstrument() +{ + // Codegen only supports single-threaded wasm today; the shared runtime skip + // counter and cooperative sampling both assume PERFTRACING_DISABLE_THREADS. + assert(!WASM_THREAD_SUPPORT); + + if (!JitConfig.WasmPerformanceInstrumentation().contains(info.compMethodHnd, info.compClassHnd, + &info.compMethodInfo->args)) + { + return PhaseStatus::MODIFIED_NOTHING; + } + + auto insertSamplepoint = [this](BasicBlock* block) { + GenTree* samplepoint = gtNewHelperCallNode(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, TYP_VOID); + samplepoint = fgMorphCall(samplepoint->AsCall()); + gtSetEvalOrder(samplepoint); + LIR::AsRange(block).InsertAtBeginning(LIR::SeqTree(this, samplepoint)); + }; + + unsigned samplepointsAdded = 0; + + // A DFS back edge identifies its ancestor target as a loop header. Sample at + // every unique loop header, including loops formed through EH flow. + // Unlike fgHasCycleWithoutGCSafePoint we do NOT skip BBF_GC_SAFE_POINT blocks: + // the framework is not instrumented, so a loop calling only BCL methods would + // otherwise never sample. + FlowGraphDfsTree* const dfsTree = fgComputeDfs(); + BitVecTraits traits = dfsTree->PostOrderTraits(); + BitVec samplepointBlocks(BitVecOps::MakeEmpty(&traits)); + + // Method entry. + BitVecOps::AddElemD(&traits, samplepointBlocks, fgFirstBB->bbPostorderNum); + + for (unsigned i = 0; i < dfsTree->GetPostOrderCount(); i++) + { + BasicBlock* const source = dfsTree->GetPostOrder(i); + source->VisitAllSuccs(this, [&](BasicBlock* target) { + assert(dfsTree->Contains(target)); + if (dfsTree->IsAncestor(target, source)) + { + BitVecOps::AddElemD(&traits, samplepointBlocks, target->bbPostorderNum); + } + + return BasicBlockVisit::Continue; + }); + } + + BitVecOps::VisitBits(&traits, samplepointBlocks, [&](unsigned postorderNum) { + insertSamplepoint(dfsTree->GetPostOrder(postorderNum)); + samplepointsAdded++; + return true; + }); + + JITDUMP("Added %u Wasm profiler samplepoint(s)\n", samplepointsAdded); + return (samplepointsAdded > 0) ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; +} + //----------------------------------------------------------------------------- // fgWasmVirtualIP: set up virtual IP mapping for EH and calls // diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index f1e3887194c52f..d0f70d1bdcd841 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -920,6 +920,9 @@ CONFIG_INTEGER(JitDispIns, "JitDispIns", 0) CONFIG_STRING(JitR2RUnsupportedRange, "JitR2RUnsupportedRange") // Enable processing methods with funclets. Set to 0 to bail to R2R unsupported before codegen. RELEASE_CONFIG_INTEGER(JitWasmFunclets, "JitWasmFunclets", 1) +// Method filter for WASM performance instrumentation profiler. Uses standard MethodSet pattern format. +// Same key as the interpreter's InterpConfig.WasmPerformanceInstrumentation so both engines share it. +RELEASE_CONFIG_METHODSET(WasmPerformanceInstrumentation, "WasmPerformanceInstrumentation") #endif // defined(TARGET_WASM) // Allow to enregister locals with struct type. diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 00f072c7528b5a..b1a6dc58024849 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -1757,6 +1757,11 @@ void HelperCallProperties::init() exceptions = ExceptionSetFlags::None; break; + case CORINFO_HELP_WASM_PROF_SAMPLEPOINT: + isNoGC = true; + exceptions = ExceptionSetFlags::None; + break; + case CORINFO_HELP_TAILCALL: // Never present on stack at the time of GC. case CORINFO_HELP_STACK_PROBE: case CORINFO_HELP_CHECK_OBJ: diff --git a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h index b3d4c5732b4a30..2bfce68a59c289 100644 --- a/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h +++ b/src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h @@ -12,7 +12,7 @@ struct ReadyToRunHeaderConstants static const uint32_t Signature = 0x00525452; // 'RTR' static const uint32_t CurrentMajorVersion = 32; - static const uint32_t CurrentMinorVersion = 0; + static const uint32_t CurrentMinorVersion = 1; }; struct ReadyToRunHeader diff --git a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs index 5c2bd42f340552..c57cd5549d54b8 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs @@ -16,7 +16,7 @@ internal struct ReadyToRunHeaderConstants public const uint Signature = 0x00525452; // 'RTR' public const ushort CurrentMajorVersion = 32; - public const ushort CurrentMinorVersion = 0; + public const ushort CurrentMinorVersion = 1; } #if READYTORUN #pragma warning disable 0169 diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index a8132da1cafbaa..0cbd61ee914695 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -383,6 +383,7 @@ public enum ReadyToRunHelper InitClass = 0x116, InitInstClass = 0x117, R2RToInterpreter = 0x118, + WasmProfSamplepoint = 0x119, // ********************************************************************************************** // diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index d874da3451ae71..8a99fbdb4d676f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -290,6 +290,8 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_ALLOC_CONTINUATION_METHOD, CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_WASM_PROF_SAMPLEPOINT, // WASM only: cooperative EventPipe CPU-sampling samplepoint + CORINFO_HELP_COUNT, } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index c325e1c756e658..88a261ab320162 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -1394,6 +1394,10 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum, out MethodDesc id = ReadyToRunHelper.GCPoll; break; + case CorInfoHelpFunc.CORINFO_HELP_WASM_PROF_SAMPLEPOINT: + id = ReadyToRunHelper.WasmProfSamplepoint; + break; + case CorInfoHelpFunc.CORINFO_HELP_GETCURRENTMANAGEDTHREADID: id = ReadyToRunHelper.GetCurrentManagedThreadId; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 5d69d699448c79..a7d05feea50279 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1814,6 +1814,10 @@ private void ParseHelper(StringBuilder builder) builder.Append("GCPOLL"); break; + case ReadyToRunHelper.WasmProfSamplepoint: + builder.Append("WASM_PROF_SAMPLEPOINT"); + break; + case ReadyToRunHelper.GetCurrentManagedThreadId: builder.Append("GET_CURRENT_MANAGED_THREAD_ID"); break; diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 143f0535032a1e..cbed83d3b3ad1f 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -467,6 +467,11 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_pNativeImage = NULL; if ((m_pReadyToRunInfo = ReadyToRunInfo::Initialize(this, pamTracker)) != NULL) { +#ifdef TARGET_WASM + // Direct R2R calls can execute before assembly activation. Register virtual IPs as soon as + // the R2R info is installed so stack walks can resolve those frames. + m_pReadyToRunInfo->RegisterVirtualIPRange(this); +#endif if (m_pReadyToRunInfo->SkipTypeValidation()) m_dwPersistedFlags = m_dwPersistedFlags | SKIP_TYPE_VALIDATION; // Skip type validation on System diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-wasm-sampling.cpp b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-wasm-sampling.cpp index 2f2c04a0cde250..e29e1c82c1b523 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-wasm-sampling.cpp +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-wasm-sampling.cpp @@ -140,6 +140,14 @@ void ep_rt_coreclr_sample_profiler_disabled(void) s_skipsPerPeriod = 1; } +// True only while an EventPipe CPU-sampling session is active. R2R samplepoints use this to +// skip the InlinedCallFrame anchor push on the overwhelmingly common no-session path. +extern "C" bool SamplingProfiler_IsActive() +{ + LIMITED_METHOD_CONTRACT; + return s_currentSamplingEvent != nullptr && s_currentSamplingThread != nullptr; +} + // Called from the interpreter's INTOP_PROF_SAMPLEPOINT handler. // On single-threaded WASM this is the cooperative sampling entry point. // On multi-threaded platforms the opcode is never emitted. diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 4cc664d1dade75..9b595fb9c87dbf 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -96,6 +96,11 @@ EXTERN_C FCDECL1(PCODE, JIT_PatchpointForced, int ilOffset); EXTERN_C FCDECL0(void, JIT_PollGC); +#ifdef TARGET_WASM +// WASM EventPipe CPU-sampling samplepoint helper (defined in vm/wasm/helpers.cpp). +EXTERN_C void JIT_WasmProfSamplepoint(uintptr_t callersStackPointer, PCODE portableEntryPointContext); +#endif // TARGET_WASM + #ifndef JIT_GetGCStaticBaseNoCtor #define JIT_GetGCStaticBaseNoCtor JIT_GetGCStaticBaseNoCtor_Portable #endif diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 6b57b3e8c29d8b..fe5c1657969202 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -491,6 +491,45 @@ EXTERN_C __attribute__((naked)) void F_CALL_CONV JIT_PollGC(uintptr_t callersSta [JIT_PollGCRarePath] "i" (JIT_PollGCRarePath)); } +// EventPipe CPU-sampling samplepoint helper for R2R (native wasm) code on single-threaded WASM. +// Emitted by the JIT at method entry and loop back-edges when a method matches the +// WasmPerformanceInstrumentation filter. Cooperative throughout; on an active sampling session it +// anchors a managed stack walk with an InlinedCallFrame derived from the caller's shadow SP, then +// delegates to the shared SamplingProfiler_OnSamplepoint (which owns the adaptive skip counter). +#if defined(FEATURE_PERFTRACING) && defined(PERFTRACING_DISABLE_THREADS) +EXTERN_C bool SamplingProfiler_IsActive(); +EXTERN_C void SamplingProfiler_OnSamplepoint(); +#endif + +HCIMPL0(void, JIT_WasmProfSamplepoint) +{ + FCALL_CONTRACT; + +#if defined(FEATURE_PERFTRACING) && defined(PERFTRACING_DISABLE_THREADS) + if (!SamplingProfiler_IsActive()) + return; + + Thread* pThread = GetThread(); + + // Anchor the walk with a cooperative InlinedCallFrame (JIT_PInvokeBeginImpl's setup minus the + // preemptive transition): StackWalkFrames starts from a zeroed context and walks the Frame chain. + InlinedCallFrame inlinedCallFrame; + ::new ((void*)&inlinedCallFrame) InlinedCallFrame(); + inlinedCallFrame.m_pCallSiteSP = (void*)callersStackPointer; + inlinedCallFrame.m_pCallerReturnAddress = INLINED_PINVOKE_FROM_R2R; + inlinedCallFrame.m_pCalleeSavedFP = 0; + inlinedCallFrame.m_pThread = pThread; + inlinedCallFrame.Push(); + + SamplingProfiler_OnSamplepoint(); + + inlinedCallFrame.Pop(); +#else + UNREFERENCED_PARAMETER(callersStackPointer); +#endif // FEATURE_PERFTRACING && PERFTRACING_DISABLE_THREADS +} +HCIMPLEND + void InitJITHelpers1() { /* no-op WASM-TODO do we need to do anything for the interpreter? */ diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.InBuild.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.InBuild.targets new file mode 100644 index 00000000000000..b200e8a3e7c2ea --- /dev/null +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.InBuild.targets @@ -0,0 +1,57 @@ + + + + + <_WasmCrossgen2InBuildUsable Condition="'$(Crossgen2InBuildDir)' != '' and Exists('$(Crossgen2InBuildDir)')">true + <_WasmCrossgen2ExeSuffix Condition="'$(ExeSuffix)' != ''">$(ExeSuffix) + <_WasmCrossgen2ExeSuffix Condition="'$(_WasmCrossgen2ExeSuffix)' == '' and $([MSBuild]::IsOSPlatform('windows'))">.exe + + <_WasmCrossgen2RootExe>$([MSBuild]::NormalizePath('$(Crossgen2InBuildDir)', 'crossgen2$(_WasmCrossgen2ExeSuffix)')) + <_WasmCrossgen2ToolsExe>$([MSBuild]::NormalizePath('$([MSBuild]::NormalizeDirectory('$(Crossgen2InBuildDir)', 'tools'))', 'crossgen2$(_WasmCrossgen2ExeSuffix)')) + $(_WasmCrossgen2RootExe) + $(_WasmCrossgen2ToolsExe) + + <_WasmResolvedCrossgen2Dir Condition="'$(Crossgen2Path)' != ''">$([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetDirectoryName('$(Crossgen2Path)')))) + + <_WasmR2RTargetOS Condition="'$(TargetOS)' != ''">$(TargetOS) + <_WasmR2RTargetOS Condition="'$(_WasmR2RTargetOS)' == ''">browser + <_WasmR2RTargetArch Condition="'$(TargetArchitecture)' != ''">$(TargetArchitecture) + <_WasmR2RTargetArch Condition="'$(_WasmR2RTargetArch)' == ''">wasm + + + + + + + + + + + + diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets index 6d09e1c20b68f8..519c68b6b62314 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets @@ -2,67 +2,20 @@ *********************************************************************************************** Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets -ReadyToRun compiler override for CoreCLR browser-wasm, appended to AfterMicrosoftNETSdkTargets by -Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props so it imports after Microsoft.NET.Publish.targets and -its ResolveReadyToRunCompilers wins. Mirrors AppleBuild.ReadyToRun.targets. - -When an in-build crossgen2 is available (in-tree / dev builds) the Crossgen2Tool item is set from it. Otherwise -the SDK-resolved crossgen2 pack (@(ResolvedCrossgen2Pack), which the SDK restores when PublishReadyToRun is set) -is used - so this override never shadows that resolution into an empty tool. Inert for normal consumers. +CoreCLR browser-wasm ReadyToRun hooks used with both SDK-resolved and in-build crossgen2. +Appended to AfterMicrosoftNETSdkTargets by Microsoft.NET.Sdk.WebAssembly.Browser.props. Copyright (c) .NET Foundation. All rights reserved. *********************************************************************************************** --> - + - <_WasmCrossgen2InBuildUsable Condition="'$(Crossgen2InBuildDir)' != '' and Exists('$(Crossgen2InBuildDir)')">true - <_WasmCrossgen2ExeSuffix Condition="'$(ExeSuffix)' != ''">$(ExeSuffix) - <_WasmCrossgen2ExeSuffix Condition="'$(_WasmCrossgen2ExeSuffix)' == '' and $([MSBuild]::IsOSPlatform('windows'))">.exe - - <_WasmCrossgen2RootExe>$([MSBuild]::NormalizePath('$(Crossgen2InBuildDir)', 'crossgen2$(_WasmCrossgen2ExeSuffix)')) - <_WasmCrossgen2ToolsExe>$([MSBuild]::NormalizePath('$([MSBuild]::NormalizeDirectory('$(Crossgen2InBuildDir)', 'tools'))', 'crossgen2$(_WasmCrossgen2ExeSuffix)')) - $(_WasmCrossgen2RootExe) - $(_WasmCrossgen2ToolsExe) - - <_WasmResolvedCrossgen2Dir Condition="'$(Crossgen2Path)' != ''">$([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetDirectoryName('$(Crossgen2Path)')))) - - <_WasmR2RTargetOS Condition="'$(TargetOS)' != ''">$(TargetOS) - <_WasmR2RTargetOS Condition="'$(_WasmR2RTargetOS)' == ''">browser - <_WasmR2RTargetArch Condition="'$(TargetArchitecture)' != ''">$(TargetArchitecture) - <_WasmR2RTargetArch Condition="'$(_WasmR2RTargetArch)' == ''">wasm + $(PublishReadyToRunCrossgen2ExtraArgs);--codegenopt:WasmPerformanceInstrumentation=$(_WasmPerfInstFilter) - - - - - - - - - - - + diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props index 0dc363e8833f6f..0266c4bfc281a5 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props @@ -2,13 +2,13 @@ *********************************************************************************************** Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props -CoreCLR browser-wasm ReadyToRun wiring, isolated from Mono. Imported (props-time) from +CoreCLR browser-wasm in-build ReadyToRun task wiring. Imported (props-time) from Microsoft.NET.Sdk.WebAssembly.Browser.props only when an in-build crossgen2 is provided. The base SDK's ReadyToRun pipeline predates wasm support (dotnet/sdk#55785) and emits composite images for wasm, which strip the assembly manifest so the runtime fails coreclr_initialize with 0x80131018. -Activate the runtime's wasm-aware Crossgen2Tasks shim and the ResolveReadyToRunCompilers override, both -appended to AfterMicrosoftNETSdkTargets, shim first so the override wins. Mirrors AppleBuild.props. +Activate the runtime's wasm-aware Crossgen2Tasks shim and append the in-build +ResolveReadyToRunCompilers override after the shim so the override wins. Mirrors AppleBuild.props. Inert for Mono and for normal consumers, which resolve crossgen2 through the base SDK. @@ -21,6 +21,6 @@ Copyright (c) .NET Foundation. All rights reserved. $(AfterMicrosoftNETSdkTargets);$(Crossgen2SdkOverrideTargetsPath) - $(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets + $(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.InBuild.targets diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets index 3c08f87116f957..fd108fda18e093 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets @@ -47,6 +47,8 @@ Copyright (c) .NET Foundation. All rights reserved. <_WasmBuildPrebuiltR2RDirectory Condition="'$(_WasmCoreClrUnderPublish)' == 'true'">$(_WasmPublishR2RDir) <_WasmBuildPrebuiltR2RDirectory Condition="'$(_WasmCoreClrUnderPublish)' != 'true'">$(_WasmRuntimePackR2RDir) + - diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props index 902c930716bcf8..30cfa34ccc3695 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.props @@ -40,14 +40,15 @@ Copyright (c) .NET Foundation. All rights reserved. true - + + + $(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets + + diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs index ec29199e0a2d50..cdf23f77be4153 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs @@ -36,53 +36,66 @@ public EventPipeDiagnosticsTests(ITestOutputHelper output, SharedBuildPerTestCla [Fact] [TestCategory("native"), TestCategory("mono")] - public Task BlazorEventPipeTestWithCpuSamplesAOT() => BlazorEventPipeTestWithCpuSamples(Configuration.Release, aot: true); + public Task BlazorEventPipeTestWithCpuSamplesAOT() => BlazorEventPipeTestWithCpuSamplesCore(Configuration.Release, aot: true, readyToRun: false); [Theory] [InlineData(Configuration.Debug, false)] [InlineData(Configuration.Release, false)] - public async Task BlazorEventPipeTestWithCpuSamples(Configuration config, bool aot) + public Task BlazorEventPipeTestWithCpuSamples(Configuration config, bool aot) + => BlazorEventPipeTestWithCpuSamplesCore(config, aot, readyToRun: false); + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsCoreClrRuntime))] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + [TestCategory("no-workload")] + public Task BlazorEventPipeTestWithCpuSamplesReadyToRun(Configuration config) + => BlazorEventPipeTestWithCpuSamplesCore(config, aot: false, readyToRun: true); + + private async Task BlazorEventPipeTestWithCpuSamplesCore(Configuration config, bool aot, bool readyToRun) { - // force no R2R until https://github.com/dotnet/runtime/issues/130521 - string extraProperties = @" + string extraProperties = $@" all,interval=0 true 0 - false + {(readyToRun ? "true" : "false")} + false true "; - ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, "blazor_cpu_samples", extraProperties: extraProperties); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, + readyToRun ? "blazor_cpu_samples_r2r" : "blazor_cpu_samples_interp", extraProperties: extraProperties); - UpdateCounterPage(); + UpdateCounterPage(useCpuSamplingTarget: true); - BuildProject(info, config, new BuildOptions(AssertAppBundle: false)); + string extraBuildArgs = readyToRun + ? $"{GetR2RBuildArgs(config)} -p:UsingBrowserRuntimeWorkload=false" + : string.Empty; + BlazorPublish(info, config, new PublishOptions( + UseCache: false, + AssertAppBundle: false, + ExtraMSBuildArgs: extraBuildArgs)); async Task CollectCpuSamplesTest(IPage page) { - await SetupCounterPage(page, "cpuprofile.nettrace", "globalThis.getDotnetRuntime(0).collectCpuSamples({ durationSeconds: 5.0, skipDownload: true })"); - await ClickAndCollect(page); + await SetupCounterPage(page, "cpuprofile.nettrace", "globalThis.getDotnetRuntime(0).collectCpuSamples({ durationSeconds: 5.0 })", uploadTrace: false); + await ClickAndCollect(page, Path.Combine(info.LogPath, "cpuprofile.nettrace")); } - // Run the test using the custom handler - await RunForBuildWithDotnetRun(new BlazorRunOptions( + var runOptions = new BlazorRunOptions( Configuration: config, Test: CollectCpuSamplesTest, TimeoutSeconds: 60, - CheckCounter: false, - ServerEnvironment: new Dictionary - { - ["DEVSERVER_UPLOAD_PATH"] = info.LogPath, - ["DEVSERVER_UPLOAD_PATTERN"] = uploadPattern - } - )); + CheckCounter: false); - bool appMethodFound = false; + await RunForPublishWithWebServer(runOptions); + + bool sampledMethodFound = false; bool readyToRunMethodFound = false; using (var source = TraceLog.OpenOrConvert(ConvertTrace(info, "cpuprofile.nettrace"))) { - appMethodFound = source.CallStacks.Any(stack => stack.CodeAddress.FullMethodName == "BlazorBasicTestApp.Pages.Counter.IncrementCount()"); - if (!appMethodFound) + sampledMethodFound = source.CallStacks.Any(stack => stack.CodeAddress.FullMethodName.StartsWith( + "BlazorBasicTestApp.Pages.Counter.CpuSamplingTarget(", StringComparison.Ordinal)); + if (!sampledMethodFound) { foreach (var stack in source.CallStacks) { @@ -93,8 +106,8 @@ await RunForBuildWithDotnetRun(new BlazorRunOptions( readyToRunMethodFound = source.CodeAddresses.Any(address => address.FullMethodName.StartsWith("System.Buffer.Memmove(", StringComparison.Ordinal)); } - Assert.True(appMethodFound, "The cpuprofile.nettrace should contain stack frames for the 'Counter.IncrementCount' method"); - if (BuildTestBase.IsCoreClrRuntime) + Assert.True(sampledMethodFound, "The cpuprofile.nettrace should contain stack frames for the 'Counter.CpuSamplingTarget' method"); + if (BuildTestBase.IsCoreClrRuntime && !readyToRun) { Assert.True(readyToRunMethodFound, "The cpuprofile.nettrace should contain rundown information for the ReadyToRun 'System.Buffer.Memmove' method"); } @@ -362,9 +375,9 @@ private static Dictionary ExtractInstrumentNames(ProjectInfo info, return dictionary; } - private void UpdateCounterPage() + private void UpdateCounterPage(bool useCpuSamplingTarget = false) { - UpdateFile(Path.Combine("Pages", "Counter.razor"), new Dictionary { + var replacements = new Dictionary { { @"currentCount++;", """ @@ -380,13 +393,34 @@ private void UpdateCounterPage() Console.WriteLine(sb.ToString()); } } - currentCount++; - """ + ${increment} + """.Replace("${increment}", useCpuSamplingTarget ? "currentCount = CpuSamplingTarget(currentCount);" : "currentCount++;") } - }); + }; + + if (useCpuSamplingTarget) + { + replacements.Add( + @"private async Task IncrementCount()", + """ + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private static int CpuSamplingTarget(int value) + { + for (int i = 0; i < 100_000; i++) + { + value = unchecked((value * 31) + i); + } + return value; + } + + private async Task IncrementCount() + """); + } + + UpdateFile(Path.Combine("Pages", "Counter.razor"), replacements); } - private async Task SetupCounterPage(IPage page, string fileName, string traceCommand) + private async Task SetupCounterPage(IPage page, string fileName, string traceCommand, bool uploadTrace = true) { await Task.Delay(500); // Navigate to the Counter page @@ -403,7 +437,12 @@ private async Task SetupCounterPage(IPage page, string fileName, string traceCom const traces = await ${traceCommand}; console.log(`Tracing done ${new Date().toISOString()}`); - + + if (!${uploadTrace}) { + console.log(`WASM EXIT 0`); + return; + } + // concatenate the buffers into a single Uint8Array const concatenated = new Uint8Array(traces.reduce((acc, curr) => acc + curr.byteLength, 0)); let offset = 0; @@ -427,11 +466,13 @@ await fetch(`/upload/${filename}`, { }; console.log(`globalThis.collectAndUpload method created ${new Date().toISOString()}`); """; - up = up.Replace("${traceCommand}", traceCommand).Replace("${filename}", fileName); + up = up.Replace("${traceCommand}", traceCommand) + .Replace("${filename}", fileName) + .Replace("${uploadTrace}", uploadTrace ? "true" : "false"); await page.EvaluateAsync(up); } - private async Task ClickAndCollect(IPage page) + private async Task ClickAndCollect(IPage page, string? downloadPath = null) { // A runtime trap never rejects donePromise: the runtime catches it, reports it as a console // error and exits non-zero, leaving the promise unsettled forever. @@ -454,6 +495,8 @@ void OnConsoleMessage(object? sender, IConsoleMessage message) try { + Task? downloadTask = downloadPath is null ? null : page.WaitForDownloadAsync(); + // Use void to prevent Playwright from awaiting the returned Promise, // so tracing runs in parallel with button clicks below. await page.EvaluateAsync(@"void (globalThis.donePromise = globalThis.collectAndUpload())"); @@ -486,6 +529,8 @@ void OnConsoleMessage(object? sender, IConsoleMessage message) } await collected; + if (downloadTask is not null && downloadPath is not null) + await (await downloadTask).SaveAsAsync(downloadPath); } finally { diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 4604b7086bfd47..a2b424fb340e5e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -385,6 +385,30 @@ protected static string GetSkiaSharpReferenceItems() "; + // Use the in-build crossgen2 when the test leg provides it. Composite builds also need the + // wasm-aware task shim until the base SDK supports wasm R2R output naming. + protected static string GetR2RBuildArgs(Configuration config, bool composite = false) + { + string? baseDir = EnvironmentVariables.BaseDir; + if (string.IsNullOrEmpty(baseDir)) + return string.Empty; + + string hostArch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + string crossgenDir = Path.Combine(baseDir, "coreclr", $"browser.wasm.{config}", hostArch, "crossgen2"); + string shimDir = Path.Combine(baseDir, "Crossgen2Tasks", config.ToString()); + string shimProps = Path.Combine(shimDir, "Microsoft.NET.CrossGen.props"); + string shimTargets = Path.Combine(shimDir, "Microsoft.NET.CrossGen.targets"); + + var args = new List(); + if (Directory.Exists(crossgenDir)) + args.Add($"-p:Crossgen2InBuildDir=\"{crossgenDir}\""); + if (composite && File.Exists(shimProps)) + args.Add($"-p:Crossgen2SdkOverridePropsPath=\"{shimProps}\""); + if (composite && File.Exists(shimTargets)) + args.Add($"-p:Crossgen2SdkOverrideTargetsPath=\"{shimTargets}\""); + return string.Join(" ", args); + } + protected static string s_mainReturns42 = @" public class TestClass { public static int Main() diff --git a/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs b/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs index a802fcbd5affe0..1b13a9c93be8e1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs @@ -60,10 +60,6 @@ public CoreCLRWasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTe // comparison is textual, so a numerically equal but differently spelled size counts as // a mismatch. That only costs an unnecessary relink, never a mismatched binary. { "2097152", true }, - // WasmPerformanceInstrumentation would force a relink, but that defaulting is temporarily - // disabled in BrowserWasmApp.CoreCLR.targets pending https://github.com/dotnet/runtime/issues/132772, - // so it currently does not relink. - { "all", false }, }; [Theory] diff --git a/src/mono/wasm/Wasm.Build.Tests/ReadyToRunTests.cs b/src/mono/wasm/Wasm.Build.Tests/ReadyToRunTests.cs index 84f705655670cd..a4ba82bd5d3a8a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ReadyToRunTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ReadyToRunTests.cs @@ -142,6 +142,30 @@ public void FrameworkAssembliesAreNotReadyToRunWhenDisabled(Configuration config AssertPerAppCrossgenRan(config, expected: false); } + [ConditionalTheory(typeof(BuildTestBase), nameof(IsCoreClrRuntime))] + [InlineData(false)] + [InlineData(true)] + public void PerformanceInstrumentationIsRejectedForBuild(bool readyToRun) + { + ProjectInfo info = CopyTestAsset( + Configuration.Debug, + aot: false, + TestAsset.BlazorBasicTestApp, + $"profiling_build_r2r_{readyToRun}", + extraProperties: $""" + true + {readyToRun} + all + """); + + (string _, string output) = BlazorBuild( + info, + Configuration.Debug, + new BuildOptions(ExpectSuccess: false, AssertAppBundle: false)); + + Assert.Contains("Only published applications are supported for CPU profiling", output); + } + [ConditionalTheory(typeof(BuildTestBase), nameof(IsCoreClrRuntime))] [InlineData(Configuration.Release)] [TestCategory("no-workload")] @@ -151,7 +175,6 @@ public void CompositeRequiresWebcil(Configuration config) extraProperties: "truetruefalse"); (string _, string output) = BlazorPublish(info, config, new PublishOptions(ExpectSuccess: false, ExtraMSBuildArgs: GetR2RBuildArgs(config, composite: true))); - Assert.Contains("PublishReadyToRunComposite for CoreCLR browser-wasm requires WebCIL-in-Wasm assemblies", output); } @@ -264,35 +287,6 @@ private void AssertPerAppCrossgenRan(Configuration config, bool expected) Assert.True(imageCount == 0, $"Expected no per-app crossgen2 output, found {imageCount} file(s) under '{r2rDir}'."); } - // Wire the in-build crossgen2 when this leg shipped it, and for composite the wasm-aware Crossgen2Tasks shim: - // composite needs the shim's wasm output naming (.r2r.wasm owner, .wasm stubs), which the base - // SDK's ReadyToRun tasks don't implement yet (dotnet/sdk#56395). Per-assembly R2R keeps the base SDK tasks so - // that path stays covered. Each is passed only when present under BASE_DIR: the no-workload leg resolves - // crossgen2 itself from the SDK pack (the SDK restores it when PublishReadyToRun is set), so passing a - // non-existent Crossgen2InBuildDir there would break the call-helpers generator. All inert if BASE_DIR is unset. - // TODO-WASM https://github.com/dotnet/runtime/issues/135023: drop the composite-only shim once the SDK names wasm R2R outputs. - private static string GetR2RBuildArgs(Configuration config, bool composite) - { - string? baseDir = EnvironmentVariables.BaseDir; - if (string.IsNullOrEmpty(baseDir)) - return string.Empty; - - string hostArch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); - string crossgenDir = Path.Combine(baseDir, "coreclr", $"browser.wasm.{config}", hostArch, "crossgen2"); - string shimDir = Path.Combine(baseDir, "Crossgen2Tasks", config.ToString()); - string shimProps = Path.Combine(shimDir, "Microsoft.NET.CrossGen.props"); - string shimTargets = Path.Combine(shimDir, "Microsoft.NET.CrossGen.targets"); - - var args = new List(); - if (Directory.Exists(crossgenDir)) - args.Add($"-p:Crossgen2InBuildDir=\"{crossgenDir}\""); - if (composite && File.Exists(shimProps)) - args.Add($"-p:Crossgen2SdkOverridePropsPath=\"{shimProps}\""); - if (composite && File.Exists(shimTargets)) - args.Add($"-p:Crossgen2SdkOverrideTargetsPath=\"{shimTargets}\""); - return string.Join(" ", args); - } - private static int GetReadyToRunTableSize(string webcilPath) { using FileStream stream = File.OpenRead(webcilPath);