Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
3 changes: 2 additions & 1 deletion src/coreclr/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,8 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM)
${ARCH_SOURCES_DIR}/calldescrworkerwasm.cpp
${ARCH_SOURCES_DIR}/profiler.cpp
${ARCH_SOURCES_DIR}/helpers.cpp
${ARCH_SOURCES_DIR}/callhelpers.cpp
${ARCH_SOURCES_DIR}/callhelpers-interp-to-managed.cpp
${ARCH_SOURCES_DIR}/callhelpers-reverse.cpp
exceptionhandling.cpp
gcinfodecoder.cpp
)
Expand Down

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions src/coreclr/vm/wasm/callhelpers-reverse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//

#include <callhelpers.hpp>

// Define reverse thunks here

// Entry point for interpreted method execution from unmanaged code
class MethodDesc;

// WASM-TODO: The method lookup would ideally be fully qualified assembly and then methodDef token.
// The current approach has limitations with overloaded methods.
extern "C" void LookupMethodByName(const char* fullQualifiedTypeName, const char* methodName, MethodDesc** ppMD);
extern "C" void ExecuteInterpretedMethodFromUnmanaged(MethodDesc* pMD, int8_t* args, size_t argSize, int8_t* ret);

static MethodDesc* MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid = nullptr;
static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler()
{
// Lazy lookup of MethodDesc for the function export scenario.
if (!MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid)
{
LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid);
}
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr);
}

extern "C" void SystemJS_ExecuteBackgroundJobCallback()
{
Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler();
}

static MethodDesc* MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid = nullptr;
static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler()
{
// Lazy lookup of MethodDesc for the function export scenario.
if (!MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid)
{
LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid);
}
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr);
}

extern "C" void SystemJS_ExecuteTimerCallback()
{
Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler();
}

extern const ReverseThunkMapEntry g_ReverseThunks[] =
{
{ 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler } },
{ 100678363, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler } },
};

const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]);
28 changes: 28 additions & 0 deletions src/tasks/WasmAppBuilder/WasmAppBuilder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
<Compile Include="$(RepoRoot)src\libraries\System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs" />
</ItemGroup>

<ItemGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR'">
<Compile Remove="mono/*.cs" />
</ItemGroup>

<ItemGroup Condition="'$(RuntimeFlavor)' == 'mono'">
<Compile Remove="coreclr/*.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Common\Utils.cs" />
<Compile Include="..\Common\LogAsErrorException.cs" />
Expand Down Expand Up @@ -50,4 +58,24 @@
</ItemGroup>
</Target>

<UsingTask TaskName="ManagedToNativeGenerator" AssemblyFile="$(OutputPath)$(NetCoreAppToolCurrent)\WasmAppBuilder.dll" />

<Target Name="RunGenerator" DependsOnTargets="Build" Condition="'$(AssembliesScanPath)' != ''">
<ItemGroup>
<WasmPInvokeAssembly Include="$(AssembliesScanPath)**/*.dll" />
<WasmPInvokeModule Include="libSystem.Native" />
<WasmPInvokeModule Include="libSystem.IO.Compression.Native" />
<WasmPInvokeModule Include="libSystem.Globalization.Native" />
<WasmPInvokeModule Include="libz" />
</ItemGroup>

<Message Importance="high" Text="Running ManagedToNativeGenerator on @(WasmPInvokeAssembly)" />
<ManagedToNativeGenerator
Assemblies="@(WasmPInvokeAssembly)"
PInvokeModules="@(WasmPInvokeModule)"
PInvokeOutputPath="$(GeneratorOutputPath)todo-pinvoke-helpers.cpp"
InterpToNativeOutputPath="$(GeneratorOutputPath)callhelpers-interp-to-managed.cpp">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</ManagedToNativeGenerator>
</Target>
</Project>
135 changes: 135 additions & 0 deletions src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Diagnostics.CodeAnalysis;
using WasmAppBuilder;

