[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

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

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention - #106266

Merged
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties
Aug 19, 2024
Merged

[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention#106266
jkotas merged 4 commits into
dotnet:mainfrom
tomeksowi:fp-structs-array-of-empties

Conversation

@tomeksowi

Copy link
Copy Markdown
Member

A note from RISC-V Hardware Floating-point Calling Convention:

One exceptional case for the flattening rule is an array of empty structs or unions;
C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines
the size of an empty struct or union as 1. i.e. for struct { struct {} e[1]; float f;
} as the first argument, C will treat it like struct { float f; } and pass f in fa0 as
described below, whereas C++ will pass the pass the entire aggregate in a0 (XLEN =
64) or a0 and a1 (XLEN = 32), as described in the integer calling convention. Zerolength
arrays of empty structs or union will be ignored for both C and C++. i.e. For
struct { struct {} e[0]; float f; };, as the first argument, C and C++ will treat it
like struct { float f; } and pass f in fa0 as described below.

@LuckyXu-HF@shushanhf LoongArch ABI doesn't seem to mention it but from what I could discern GCC behaves the same way so I left it in for LA as well.

Plus, some fixes to the EmptyStructs test code.

Stems from #101796, part of #84834, cc @dotnet/samsung

@ghostghost added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 12, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 12, 2024
@am11am11 added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 13, 2024
@risc-vv

risc-vv commented Aug 13, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9389 / 9390 (99.99%)
=======================
passed: 9389
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9498
TOTAL tests: 9498
REAL time: 48min 14s 527ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 727185 / 732507 (99.27%)
=======================
passed: 727185
failed: 403
skipped: 1774
killed: 4919
------------------------
TOTAL libs: 253
TOTAL tests: 734281
REAL time: 2h 31min 49s 599ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testfx_output.tar.gz

Build information and links

GIT: 67142cf81e2dbdd37ea1fe33999266a918521472
CI: 99840094d8ef9dc8ae10ce340b0684ff2cac384f
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@jkotasjkotas added arch-riscv Related to the RISC-V architecture arch-loongarch64 labels Aug 13, 2024
@tomeksowi

Copy link
Copy Markdown
MemberAuthor

@MichalStrehovsky@jkotas Can anyone review please?

@MichalStrehovsky

Copy link
Copy Markdown
Member

@MichalStrehovsky@jkotas Can anyone review please?

@shushanhf could you have a look please? Cc @dotnet/jit-contrib

@MichalStrehovsky
MichalStrehovsky removed their request for review August 16, 2024 16:25
@jkotas

Copy link
Copy Markdown
Member

C treats it as an empty field, but C++ treats it as a non-empty field since C++ defines the size of an empty struct or union as 1.

Are you implementing the C treatment or C++ treatment in this PR?

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

Are you implementing the C treatment or C++ treatment in this PR?

C++. That matches empty structs in .NET which are also sized 1 byte.

@jkotas

Copy link
Copy Markdown
Member

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI. Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/unmanaged interop should follow C ABI. There are number of differences between C and C++ ABIs, for example #106471.

The managed/managed calling convention does not need to follow C ABI.

So it's ok for managed/managed calling convention to follow C++ ABI wrt empty structs?

Managed/unmanaged should either follow the C ABI or we should throw PlatformNotSupportedException if it is hard to implement.

Given empty structs are undefined in C (it's a GCC extension to define them as 0 bytes) and they are defined in .NET as 1 byte like in C++, shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

I'm ok with that, but in that case how to check that managed/managed follows C++ ABI wrt empty structs? Currently I'm using the EmptyStructs test for confronting it against native compilers.

@jkotas

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

shouldn't .NET throw PlatformNotSupportedException for anything with an empty struct field in a managed/unmanaged signature? (not just on RISC-V but on any architecture)

It would be best, but it would be a breaking change. I am not sure whether we would want to make this breaking change.

how to check that managed/managed follows C++ ABI wrt empty structs?

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

@risc-vv

risc-vv commented Aug 19, 2024

Copy link
Copy Markdown
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 48min 15s 416ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing
RISC-V Release-CLR-QEMU: 9399 / 9400 (99.99%)
=======================
passed: 9399
failed: 1
skipped: 108
killed: 0
------------------------
TOTAL libs: 9508
TOTAL tests: 9508
REAL time: 1h 5min 49s 519ms
=======================

Release-CLR-QEMU.md, Release-CLR-QEMU.xml, testclr_output.tar.gz

RISC-V Release-FX-QEMU: 670605 / 688189 (97.44%)
=======================
passed: 670605
failed: 382
skipped: 1784
killed: 17202
------------------------
TOTAL libs: 253
TOTAL tests: 689973
REAL time: 2h 32min 37s 234ms
=======================

Release-FX-QEMU.md, Release-FX-QEMU.xml, testclr_output.tar.gz

Build information and links

GIT: 798cb12987f2177a86ba175795394c48e738c8ed
CI: fa8bcf642f8ae7a749cd6fa0832c1c2844d14c5a
REPO: dotnet/runtime
BRANCH: main
CONFIG: Release
LIB_CONFIG: Release

# CORE_LIBS_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s libs /p:EnableSourceLink=false
# CORE_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -s clr+libs+host /p:EnableSourceLink=false
# TESTCLR_BUILD_CMD
runtime/src/tests/build.sh -riscv64 -cross -Release -priority1 -p:UseLocalAppHostPack=true
# TESTCLR_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --core_root ./coreclr.Release/Tests/Core_Root --testhost ./testhost.Release --atest ./coreclr.Release --test ./ --log_dir ./logs --timeout 2700 --log_level DEBUG
# TESTCLR_RUN
/godata/pipelines/Release-CLR-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/godata/pipelines/Release-CLR-QEMU/testhost.Release/dotnet CORE_ROOT=/godata/pipelines/Release-CLR-QEMU/coreclr.Release/Tests/Core_Root /usr/bin/time -f "exec_time: %e" ./_TEST_BINARY_
# TESTFX_BUILD_CMD
runtime/build.sh --arch riscv64 --cross -c Release -rc Release -hc Release -lc Release -s libs.tests --testscope innerloop /p:EnableSourceLink=false /p:UseLocalAppHostPack=true
# TESTFX_CMD
python3 riscv-CI/goci/agent/TestRunner/run.py --corefx --testhost ./testhost.Release --atest ./corefx.Release --log_dir ./logs --timeout 6000 --memlimit 4096 --jobs 16 --log_level DEBUG
# TESTFX_RUN
/go-agent/pipelines/Release-FX-QEMU/logs/run_tests.log
cd"/_PATH_/_WITH_/_TEST_"&& ROOTFS_DIR=/crossrootfs/riscv64 QEMU_LD_PREFIX=/crossrootfs/riscv64 __TestDotNetCmd=/go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet /usr/bin/time -f "exec_time: %e" /go-agent/pipelines/Release-FX-QEMU/testhost.Release/dotnet exec xunit.console.dll _TEST_BINARY_ -nologo -nocolor -notrait category=failing

@tomeksowi

Copy link
Copy Markdown
MemberAuthor

The managed/managed calling convention has several differences from the managed/unmanaged calling convention. We do not have an explicit for the specific managed/managed calling convention details. We just have a tests that validate that all parts of the system agree on the details.

What are the reasons for these differences? I'm wondering if they are avoidable as long as the arguments can be represented the same way in .NET and on the native side.

One of the reasons I've been pushing in recent PRs for closer compliance with the RISC-V calling convention also for managed/managed is to avoid maintaining separate code paths for the managed/unmanaged calling convention, which we have to support anyway and is well documented.

Another reason (for supporting for custom field offsets in FP structs) is of course that custom field padding may also appear without empty structs, e.g. when the struct argument is packed, so it would be valid for interop calls. Empty structs just allow for different (oversized) padding cases that couldn't be achieved with StructLayoutAttribute.Pack, whose effects cannot exceed the default alignment for a type.

I guess it may be ok to keep the test that you have added, but it should have a comment that it is testing undefined behavior.

Added comment.

@jkotas

Copy link
Copy Markdown
Member

What are the reasons for these differences?

@tannergooding

tannergooding commented Aug 19, 2024

Copy link
Copy Markdown
Member

cc @dotnet/interop-contrib for opinions about interop for empty structs.

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf<T>(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

I would expect we are already treating it as 1-byte on Windows x86/x64/Arm64 due to the need for COM interop (which is typically C++ oriented) and that it likely required less work to make function. If we do have some differing back-compat requirement that treats it differently, however, then I'd expect we just preserve that everywhere for consistency.

@AaronRobinsonMSFT

AaronRobinsonMSFT commented Aug 19, 2024

Copy link
Copy Markdown
Member

I think it'd be best/easiest to treat empty structs as 1-byte. This ensures that there isn't any "weirdness" when doing [DisableRuntimeMarshalling] or using APIs like sizeof(T)/Unsafe.SizeOf(), matches the .NET and C++ requirements and is within the allowance of C (where it is undefined).

Agree. I know that @jkoritzinsky did some work in this area and it caused a great deal of grief trying to get it right. I'll defer to him, but if I recall correctly, we came down on empty structs were 1-byte.

@jkoritzinsky

Copy link
Copy Markdown
Member

Yes, today we treat empty structs as 1-byte. I'd recommend we continue doing so.

The only alternative I'd be comfortable with at this time would be erroring out for zero-sized structs in interop calls on platforms where there is a special ABI for zero-sized structs (like the Int128 and VectorX cases) and treating them as 1-byte in managed code.

@jkotas
jkotas merged commit 71373ae into dotnet:mainAug 19, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Sep 19, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-loongarch64arch-riscvRelated to the RISC-V architecturearea-Interop-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@tomeksowi@risc-vv@MichalStrehovsky@jkotas@tannergooding@AaronRobinsonMSFT@jkoritzinsky@am11