Skip to content

Commit e1afb22

Browse files
authored
[mono][wasm] marshal-ilgen is dropped when not required (#86035)
* Replicating naricc's PInvokeScanner. * MarshalingPInvokeScanner now detects and outputs incompatible assemblies. * Allowing void return type. * PInvoke * Two-pass searching in progress. * Second pass resolves inconclusive types. * Cleanup. * Modifying the wasm toolchain to omit marshal-ilgen when possible. * Hopefully fix incorrect app dir. * Added definitions to MarshalingPInvokeScannerPath hopefully where needed. * Adding definitions of MarshalingPInvokeScannerPath to more locations. * Adding missing references to PInvoke scanner. * Changed task ordering, assemblies list. * Removed metadata load context. * Fixed code analyzer issues. * Fixed file name. * Moved MarshalingPInvokeScanner to MonoTargetsTask. * Removed BlazorApp. * Implemented more marshaling validation rules, removed warning message that got "promoted" to an error. * Catching bad image exceptions, giving reason for requiring marshal-ilgen. * Cleaned up references to standalone MarshalingPInvokeScanner project, now that the analyzer is in MonoTargtesTask. * More cleanup. * Fixed P/Invoke return value in marshal-lightweight. * Removed incompatible assemblies listing. * Restoring minimal functionality to marshal-ilgen-stub. * Addressed feedback. * Tweaked identification of blittable types. Added explanation to Compatibility enum. * Moved PInvokeCollector.cs and SignatureMapper.cs back to WasmAppBuilder. * Addressed feedback.
1 parent 9b6bab4 commit e1afb22

File tree

8 files changed

+632
-163
lines changed

8 files changed

+632
-163
lines changed

src/mono/mono/component/marshal-ilgen-stub.c

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,46 @@ marshal_ilgen_available (void)
99
return false;
1010
}
1111

12+
static void emit_throw_exception (MonoMarshalLightweightCallbacks* lightweight_cb,
13+
MonoMethodBuilder* mb, const char* exc_nspace, const char* exc_name, const char* msg)
14+
{
15+
lightweight_cb->mb_emit_exception (mb, exc_nspace, exc_name, msg);
16+
}
17+
1218
static int
13-
stub_emit_marshal_ilgen (EmitMarshalContext *m, int argnum, MonoType *t,
14-
MonoMarshalSpec *spec, int conv_arg,
15-
MonoType **conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweight_cb)
19+
stub_emit_marshal_ilgen (EmitMarshalContext* m, int argnum, MonoType* t,
20+
MonoMarshalSpec* spec, int conv_arg,
21+
MonoType** conv_arg_type, MarshalAction action, MonoMarshalLightweightCallbacks* lightweight_cb)
1622
{
23+
if (spec) {
24+
g_assert (spec->native != MONO_NATIVE_ASANY);
25+
g_assert (spec->native != MONO_NATIVE_CUSTOM);
26+
}
27+
28+
g_assert (!m_type_is_byref(t));
29+
30+
switch (t->type) {
31+
case MONO_TYPE_PTR:
32+
case MONO_TYPE_I1:
33+
case MONO_TYPE_U1:
34+
case MONO_TYPE_I2:
35+
case MONO_TYPE_U2:
36+
case MONO_TYPE_I4:
37+
case MONO_TYPE_U4:
38+
case MONO_TYPE_I:
39+
case MONO_TYPE_U:
40+
case MONO_TYPE_R4:
41+
case MONO_TYPE_R8:
42+
case MONO_TYPE_I8:
43+
case MONO_TYPE_U8:
44+
case MONO_TYPE_FNPTR:
45+
return lightweight_cb->emit_marshal_scalar (m, argnum, t, spec, conv_arg, conv_arg_type, action);
46+
default:
47+
emit_throw_exception (lightweight_cb, m->mb, "System", "ApplicationException",
48+
g_strdup("Cannot marshal nonblittlable types without marshal-ilgen."));
49+
break;
50+
}
51+
1752
return 0;
1853
}
1954

src/mono/mono/metadata/marshal-lightweight.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ emit_runtime_invoke_body_ilgen (MonoMethodBuilder *mb, const char **param_names,
523523
emit_thread_force_interrupt_checkpoint (mb);
524524
emit_invoke_call (mb, method, sig, callsig, loc_res, virtual_, need_direct_wrapper);
525525

526-
mono_mb_emit_ldloc (mb, 0);
526+
mono_mb_emit_ldloc (mb, loc_res);
527527
mono_mb_emit_byte (mb, CEE_RET);
528528
}
529529

