Update guidelines for C++ standard library usage and build against libc++ w/ libstdc++ ABI for linux arm/arm64/x64 - #132347
Update guidelines for C++ standard library usage and build against libc++ w/ libstdc++ ABI for linux arm/arm64/x64#132347jkoritzinsky wants to merge 7 commits into
Conversation
…bc++ w/ libstdc++ ABI for linux arm/arm64/x64
|
Azure Pipelines: Successfully started running 6 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @dotnet/runtime-infrastructure |
There was a problem hiding this comment.
Pull request overview
This PR updates the Linux build matrix to request a specific C++ standard library/ABI configuration (libc++ + libstdc++ ABI) for selected Linux legs, and updates the CoreCLR coding guidelines to reflect the intended use of C/C++ standard headers/types under the new build setup.
Changes:
- Add
cxxStandardLibrary,cxxStandardLibraryStatic, andcxxAbiLibraryjob parameters to Linux arm/arm64/x64 (and linux_x64_sanitizer) entries in the platform matrix. - Update
clr-code-guide.mdguidance around standard header usage, Linux C++ library setup, and DAC/cDAC constraints.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| eng/pipelines/common/platform-matrix.yml | Adds C++ stdlib/ABI selection parameters to specific Linux build matrix legs. |
| docs/coding-guidelines/clr-code-guide.md | Revises CoreCLR guidance on standard headers/types and clarifies DAC/cDAC restrictions. |
| Using types and algorithms from the C++ standard is supported within the CoreCLR code base; however, we do not support it in our DAC/cDAC tooling. Do not use C++ Standard-defined containers, smart pointers, etc. for any fields accessed by the DAC or cDAC. Only use our collections that have cDAC contracts when you need to expose them for diagnostic tooling. | ||
|
|
||
| For non-shipping native code, like the `superpmi` tools suite, standard headers can be used without limitation. | ||
| For cases where diagnostic tooling integration is not required, you may use C++ Standard-defined types and algorithms meeting the C++ standard version CoreCLR builds with (defined in [eng/native/configurecompiler.cmake](../../eng/native/configurecompiler.cmake)). |
There was a problem hiding this comment.
I would add a note that standard library must run in place where the following are satisfied:
- It is not permitted to run under
NOTHROW, unless the API is marked withnoexcept. - It is never permitted to run under
MODE_COOPERATIVE.
It is preferred to keep standard library code and related callbacks/lambdas used in algorithms etc., under the STANDARD_VM_CONTRACT contract.
There was a problem hiding this comment.
I'm curious about the reason here. It's straight to understand the noexcept requirement, but not straight about cooperative mode restriction.
And also, are there any STL component considered "trivial enough" for safe usage anywhere? For example, I can't imagine where <bits> can't be used.
There was a problem hiding this comment.
but not straight about cooperative mode restriction.
It is far too easy to reach for a standard library collection or algorithm and create a GC starvation issue. We're being selective about this because there is an inherent cost to consuming standard library APIs and not truly understanding how they will behave.
And also, are there any STL component considered "trivial enough" for safe usage anywhere?
No. Using more standard library collections and algorithms is, at least for now, going to be limited to actual cases where there is measurable benefits. We will not accept wholesale replacements of our built-in collections for say std::vector<> or arbitrary replacement of holders for std::unique_ptr<>. The group of maintainers needs to get comfortable with best practices and when and where we can modernize the C++ without creating a maintenance burden for servicing or unlocking undefined behavior that compilers can then optimize in unexpected ways.
There was a problem hiding this comment.
I think the note here should be more nuanced around COOPERATIVE mode. I would prefer to see a spot for an allow list of apis we've determined are acceptable to use in COOPERATIVE mode unconditionally, and a list of the guarantees we want to have in COOPERATIVE mode to allow us to use stl code. Something on the order of "In COOPERATIVE mode, standard C++ library code can only be used if it is required to only perform a bounded set of operations, and cannot throw."
My preference is for for the initial allow list of apis to be empty, and to have a discussion as we attempt to grow the list. For instance, I could see us allowing C++ atomics in COOPERATIVE mode.
In addition to the COOPERATIVE restriction, I believe we need to be explicit about the set of exceptions permitted to be exposed. Notably, if an exception flows through an FCall boundary, then the runtime will crash in unpredictable ways, so all possible exception types must be caught by the runtime. For QCall boundaries, today we can handle std::bad_alloc, but any other standard C++ exception escaping a QCall will also cause unpredictable behavior. Also, I find it very hard to know exactly which exceptions might be thrown by standard C++ code. For instance, std::vector::reserve is documented to throw std::length_error, but I'm not certain if its permissible for a C++ standard implementation to throw that exception as part of std::vector::insert. Other apis, such as std::basic_string are more clear in the documentation I can find, and indicate that there is a series of exceptions that we need to handle at boundaries if we want to support standard C++ library apis that are not noexcept.
There was a problem hiding this comment.
I think the note here should be more nuanced around COOPERATIVE mode.
That is what was intented with:
We're being selective about this because there is an inherent cost to consuming standard library APIs and not truly understanding how they will behave.
We are still very early here and we start off with never and relax as we determine when appropriate. Atomic are likely to have value in the long enough time line, but for until we have a PR with a conversation and agreement, it is "never".
There was a problem hiding this comment.
For instance, I could see us allowing C++ atomics in COOPERATIVE mode.
FYI we cant use std::atomic for the FCalls cuz you can't use it on pointers to normal type.
There was a problem hiding this comment.
For instance, I could see us allowing C++ atomics in COOPERATIVE mode.
C++ atomics may also have dependency issue for non-primitive types. Should we mention dependency issue too?
There was a problem hiding this comment.
C++ atomics may also have dependency issue for non-primitive types. Should we mention dependency issue too?
wdym here? I assume all of our usecases mandate std::atomic<T>::is_always_lock_free being true.
There was a problem hiding this comment.
I want this particular discussion to happen if and when we declare we're allowed to use atomics. For using atomics for referring to arbitrary things or managed fields or something, we probably would actually require the use of std::atomic_ref, which is yet another discussion as that is C++ 20 and above. Lets table it for now, but this reaches into parts of why the C++ standard library is difficult for us to use broadly.
There was a problem hiding this comment.
we probably would actually require the use of
std::atomic_ref, which is yet another discussion as that is C++ 20 and above
I think that'd be fine to use on Windows then while keeping old style otherwise?
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8ca91663-a7d4-41ba-86ec-f907bec4de34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8ca91663-a7d4-41ba-86ec-f907bec4de34
|
The dotnet/dotnet PR has passed, so marking that one and this one ready for review. |
|
Azure Pipelines: Successfully started running 6 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8ca91663-a7d4-41ba-86ec-f907bec4de34
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
docs/coding-guidelines/clr-code-guide.md:1223
- The guideline says there are currently no C++ standard library APIs legal to call in MODE_COOPERATIVE, but this PR introduces CoreCLR infrastructure that calls standard library APIs (e.g., std::current_exception/std::rethrow_exception) under MODE_ANY during exception translation. Either the guideline needs to call out exception-translation as an explicit exception (or list the allowed APIs), or the code should be constrained to MODE_PREEMPTIVE/GCX_PREEMP to match the rule. As written, the guideline is self-contradictory with the runtime behavior this PR adds.
Except for APIs listed in the following section, code that calls C++ standard APIs must be marked as `MODE_PREEMPTIVE` or within a `GCX_PREEMP()` scope. For an item to be listed in the following list, it must, at minimum, not perform an unbounded set of operations.
#### <a name="2.11.4.1"></a>2.11.4.1 C++ Standard Library APIs that are legal to call in `MODE_COOPERATIVE`
There are no C++ Standard Library APIs currently legal to call in `MODE_COOPERATIVE`.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
src/coreclr/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/test1.cpp:79
trialis a signed LONG64 and the code explicitly checkstrial < 0, but the message usesPRIu64(unsigned). That will print negative values incorrectly (and is undefined behavior on some platforms). Use the signed format macro and cast to the corresponding int64_t type.
if (trial < 0){
printf("Negative value %" PRIu64 " measured", trial);
}
src/coreclr/pal/tests/palsuite/threading/QueryThreadCycleTime/test1/test1.cpp:88
- These values are LONG64 (signed), but the format string was switched to
PRIu64(unsigned). This can produce incorrect output and varargs type mismatches. UsePRId64and cast the divided results to int64_t.
Fail("ERROR: The measured time (%" PRIu64 " millisecs) was not within Delta %" PRIu64 " "
"of the expected time (%" PRIu64 " millisecs).\n",
(Actual / MSEC_TO_NSEC), (Delta / MSEC_TO_NSEC), (Expected / MSEC_TO_NSEC));
| ** | ||
| **=========================================================*/ | ||
|
|
||
| #include <palsuite.h> |
| PALTEST(exception_handling_cpp_exception_mapping_test1_paltest_cpp_exception_mapping_test1, | ||
| "exception_handling/cpp_exception_mapping/test1/paltest_cpp_exception_mapping_test1") | ||
| { |
| LONG64 trial = (LONG64)SecondCount - (LONG64)FirstCount; | ||
| if (trial < 0){ | ||
| printf("Negative value %" PRId64 " measured", trial); | ||
| printf("Negative value %" PRIu64 " measured", trial); | ||
| } |
Follow-up to #101088 and #101773
Update our build pipelines to build against a serviceable C++ standard library and update our guidelines to allow such usage.
dotnet/dotnet PR at dotnet/dotnet#8608