Skip to content

Commit e01de4e

Browse files
radekdoulikCopilotAaronRobinsonMSFTjkotaslewing
authored
[wasm][coreclr] First part of the call generator, interp to native (#121491)
Port of the generator task to the CoreCLR. It can be now used to update wasm callhelpers manually. Example usage: ./dotnet.sh build /t:RunGenerator /p:GeneratorOutputPath="./" /p:AssembliesScanPath="<your-path-to-assemblies>" src/tasks/WasmAppBuilder/WasmAppBuilder.csproj The call helper files are split to 2 files, similar as on Mono. The pinvoke/reverse parts of the generator will be updated in the followup PR. Contributes to #113692 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Aaron R Robinson <arobins@microsoft.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com> Co-authored-by: Larry Ewing <lewing@microsoft.com>
1 parent 404a358 commit e01de4e

File tree

10 files changed

+682
-278
lines changed

10 files changed

+682
-278
lines changed

src/coreclr/vm/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,8 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM)
920920
${ARCH_SOURCES_DIR}/calldescrworkerwasm.cpp
921921
${ARCH_SOURCES_DIR}/profiler.cpp
922922
${ARCH_SOURCES_DIR}/helpers.cpp
923-
${ARCH_SOURCES_DIR}/callhelpers.cpp
923+
${ARCH_SOURCES_DIR}/callhelpers-interp-to-managed.cpp
924+
${ARCH_SOURCES_DIR}/callhelpers-reverse.cpp
924925
exceptionhandling.cpp
925926
gcinfodecoder.cpp
926927
)

src/coreclr/vm/wasm/callhelpers.cpp renamed to src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp

Lines changed: 142 additions & 277 deletions
Large diffs are not rendered by default.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
//
4+
5+
#include <callhelpers.hpp>
6+
7+
// Define reverse thunks here
8+
9+
// Entry point for interpreted method execution from unmanaged code
10+
class MethodDesc;
11+
12+
// WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token.
13+
// The current approach has limitations with overloaded methods.
14+
extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD);
15+
extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret);
16+
17+
static MethodDesc* MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid = nullptr;
18+
static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler()
19+
{
20+
// Lazy lookup of MethodDesc for the function export scenario.
21+
if (!MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid)
22+
{
23+
LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid);
24+
}
25+
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr);
26+
}
27+
28+
extern "C" void SystemJS_ExecuteBackgroundJobCallback()
29+
{
30+
Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler();
31+
}
32+
33+
static MethodDesc* MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid = nullptr;
34+
static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler()
35+
{
36+
// Lazy lookup of MethodDesc for the function export scenario.
37+
if (!MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid)
38+
{
39+
LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid);
40+
}
41+
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr);
42+
}
43+
44+
extern "C" void SystemJS_ExecuteTimerCallback()
45+
{
46+
Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler();
47+
}
48+
49+
extern const ReverseThunkMapEntry g_ReverseThunks[] =
50+
{
51+
{ 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler } },
52+
{ 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler } },
53+
};
54+
55+
const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]);

src/tasks/WasmAppBuilder/WasmAppBuilder.csproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
<Compile Include="$(RepoRoot)src\libraries\System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs" />
1717
</ItemGroup>
1818

19+
<ItemGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR'">
20+
<Compile Remove="mono/*.cs" />
21+
</ItemGroup>
22+
23+
<ItemGroup Condition="'$(RuntimeFlavor)' == 'mono'">
24+
<Compile Remove="coreclr/*.cs" />
25+
</ItemGroup>
26+
1927
<ItemGroup>
2028
<Compile Include="..\Common\Utils.cs" />
2129
<Compile Include="..\Common\LogAsErrorException.cs" />
@@ -50,4 +58,24 @@
5058
</ItemGroup>
5159
</Target>
5260