src/mono/wasm/build/WasmApp.Native.targets

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
<UsingTask TaskName="Microsoft.WebAssembly.Build.Tasks.ManagedToNativeGenerator" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
55
<UsingTask TaskName="Microsoft.WebAssembly.Build.Tasks.EmccCompile" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
6+
<UsingTask TaskName="MonoTargetsTasks.MarshalingPInvokeScanner" AssemblyFile="$(MonoTargetsTasksAssemblyPath)" />
67

78
<PropertyGroup>
89
<_WasmBuildNativeCoreDependsOn>
10+
_ScanAssembliesDecideLightweightMarshaler;
911
_WasmAotCompileApp;
1012
_WasmStripAOTAssemblies;
1113
_PrepareForWasmBuildNative;
@@ -33,7 +35,7 @@
3335
<ItemGroup Condition="'$(Configuration)' == 'Debug' and '@(_MonoComponent->Count())' == 0">
3436
<_MonoComponent Include="hot_reload;debugger" />
3537
</ItemGroup>
36-
<ItemGroup>
38+
<ItemGroup Condition="'@(MonoLightweightMarshallerIncompatibleAssemblies->Count())' > 0">
3739
<_MonoComponent Include="marshal-ilgen" />
3840
</ItemGroup>
3941

@@ -680,6 +682,16 @@
680682
</ItemGroup>
681683
</Target>
682684