using JoinedString;
//
// This class generates the g_wasmThunks array and CallFunc_* functions used by the CoreCLR interpreter to call native code on WASM.
// The generated code should be kept in sync with the corresponding CoreCLR runtime code that consumes these thunks and call functions.
//

#nullable enable

internal sealed class InterpToNativeGenerator
{
private LogAdapter Log { get; set; }

public InterpToNativeGenerator(LogAdapter log) => Log = log;

public void Generate(IEnumerable<string> cookies, string outputPath)
{
using TempFileName tmpFileName = new();
using (var w = File.CreateText(tmpFileName.Path))
{
Emit(w, cookies);
}

if (Utils.CopyIfDifferent(tmpFileName.Path, outputPath, useHash: false))
Log.LogMessage(MessageImportance.Low, $"Generating managed2native table to '{outputPath}'.");
else
Log.LogMessage(MessageImportance.Low, $"Managed2native table in {outputPath} is unchanged.");
}

private static string SignatureToArguments(string signature)
{
if (signature.Length <= 1)
return "void";

return string.Join(", ", signature.Skip(1).Select(static c => SignatureMapper.CharToNativeType(c)));
}

private static string CallFuncName(IEnumerable<char> args, string result)
{
var paramTypes = args.Any() ? args.Join("_", (p, i) => SignatureMapper.CharToNameType(p)).ToString() : "Void";

return $"CallFunc_{paramTypes}_Ret{result}";
}

private static void Emit(StreamWriter w, IEnumerable<string> cookies)
{
// Use OrderBy because Order() is not available on .NET Framework
var signatures = cookies.OrderBy(c => c).Distinct().ToArray();
Array.Sort(signatures, StringComparer.Ordinal);

w.Write(
"""
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//

//
// GENERATED FILE, DON'T EDIT
// Generated by coreclr InterpToNativeGenerator
//

#include <callhelpers.hpp>

// Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE.
#define ARG_ADDR(i) (pArgs + (i * INTERP_STACK_SLOT_SIZE))
#define ARG_IND(i) ((int32_t)((int32_t*)ARG_ADDR(i)))
#define ARG_I32(i) (*(int32_t*)ARG_ADDR(i))
#define ARG_I64(i) (*(int64_t*)ARG_ADDR(i))
#define ARG_F32(i) (*(float*)ARG_ADDR(i))
#define ARG_F64(i) (*(double*)ARG_ADDR(i))

namespace
{
""");

foreach (var signature in signatures)
{
try
{
var result = Result(signature);
var args = Args(signature);
var portabilityAssert = signature[0] == 'n' ? "PORTABILITY_ASSERT(\"Indirect struct return is not yet implemented.\");\n " : "";
w.Write(
$$"""

static void {{CallFuncName(args, SignatureMapper.CharToNameType(signature[0]))}}(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
{{result.nativeType}} (*fptr)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}) = ({{result.nativeType}} (*)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}))pcode;
{{portabilityAssert}}{{(result.isVoid ? "" : "*" + "((" + result.nativeType + "*)pRet) = ")}}(*fptr)({{args.Join(", ", (p, i) => $"{SignatureMapper.CharToArgType(p)}({i})")}});
}

""");
}
catch (InvalidSignatureCharException e)
{
throw new LogAsErrorException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
}
}

w.Write(
$$"""
}

const StringToWasmSigThunk g_wasmThunks[] = {
{{signatures.Join($",{w.NewLine}", signature =>
$" {{ \"{signature}\", (void*)&{CallFuncName(Args(signature), SignatureMapper.CharToNameType(signature[0]))} }}")}}
};

const size_t g_wasmThunksCount = sizeof(g_wasmThunks) / sizeof(g_wasmThunks[0]);

""");

static IEnumerable<char> Args(string signature)
{
for (int i = 1; i < signature.Length; ++i)
yield return signature[i];
}

static (bool isVoid, string nativeType) Result(string signature)
=> new(SignatureMapper.IsVoidSignature(signature), SignatureMapper.CharToNativeType(signature[0]));
}
}
Loading
Loading