Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RISC-V] Fix passing float and uint arguments in VM #105021

Merged
merged 7 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/coreclr/vm/argdestination.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ class ArgDestination
_ASSERTE(!"---------UNReachable-------LoongArch64/RISC-V64!!!");
}
}

#ifdef TARGET_RISCV64
void CopySingleFloatToRegister(void* src)
{
void* dest = GetDestinationAddress();
UINT32 value = *(UINT32*)src;
if (TransitionBlock::IsFloatArgumentRegisterOffset(m_offset))
{
// NaN-box the floating register value or single-float instructions will treat it as NaN
*(UINT64*)dest = 0xffffffff00000000L | value;
}
else
{
// When a single float is passed according to integer calling convention
// (in integer register or on stack), the upper bits are not specified.
*(UINT32*)dest = value;
}
}
#endif // TARGET_RISCV64

#endif // !DACCESS_COMPILE

PTR_VOID GetStructGenRegDestinationAddress()
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/callhelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,15 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT *
*((INT64*)pDest) = (INT16)pArguments[arg];
break;
case 4:
#ifdef TARGET_RISCV64
// RISC-V integer calling convention requires to sign-extend `uint` arguments as well
*((INT64*)pDest) = (INT32)pArguments[arg];
#else // TARGET_LOONGARCH64
if (m_argIt.GetArgType() == ELEMENT_TYPE_U4)
*((INT64*)pDest) = (UINT32)pArguments[arg];
else
*((INT64*)pDest) = (INT32)pArguments[arg];
#endif // TARGET_RISCV64
break;
#else
case 1:
Expand Down
13 changes: 5 additions & 8 deletions src/coreclr/vm/invokeutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) {

switch (type) {
#ifdef TARGET_RISCV64
tomeksowi marked this conversation as resolved.
Show resolved Hide resolved
// RISC-V call convention requires signed ints sign-extended (unsigned -- zero-extended) to register width
// RISC-V call convention requires integer scalars narrower than XLEN bits to be widened according to the sign
// of their type up to 32 bits, then sign-extended to XLEN bits. In practice it means type-extending all ints
// except `uint` which is sign-extended regardless.
case ELEMENT_TYPE_BOOLEAN:
case ELEMENT_TYPE_U1:
_ASSERTE(argRef != NULL);
Expand All @@ -164,18 +166,13 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) {

case ELEMENT_TYPE_R4:
_ASSERTE(argRef != NULL);
// NaN-box the register value or single-float instructions will treat it as NaN
*(UINT64 *)pArgDst = 0xffffffff00000000L | *(UINT32 *)argRef;
argDest->CopySingleFloatToRegister(argRef);
break;

case ELEMENT_TYPE_I4:
_ASSERTE(argRef != NULL);
*(INT64 *)pArgDst = *(INT32 *)argRef;
break;

case ELEMENT_TYPE_U4:
_ASSERTE(argRef != NULL);
*(UINT64 *)pArgDst = *(UINT32 *)argRef;
*(INT64 *)pArgDst = *(INT32 *)argRef;
break;

#else // !TARGET_RISCV64
Expand Down
12 changes: 12 additions & 0 deletions src/tests/JIT/Directed/PrimitiveABI/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
project (PrimitiveABINative)
include_directories(${INC_PLATFORM_DIR})

if(CLR_CMAKE_HOST_WIN32)
set_source_files_properties(PrimitiveABI.c PROPERTIES COMPILE_OPTIONS /TC) # compile as C
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -Oz")
endif()

add_library (PrimitiveABINative SHARED PrimitiveABI.c)

install (TARGETS PrimitiveABINative DESTINATION bin)
41 changes: 41 additions & 0 deletions src/tests/JIT/Directed/PrimitiveABI/PrimitiveABI.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include <stdint.h>
#include <stddef.h>
#include <stdio.h>

#ifdef _MSC_VER
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __attribute__((visibility("default")))
#endif // _MSC_VER

DLLEXPORT int64_t Echo_ExtendedUint_RiscV(int a0, uint32_t a1)
{
return (int32_t)a1;
}

DLLEXPORT int64_t Echo_ExtendedUint_OnStack_RiscV(
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, uint32_t stack0)
{
return (int32_t)stack0;
}

DLLEXPORT double Echo_Float_RiscV(float fa0, float fa1)
{
return fa1 + fa0;
}

DLLEXPORT double Echo_Float_InIntegerReg_RiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7,
float a0)
{
return a0 + fa7;
}

DLLEXPORT double Echo_Float_OnStack_RiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7,
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, float stack0)
{
return stack0 + fa7;
}
182 changes: 182 additions & 0 deletions src/tests/JIT/Directed/PrimitiveABI/PrimitiveABI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using Xunit;