61+
<UsingTask TaskName="ManagedToNativeGenerator" Runtime="NET" TaskFactory="TaskHostFactory" AssemblyFile="$(OutputPath)$(NetCoreAppToolCurrent)\WasmAppBuilder.dll" />
62+
63+
<Target Name="RunGenerator" DependsOnTargets="Build" Condition="'$(AssembliesScanPath)' != ''">
64+
<ItemGroup>
65+
<WasmPInvokeAssembly Include="$(AssembliesScanPath)**/*.dll" />
66+
<WasmPInvokeModule Include="libSystem.Native" />
67+
<WasmPInvokeModule Include="libSystem.IO.Compression.Native" />
68+
<WasmPInvokeModule Include="libSystem.Globalization.Native" />
69+
<WasmPInvokeModule Include="libz" />
70+
</ItemGroup>
71+
72+
<Message Importance="high" Text="Running ManagedToNativeGenerator on @(WasmPInvokeAssembly)" />
73+
<ManagedToNativeGenerator
74+
Assemblies="@(WasmPInvokeAssembly)"
75+
PInvokeModules="@(WasmPInvokeModule)"
76+
PInvokeOutputPath="$(GeneratorOutputPath)todo-pinvoke-helpers.cpp"
77+
InterpToNativeOutputPath="$(GeneratorOutputPath)callhelpers-interp-to-managed.cpp">
78+
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
79+
</ManagedToNativeGenerator>
80+
</Target>
5381
</Project>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Collections.Generic;
9+
using System.Globalization;
10+
using Microsoft.Build.Utilities;
11+
using Microsoft.Build.Framework;
12+
using System.Diagnostics.CodeAnalysis;
13+
using WasmAppBuilder;
14+
15+
using JoinedString;
16+
//
17+
// This class generates the g_wasmThunks array and CallFunc_* functions used by the CoreCLR interpreter to call native code on WASM.
18+
// The generated code should be kept in sync with the corresponding CoreCLR runtime code that consumes these thunks and call functions.
19+
//
20+
21+
#nullable enable
22+
23+
internal sealed class InterpToNativeGenerator
24+
{
25+
private LogAdapter Log { get; set; }
26+
27+
public InterpToNativeGenerator(LogAdapter log) => Log = log;
28+
29+
public void Generate(IEnumerable<string> cookies, string outputPath)
30+
{
31+
using TempFileName tmpFileName = new();
32+
using (var w = File.CreateText(tmpFileName.Path))
33+
{
34+
Emit(w, cookies);
35+
}
36+
37+
if (Utils.CopyIfDifferent(tmpFileName.Path, outputPath, useHash: false))
38+
Log.LogMessage(MessageImportance.Low, $"Generating managed2native table to '{outputPath}'.");
39+
else
40+
Log.LogMessage(MessageImportance.Low, $"Managed2native table in {outputPath} is unchanged.");
41+
}
42+
43+
private static string SignatureToArguments(string signature)
44+
{
45+
if (signature.Length <= 1)
46+
return "void";
47+
48+
return string.Join(", ", signature.Skip(1).Select(static c => SignatureMapper.CharToNativeType(c)));
49+
}
50+
51+
private static string CallFuncName(IEnumerable<char> args, string result)
52+
{
53+
var paramTypes = args.Any() ? args.Join("_", (p, i) => SignatureMapper.CharToNameType(p)).ToString() : "Void";
54+
55+
return $"CallFunc_{paramTypes}_Ret{result}";
56+
}
57+
58+
private static void Emit(StreamWriter w, IEnumerable<string> cookies)
59+
{
60+
// Use OrderBy because Order() is not available on .NET Framework
61+
var signatures = cookies.OrderBy(c => c).Distinct().ToArray();
62+
Array.Sort(signatures, StringComparer.Ordinal);
63+
64+
w.Write(
65+
"""
66+
// Licensed to the .NET Foundation under one or more agreements.
67+
// The .NET Foundation licenses this file to you under the MIT license.
68+
//
69+
70+
//
71+
// GENERATED FILE, DON'T EDIT
72+
// Generated by coreclr InterpToNativeGenerator
73+
//
74+
75+
#include <callhelpers.hpp>
76+
77+
// Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE.
78+
#define ARG_ADDR(i) (pArgs + (i * INTERP_STACK_SLOT_SIZE))
79+
#define ARG_IND(i) ((int32_t)((int32_t*)ARG_ADDR(i)))
80+
#define ARG_I32(i) (*(int32_t*)ARG_ADDR(i))
81+
#define ARG_I64(i) (*(int64_t*)ARG_ADDR(i))
82+
#define ARG_F32(i) (*(float*)ARG_ADDR(i))
83+
#define ARG_F64(i) (*(double*)ARG_ADDR(i))
84+
85+
namespace
86+
{
87+
""");
88+
89+
foreach (var signature in signatures)
90+
{
91+
try
92+
{
93+
var result = Result(signature);
94+
var args = Args(signature);
95+
var portabilityAssert = signature[0] == 'n' ? "PORTABILITY_ASSERT(\"Indirect struct return is not yet implemented.\");\n " : "";
96+
w.Write(
97+
$$"""
98+
99+
static void {{CallFuncName(args, SignatureMapper.CharToNameType(signature[0]))}}(PCODE pcode, int8_t* pArgs, int8_t* pRet)
100+
{
101+
{{result.nativeType}} (*fptr)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}) = ({{result.nativeType}} (*)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}))pcode;
102+
{{portabilityAssert}}{{(result.isVoid ? "" : "*" + "((" + result.nativeType + "*)pRet) = ")}}(*fptr)({{args.Join(", ", (p, i) => $"{SignatureMapper.CharToArgType(p)}({i})")}});
103+
}
104+
105+
""");
106+
}
107+
catch (InvalidSignatureCharException e)
108+
{
109+
throw new LogAsErrorException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
110+
}
111+
}
112+
113+
w.Write(
114+
$$"""
115+
}
116+
117+
const StringToWasmSigThunk g_wasmThunks[] = {
118+
{{signatures.Join($",{w.NewLine}", signature =>
119+
$" {{ \"{signature}\", (void*)&{CallFuncName(Args(signature), SignatureMapper.CharToNameType(signature[0]))} }}")}}
120+
};
121+
122+
const size_t g_wasmThunksCount = sizeof(g_wasmThunks) / sizeof(g_wasmThunks[0]);
123+
124+
""");
125+
126+
static IEnumerable<char> Args(string signature)
127+
{
128+
for (int i = 1; i < signature.Length; ++i)
129+
yield return signature[i];
130+
}
131+
132+
static (bool isVoid, string nativeType) Result(string signature)
133+
=> new(SignatureMapper.IsVoidSignature(signature), SignatureMapper.CharToNativeType(signature[0]));
134+
}
135+
}

0 commit comments

Comments
 (0)