685+
<Target Name="_ScanAssembliesDecideLightweightMarshaler">
686+
<ItemGroup>
687+
<AssembliesToScan Include="@(_WasmAssembliesInternal)" />
688+
</ItemGroup>
689+
690+
<MarshalingPInvokeScanner Assemblies ="@(AssembliesToScan)">
691+
<Output TaskParameter="IncompatibleAssemblies" ItemName="MonoLightweightMarshallerIncompatibleAssemblies" />
692+
</MarshalingPInvokeScanner>
693+
</Target>
694+
683695
<!-- '$(ArchiveTests)' != 'true' is to skip on CI for now -->
684696
<Target Name="_WasmStripAOTAssemblies" Condition="'$(_WasmShouldAOT)' == 'true' and '$(WasmStripAOTAssemblies)' == 'true' and '$(AOTMode)' != 'LLVMOnlyInterp' and '$(ArchiveTests)' != 'true'">
685697
<PropertyGroup>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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.Collections.Generic;
6+
using System.Runtime.CompilerServices;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Reflection.Metadata;
9+
using System.Reflection.Metadata.Ecma335;
10+
using System.Collections.Immutable;
11+
using System.IO;
12+
using System.Linq;
13+
using System.Text;
14+
using System.Reflection;
15+
using System.Reflection.PortableExecutable;
16+
using Microsoft.Build.Framework;
17+
using Microsoft.Build.Utilities;
18+
19+
namespace MonoTargetsTasks
20+
{
21+
public class MarshalingPInvokeScanner : Task
22+
{
23+
[Required]
24+
public string[] Assemblies { get; set; } = Array.Empty<string>();
25+
26+
[Output]
27+
public string[]? IncompatibleAssemblies { get; private set; }
28+
29+
public override bool Execute()
30+
{
31+
if (Assemblies is null || Assemblies!.Length == 0)
32+
{
33+
Log.LogError($"{nameof(MarshalingPInvokeScanner)}.{nameof(Assemblies)} cannot be empty");
34+
return false;
35+
}
36+
37+
try
38+
{
39+
ExecuteInternal();
40+
return !Log.HasLoggedErrors;
41+
}
42+
catch (LogAsErrorException e)
43+
{
44+
Log.LogError(e.Message);
45+
return false;
46+
}
47+
}
48+
49+
private void ExecuteInternal()
50+
{
51+
IncompatibleAssemblies = ScanAssemblies(Assemblies);
52+
}
53+
54+
private string[] ScanAssemblies(string[] assemblies)
55+
{
56+
HashSet<string> incompatible = new HashSet<string>();
57+
MinimalMarshalingTypeCompatibilityProvider mmtcp = new(Log);
58+
foreach (string aname in assemblies)
59+
{
60+
if (IsAssemblyIncompatible(aname, mmtcp))
61+
incompatible.Add(aname);
62+
}
63+
64+
if (mmtcp.IsSecondPassNeeded)
65+
{
66+
foreach (string aname in assemblies)
67+
ResolveInconclusiveTypes(incompatible, aname, mmtcp);
68+
}
69+
70+
return incompatible.ToArray();
71+
}
72+
73+
private static string GetMethodName(MetadataReader mr, MethodDefinition md) => mr.GetString(md.Name);
74+
75+
private void ResolveInconclusiveTypes(HashSet<string> incompatible, string assyPath, MinimalMarshalingTypeCompatibilityProvider mmtcp)
76+
{
77+
string assyName = MetadataReader.GetAssemblyName(assyPath).Name!;
78+
HashSet<string> inconclusiveTypes = mmtcp.GetInconclusiveTypesForAssembly(assyName);
79+
if(inconclusiveTypes.Count == 0)
80+
return;
81+
82+
using FileStream file = new FileStream(assyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
83+
using PEReader peReader = new PEReader(file);
84+
MetadataReader mdtReader = peReader.GetMetadataReader();
85+
86+
SignatureDecoder<Compatibility, object> decoder = new(mmtcp, mdtReader, null!);
87+
88+
foreach (TypeDefinitionHandle typeDefHandle in mdtReader.TypeDefinitions)
89+
{
90+
TypeDefinition typeDef = mdtReader.GetTypeDefinition(typeDefHandle);
91+
string fullTypeName = string.Join(":", mdtReader.GetString(typeDef.Namespace), mdtReader.GetString(typeDef.Name));
92+
93+
// This is not perfect, but should work right for enums defined in other assemblies,
94+
// which is the only case where we use Compatibility.Inconclusive.
95+
if (inconclusiveTypes.Contains(fullTypeName) &&
96+
mmtcp.GetTypeFromDefinition(mdtReader, typeDefHandle, 0) != Compatibility.Compatible)
97+
{
98+
Log.LogMessage(MessageImportance.Low, string.Format("Type {0} is marshaled and requires marshal-ilgen.", fullTypeName));
99+
100+
incompatible.Add("(unknown assembly)");
101+
}
102+
}
103+
}
104+
105+
private bool IsAssemblyIncompatible(string assyPath, MinimalMarshalingTypeCompatibilityProvider mmtcp)
106+
{
107+
using FileStream file = new FileStream(assyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
108+
using PEReader peReader = new PEReader(file);
109+
MetadataReader mdtReader = peReader.GetMetadataReader();
110+
111+
foreach(CustomAttributeHandle attrHandle in mdtReader.CustomAttributes)
112+
{
113+
CustomAttribute attr = mdtReader.GetCustomAttribute(attrHandle);
114+
115+
if(attr.Constructor.Kind == HandleKind.MethodDefinition)
116+
{
117+
MethodDefinitionHandle mdh = (MethodDefinitionHandle)attr.Constructor;
118+
MethodDefinition md = mdtReader.GetMethodDefinition(mdh);
119+
TypeDefinitionHandle tdh = md.GetDeclaringType();
120+
TypeDefinition td = mdtReader.GetTypeDefinition(tdh);
121+
122+
if(mdtReader.GetString(td.Namespace) == "System.Runtime.CompilerServices" &&
123+
mdtReader.GetString(td.Name) == "DisableRuntimeMarshallingAttribute")
124+
return false;
125+
}
126+
}
127+
128+
foreach (TypeDefinitionHandle typeDefHandle in mdtReader.TypeDefinitions)
129+
{
130+
TypeDefinition typeDef = mdtReader.GetTypeDefinition(typeDefHandle);
131+
string ns = mdtReader.GetString(typeDef.Namespace);
132+
string name = mdtReader.GetString(typeDef.Name);
133+
134+
foreach(MethodDefinitionHandle mthDefHandle in typeDef.GetMethods())
135+
{
136+
MethodDefinition mthDef = mdtReader.GetMethodDefinition(mthDefHandle);
137+
if(!mthDef.Attributes.HasFlag(MethodAttributes.PinvokeImpl))
138+
continue;
139+
140+
BlobReader sgnBlobReader = mdtReader.GetBlobReader(mthDef.Signature);
141+
SignatureDecoder<Compatibility, object> decoder = new(mmtcp, mdtReader, null!);
142+
143+
MethodSignature<Compatibility> sgn = decoder.DecodeMethodSignature(ref sgnBlobReader);
144+
if(sgn.ReturnType == Compatibility.Incompatible || sgn.ParameterTypes.Any(p => p == Compatibility.Incompatible))
145+
{
146+
Log.LogMessage(MessageImportance.Low, string.Format("Assembly {0} requires marhsal-ilgen for method {1}.{2}:{3} (first pass).",
147+
assyPath, ns, name, mdtReader.GetString(mthDef.Name)));
148+
149+
return true;
150+
}
151+
}
152+
}
153+
154+
return false;
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)