[do not review] test regression 2#124522
Conversation
Set `isTailcall` to `false`, when preparing the call. This is similar to how we set other state vars for the call, so I did it this way. Alternatively we can reset the `isTailcall` after updating the stack and frame for tailcalls. That would invalidate the meaning of the variable for the rest of the tailcall though, so we would need to rename it at least to signal, that it is valid only in the beginning of the tailcall.
Based on changes from the previous branch (https://github.com/radekdoulik/runtime/tree/clr-wasm-runtime-tests-with-node)
…s' into clr-wasm-runtime-tests-on-node-2
|
Tagging subscribers to this area: @agocke, @dotnet/runtime-infrastructure |
There was a problem hiding this comment.
Pull request overview
This PR adds support for running CoreCLR WASM tests with NodeJS and implements native symbol map functionality for better stack trace symbolication in WASM environments. The changes enable running tests on platforms that use NodeJS instead of V8 shell, improving test coverage for CoreCLR on browser/WASM.
Changes:
- Added
--nodecommand-line option to test runners (run.sh, run.py, run.cmd) with automatic default for WASM architecture - Implemented WASM symbol map loading and stack trace symbolication for debugging
- Refactored CoreCLR prestub logic to move DoPrestub calls out of CallDescrWorkerInternal
- Improved exit code propagation through BrowserHost_ExecuteAssembly and BrowserHost_ShutdownCoreCLR
- Updated threading tests to use ConditionalFact instead of ActiveIssue annotations
- Added IsWasm property to PlatformDetection for test filtering
- Restructured build pipelines for browser_wasm with separate Release and CoreCLR_ReleaseLibraries builds
Reviewed changes
Copilot reviewed 85 out of 86 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/run.sh, run.py, run.cmd | Added --node option for NodeJS test execution on WASM |
| src/tests/Common/CoreCLRTestLibrary/PlatformDetection.cs | Added IsWasm property combining IsBrowser and IsWasi |
| src/tests/Common/CLRTest.Execute.{Bash,Batch}.targets | Implemented NodeJS execution path with timeout support |
| src/tests/baseservices/threading/coverage/OSThreadId/OSThreadId.cs | Changed from ActiveIssue to ConditionalFact for threading support |
| src/tests/Loader/classloader/StaticVirtualMethods/*.il | Added ActiveIssue attributes for WASM platform |
| src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts | Implemented symbol map parsing and stack trace symbolication |
| src/native/corehost/browserhost/loader/logging.ts | Refactored error() signature and added normalizeException() |
| src/native/corehost/browserhost/browserhost.cpp | Added BrowserHost_ShutdownCoreCLR with proper exit code handling |
| src/coreclr/vm/method.hpp | Added GetInterpreterCodeWithPrestub() helper method |
| src/coreclr/vm/wasm/calldescrworkerwasm.cpp | Removed prestub call (now called earlier via helper) |
| src/coreclr/vm/reflectioninvocation.cpp | Added prestub call before CallDescrWorkerInternal |
| src/coreclr/vm/callhelpers.cpp | Added prestub calls for PORTABLE_ENTRYPOINTS |
| src/coreclr/vm/dllimport.cpp | Added debug printf statements (issue) |
| src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs | Added missing cookie signatures for interop |
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/*.cs | Added EmitSymbolMap property support |
| eng/pipelines/runtime.yml | Restructured browser_wasm build configurations |
| eng/pipelines/coreclr/templates/*.yml | Added browser_wasm test job configurations |
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); |
There was a problem hiding this comment.
Debug printf statements should not be committed to production code. These should be replaced with proper logging or removed entirely.
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } |
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pCallDescrData->pTarget); | ||
| InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); | ||
| if (targetIp == NULL) | ||
| { | ||
| GCX_PREEMP(); | ||
| (void)pMethod->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); | ||
| targetIp = pMethod->GetInterpreterCode(); | ||
| } | ||
| _ASSERTE(targetIp != nullptr); |
There was a problem hiding this comment.
The removal of the DoPrestub call and related GC comment changes the behavior significantly. The code now asserts that targetIp is not null instead of lazily calling DoPrestub when needed. This could cause crashes if GetInterpreterCode() returns nullptr. Ensure that all code paths guarantee the interpreter code is set before CallDescrWorkerInternal is called, or restore the prestub logic.
| GCX_COOP(); | ||
|
|
There was a problem hiding this comment.
There's a redundant GCX_COOP() call at line 375. The code is already in GCX_COOP() mode from line 336. This second call is unnecessary and should be removed.
| GCX_COOP(); |
| *ppMD = pMD; | ||
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); |
There was a problem hiding this comment.
Debug printf statement should not be committed to production code. This will output to stdout in all builds, not just debug builds. If logging is needed, use appropriate logging mechanisms or wrap in #ifdef _DEBUG.
| *ppMD = pMD; | |
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| *ppMD = pMD; | |
| #ifdef _DEBUG | |
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| #endif // _DEBUG | |
| return; | |
| } | |
| } | |
| #ifdef _DEBUG | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| #endif // _DEBUG |
| // revisited to ensure correctness. | ||
|
|
||
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pCallDescrData->pTarget); | ||
| InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); |
There was a problem hiding this comment.
Removing the DoPrestub call and related GC protection logic appears to assume that GetInterpreterCode() will always return non-null. The assertion at line 19 will crash if the method hasn't been prestubbed. The comment about GC protection being needed suggests this was a known issue. Verify that all callers now ensure prestubbing happens before calling CallDescrWorkerInternal, otherwise this introduces a regression where methods that haven't been jitted will crash instead of being lazily compiled.
| InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); | |
| InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); | |
| if (targetIp == nullptr) | |
| { | |
| pMethod->DoPrestub(nullptr); | |
| targetIp = pMethod->GetInterpreterCode(); | |
| } |
| ( | ||
| sleep "$timeout_sec" | ||
| if [ $? -eq 0 ]; then | ||
| echo "Process $pid exceeded timeout of $timeout_sec seconds. Killing..." | ||
| if kill -0 "$pid" 2>/dev/null; then | ||
| kill -9 "$pid" 2>/dev/null | ||
| fi | ||
| fi |
There was a problem hiding this comment.
The timeout watchdog implementation has a logic issue. The condition if [ $? -eq 0 ] at line 346 always evaluates to true after sleep completes successfully, meaning the watchdog will always try to kill the process even if it completed normally. The watchdog should check if the process is still running before attempting to kill it. Consider using a different synchronization mechanism, such as checking if the PID is still alive inside the watchdog before killing, or using a signal-based approach where the main process signals the watchdog when it completes.
| } catch (exc) { | ||
| } |
There was a problem hiding this comment.
Empty catch block silently swallows all exceptions during symbol map initialization. At minimum, log the exception using console.debug or console.error to aid debugging when symbol map parsing fails.
| *ppMD = pMD; | ||
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); |
There was a problem hiding this comment.
Debug printf statement should not be committed to production code. This will output to stdout in all builds, not just debug builds. If logging is needed, use appropriate logging mechanisms or wrap in #ifdef _DEBUG.
| *ppMD = pMD; | |
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| *ppMD = pMD; | |
| #ifdef _DEBUG | |
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| #endif // _DEBUG | |
| return; | |
| } | |
| } | |
| #ifdef _DEBUG | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| #endif // _DEBUG |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 76 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/tests/JIT/Directed/Misc/function_pointer/MutualThdRecur-fptr.il:14
- This IL file now declares
Microsoft.DotNet.XUnitExtensions,System.Runtime, andTestLibrarytwice in the extern list. Duplicate.assembly externentries are easy to overlook and can break ilasm/build or cause confusing metadata. Remove the duplicate declarations and keep a single extern per assembly.
| # Set default for RunWithNodeJS when using wasm architecture | ||
| if [ "$buildArch" = "wasm" ] && [ -z "$RunWithNodeJS" ]; then | ||
| export RunWithNodeJS=1 | ||
| fi |
There was a problem hiding this comment.
RunWithNodeJS is defaulted on any buildArch=wasm, even when buildOS=wasi is explicitly selected. That will pass --node into run.py and set RunWithNodeJS=1 for WASI runs, but NodeJS is only applicable to browser/wasm execution. Consider gating the default to the browser scenario only (e.g., only when buildOS is unset or equals browser).
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); |
There was a problem hiding this comment.
The new printf diagnostics in LookupUnmanagedCallersOnlyMethodByName will emit to stdout on every lookup, which can severely impact performance and pollute test output/logs. Please remove these prints or gate them behind an existing logging facility/conditional compilation (e.g., a debug-only or feature-specific trace macro).
| printf("Found UCO method %s on type %s\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } | |
| printf("Did not find UCO method %s on type %s, looking for a method with the given name\n", methodName, fullQualifiedTypeName); | |
| return; | |
| } | |
| } |
No description provided.