public static class Program
{
#region ExtendedUint_RiscVTests
[DllImport("PrimitiveABINative")]
public static extern long Echo_ExtendedUint_RiscV(int a0, uint a1);

[MethodImpl(MethodImplOptions.NoInlining)]
public static long Echo_ExtendedUint_RiscV_Managed(int a0, uint a1) => unchecked((int)a1);

[Fact]
public static void Test_ExtendedUint_RiscV()
{
const uint arg = 0xB1ED0C1Eu;
const long ret = unchecked((int)arg);
long managed = Echo_ExtendedUint_RiscV_Managed(0, arg);
long native = Echo_ExtendedUint_RiscV(0, arg);

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[Fact]
public static void Test_ExtendedUint_ByReflection_RiscV()
{
const uint arg = 0xB1ED0C1Eu;
const long ret = unchecked((int)arg);
long managed = (long)typeof(Program).GetMethod("Echo_ExtendedUint_RiscV_Managed").Invoke(
null, new object[] {0, arg});
long native = (long)typeof(Program).GetMethod("Echo_ExtendedUint_RiscV").Invoke(
null, new object[] {0, arg});

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[DllImport("PrimitiveABINative")]
public static extern long Echo_ExtendedUint_OnStack_RiscV(
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, uint stack0);

[MethodImpl(MethodImplOptions.NoInlining)]
public static long Echo_ExtendedUint_OnStack_RiscV_Managed(
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, uint stack0) => unchecked((int)stack0);

[Fact]
public static void Test_ExtendedUint_OnStack_RiscV()
{
const uint arg = 0xB1ED0C1Eu;
const long ret = unchecked((int)arg);
long managed = Echo_ExtendedUint_OnStack_RiscV_Managed(0, 0, 0, 0, 0, 0, 0, 0, arg);
long native = Echo_ExtendedUint_OnStack_RiscV(0, 0, 0, 0, 0, 0, 0, 0, arg);

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[Fact]
public static void Test_ExtendedUint_OnStack_ByReflection_RiscV()
{
const uint arg = 0xB1ED0C1Eu;
const long ret = unchecked((int)arg);
long managed = (long)typeof(Program).GetMethod("Echo_ExtendedUint_OnStack_RiscV_Managed").Invoke(
null, new object[] {0, 0, 0, 0, 0, 0, 0, 0, arg});
long native = (long)typeof(Program).GetMethod("Echo_ExtendedUint_OnStack_RiscV").Invoke(
null, new object[] {0, 0, 0, 0, 0, 0, 0, 0, arg});

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}
#endregion

#region Float_RiscVTests
[DllImport("PrimitiveABINative")]
public static extern double Echo_Float_RiscV(float fa0, float fa1);

[MethodImpl(MethodImplOptions.NoInlining)]
public static double Echo_Float_RiscV_Managed(float fa0, float fa1) => fa1;

[Fact]
public static void Test_Float_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = Echo_Float_RiscV_Managed(0f, arg);
double native = Echo_Float_RiscV(0f, arg);

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[Fact]
public static void Test_Float_ByReflection_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = (double)typeof(Program).GetMethod("Echo_Float_RiscV_Managed").Invoke(
null, new object[] {0f, arg});
double native = (double)typeof(Program).GetMethod("Echo_Float_RiscV").Invoke(
null, new object[] {0f, arg});

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[DllImport("PrimitiveABINative")]
public static extern double Echo_Float_InIntegerReg_RiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, float a0);

[MethodImpl(MethodImplOptions.NoInlining)]
public static double Echo_Float_InIntegerReg_RiscV_Managed(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, float a0) => a0;

[Fact]
public static void Test_Float_InIntegerReg_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = Echo_Float_InIntegerReg_RiscV_Managed(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, arg);
double native = Echo_Float_InIntegerReg_RiscV(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, arg);

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[Fact]
public static void Test_Float_InIntegerReg_ByReflection_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = (double)typeof(Program).GetMethod("Echo_Float_InIntegerReg_RiscV_Managed").Invoke(
null, new object[] {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, arg});
double native = (double)typeof(Program).GetMethod("Echo_Float_InIntegerReg_RiscV").Invoke(
null, new object[] {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, arg});

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[DllImport("PrimitiveABINative")]
public static extern double Echo_Float_OnStack_RiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7,
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, float stack0);

[MethodImpl(MethodImplOptions.NoInlining)]
public static double Echo_Float_OnStack_RiscV_Managed(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7,
int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, float stack0) => stack0;

[Fact]
public static void Test_Float_OnStack_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = Echo_Float_OnStack_RiscV_Managed(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0, 0, 0, 0, 0, 0, 0, 0, arg);
double native = Echo_Float_OnStack_RiscV(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0, 0, 0, 0, 0, 0, 0, 0, arg);

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}

[Fact]
public static void Test_Float_OnStack_ByReflection_RiscV()
{
const float arg = 3.14159f;
const double ret = 3.14159f;
double managed = (double)typeof(Program).GetMethod("Echo_Float_OnStack_RiscV_Managed").Invoke(
null, new object[] {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0, 0, 0, 0, 0, 0, 0, 0, arg});
double native = (double)typeof(Program).GetMethod("Echo_Float_OnStack_RiscV").Invoke(
null, new object[] {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0, 0, 0, 0, 0, 0, 0, 0, arg});

Assert.Equal(ret, managed);
Assert.Equal(ret, native);
}
#endregion
}
16 changes: 16 additions & 0 deletions src/tests/JIT/Directed/PrimitiveABI/PrimitiveABI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CMakeProjectReference -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
</PropertyGroup>
<PropertyGroup>
<DebugType>PdbOnly</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="PrimitiveABI.cs" />
</ItemGroup>
<ItemGroup>
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>
</Project>
Loading