Skip to content

Commit 61dd272

Browse files
committed
Progress
1 parent beab9ac commit 61dd272

37 files changed

+1876
-23
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ csharp_using_directive_placement = outside_namespace:warning
124124
# File header
125125
file_header_template = unset
126126

127+
# Misc
128+
csharp_style_namespace_declarations = file_scoped:none
129+
127130
#----------------------------------------------------------
128131
# Unnecessary code rules
129132
#----------------------------------------------------------
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
using Biohazrd.CSharp.Trampolines;
2+
using Biohazrd.Transformation;
3+
using ClangSharp.Pathogen;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Diagnostics;
8+
9+
namespace Biohazrd.CSharp;
10+
11+
public sealed class CreateTrampolinesTransformation : CSharpTransformationBase
12+
{
13+
public TargetRuntime TargetRuntime { get; init; } = CSharpGenerationOptions.Default.TargetRuntime; //TODO: This ideally should come from some central context to ensure consistency
14+
15+
private ReferenceTypeOutputBehavior _CppReferenceOutputBehavior = ReferenceTypeOutputBehavior.AsRefOrByValue;
16+
public ReferenceTypeOutputBehavior CppReferenceOutputBehavior
17+
{
18+
get => _CppReferenceOutputBehavior;
19+
init
20+
{
21+
if (!Enum.IsDefined(value))
22+
{ throw new ArgumentOutOfRangeException(nameof(value)); }
23+
24+
_CppReferenceOutputBehavior = value;
25+
}
26+
}
27+
28+
private ReferenceTypeOutputBehavior _ImplicitReferenceOutputBehavior = ReferenceTypeOutputBehavior.AsRefOrByValue;
29+
public ReferenceTypeOutputBehavior ImplicitReferenceOutputBehavior
30+
{
31+
get => _ImplicitReferenceOutputBehavior;
32+
init
33+
{
34+
if (!Enum.IsDefined(value))
35+
{ throw new ArgumentOutOfRangeException(nameof(value)); }
36+
37+
_ImplicitReferenceOutputBehavior = value;
38+
}
39+
}
40+
41+
protected override TransformationResult TransformFunction(TransformationContext context, TranslatedFunction declaration)
42+
{
43+
// Don't try to add trampolines to a function which already has them
44+
if (declaration.Metadata.Has<TrampolineCollection>())
45+
{ return declaration; }
46+
47+
// Can't generate trampolines when the function ABI is unknown
48+
if (declaration.FunctionAbi is null)
49+
{ return declaration; }
50+
51+
// Create diagnostic accumulator for diagnostics relating to the default friendly trampoline
52+
ImmutableArray<TranslationDiagnostic>.Builder? friendlyTrampolineDiagnostics = null;
53+
void PrimaryTrampolineProblem(Severity severity, string message)
54+
{
55+
friendlyTrampolineDiagnostics ??= ImmutableArray.CreateBuilder<TranslationDiagnostic>();
56+
friendlyTrampolineDiagnostics.Add(severity, message);
57+
}
58+
59+
// Build dummy native trampoline
60+
IReturnAdapter? nativeReturnAdapter = null;
61+
ImmutableArray<Adapter>.Builder nativeAdapters;
62+
{
63+
int expectedNativeParameterCount = declaration.Parameters.Length;
64+
65+
// Add parameter slot for this pointer
66+
if (declaration.IsInstanceMethod)
67+
{ expectedNativeParameterCount++; }
68+
69+
// Add parameter slot for return buffer
70+
if (declaration.ReturnByReference)
71+
{ expectedNativeParameterCount++; }
72+
73+
nativeAdapters = ImmutableArray.CreateBuilder<Adapter>(expectedNativeParameterCount);
74+
}
75+
76+
IReturnAdapter? friendlyReturnAdapter = null;
77+
Dictionary<Adapter, Adapter>? friendlyAdapters = null;
78+
79+
void AddFriendlyAdapter(Adapter target, Adapter adapter)
80+
{
81+
friendlyAdapters ??= new Dictionary<Adapter, Adapter>(nativeAdapters.Count);
82+
friendlyAdapters.Add(target, adapter);
83+
}
84+
85+
// Handle return type when not returning by reference
86+
if (!declaration.ReturnByReference)
87+
{
88+
TypeReference returnType = declaration.ReturnType;
89+
90+
// Handle returning bool
91+
if (TargetRuntime < TargetRuntime.Net7 && returnType == CSharpBuiltinType.Bool)
92+
{
93+
//TODO: If function is virtual method rewrite to NativeBoolean instead.
94+
nativeReturnAdapter = new PassthroughReturnAdapter(CSharpBuiltinType.Byte);
95+
friendlyReturnAdapter = new ByteToBoolReturnAdapter(nativeReturnAdapter);
96+
}
97+
//TODO: Handle char for virtual methods
98+
// Handle returning void
99+
else if (returnType is VoidTypeReference)
100+
{ nativeReturnAdapter = VoidReturnAdapter.Instance; }
101+
// Handle typical return
102+
else
103+
{ nativeReturnAdapter = new PassthroughReturnAdapter(returnType); }
104+
}
105+
106+
// Handle implicit parameters
107+
{
108+
void CreateNativeReturnByReferenceAdapter()
109+
{
110+
TypeReference returnType = declaration.ReturnType;
111+
TypeReference returnBufferType = new PointerTypeReference(returnType);
112+
113+
// Create native return adapter and return buffer parameter
114+
Debug.Assert(nativeReturnAdapter is null);
115+
nativeReturnAdapter = new PassthroughReturnAdapter(returnBufferType);
116+
Adapter returnBufferParameter = new PassthroughAdapter(declaration, SpecialAdapterKind.ReturnBuffer, returnBufferType);
117+
nativeAdapters.Add(returnBufferParameter);
118+
119+
// Create friendly adapter for return buffer
120+
ReturnByReferenceAdapter returnByReferenceAdapter = new(returnBufferParameter);
121+
friendlyReturnAdapter = returnByReferenceAdapter;
122+
AddFriendlyAdapter(returnBufferParameter, returnByReferenceAdapter);
123+
}
124+
125+
// Add return buffer before this pointer
126+
// (This also handles normal non-implicit-by-reference return.)
127+
if (declaration.ReturnByReference && !declaration.FunctionAbi.ReturnInfo.Flags.HasFlag(PathogenArgumentFlags.IsSRetAfterThis))
128+
{ CreateNativeReturnByReferenceAdapter(); }
129+
130+
// Add this pointer
131+
if (declaration.IsInstanceMethod)
132+
{
133+
TypeReference thisType;
134+
if (context.ParentDeclaration is TranslatedRecord parentRecord)
135+
{ thisType = new PointerTypeReference(TranslatedTypeReference.Create(parentRecord)); }
136+
else
137+
{
138+
thisType = VoidTypeReference.PointerInstance;
139+
PrimaryTrampolineProblem(Severity.Warning, "Cannot generate primary trampoline: `this` type is unknown.");
140+
}
141+
142+
Adapter thisPointer = new PassthroughAdapter(declaration, SpecialAdapterKind.ThisPointer, thisType);
143+
nativeAdapters.Add(thisPointer);
144+
AddFriendlyAdapter(thisPointer, new ThisPointerAdapter(thisPointer));
145+
}
146+
147+
// Add return buffer after this pointer
148+
if (declaration.ReturnByReference && declaration.FunctionAbi.ReturnInfo.Flags.HasFlag(PathogenArgumentFlags.IsSRetAfterThis))
149+
{ CreateNativeReturnByReferenceAdapter(); }
150+
}
151+
152+
// We should have a native return adapter by this point
153+
Debug.Assert(nativeReturnAdapter is not null);
154+
155+
// Handle explicit parameters
156+
foreach (TranslatedParameter parameter in declaration.Parameters)
157+
{
158+
// Handle pre-.NET 7 non-blittables
159+
if (TargetRuntime < TargetRuntime.Net7)
160+
{
161+
// Handle bool
162+
if (parameter.Type == CSharpBuiltinType.Bool)
163+
{
164+
//TODO: Rewrite to NativeBoolean for virtual methods
165+
Adapter nativeAdapter = new PassthroughAdapter(parameter, CSharpBuiltinType.Byte);
166+
nativeAdapters.Add(nativeAdapter);
167+
AddFriendlyAdapter(nativeAdapter, new BoolToByteAdapter(nativeAdapter));
168+
continue;
169+
}
170+
171+
// Handle char
172+
if (declaration.IsVirtual && parameter.Type == CSharpBuiltinType.Char)
173+
{
174+
//TODO: Rewrite to NativeChar for virtual methods
175+
}
176+
}
177+
178+
// Handle implicit pass by reference
179+
if (parameter.ImplicitlyPassedByReference)
180+
{
181+
Adapter nativeAdapter = new PassthroughAdapter(parameter, new PointerTypeReference(parameter.Type));
182+
nativeAdapters.Add(nativeAdapter);
183+
184+
// Pick friendly adapter
185+
switch (ImplicitReferenceOutputBehavior)
186+
{
187+
case ReferenceTypeOutputBehavior.AlwaysByRef:
188+
AddFriendlyAdapter(nativeAdapter, new ByRefAdapter(nativeAdapter, ByRefKind.In));
189+
break;
190+
case ReferenceTypeOutputBehavior.AsRefOrByValue:
191+
AddFriendlyAdapter(nativeAdapter, new ToPointerAdapter(nativeAdapter));
192+
break;
193+
default:
194+
Debug.Assert(ImplicitReferenceOutputBehavior == ReferenceTypeOutputBehavior.AsPointer);
195+
break;
196+
}
197+
198+
continue;
199+
}
200+
201+
// Typical case
202+
{
203+
Adapter nativeAdapter = new PassthroughAdapter(parameter);
204+
nativeAdapters.Add(nativeAdapter);
205+
206+
// Handle C++-style reference
207+
if (CppReferenceOutputBehavior != ReferenceTypeOutputBehavior.AsPointer && parameter.Type is PointerTypeReference { WasReference: true } referenceType)
208+
{
209+
switch (CppReferenceOutputBehavior)
210+
{
211+
case ReferenceTypeOutputBehavior.AsRefOrByValue:
212+
if (referenceType.InnerIsConst)
213+
{ AddFriendlyAdapter(nativeAdapter, new ToPointerAdapter(nativeAdapter)); }
214+
else
215+
{ AddFriendlyAdapter(nativeAdapter, new ByRefAdapter(nativeAdapter, ByRefKind.Ref)); }
216+
break;
217+
case ReferenceTypeOutputBehavior.AlwaysByRef:
218+
AddFriendlyAdapter(nativeAdapter, new ByRefAdapter(nativeAdapter, referenceType.InnerIsConst ? ByRefKind.In : ByRefKind.Ref));
219+
break;
220+
default:
221+
Debug.Fail("Unreachable.");
222+
break;
223+
}
224+
}
225+
}
226+
}
227+
228+
// Create native trampoline
229+
bool haveFriendlyTrampoline = friendlyReturnAdapter is not null || friendlyAdapters is not null;
230+
Trampoline nativeTrampoline = new(declaration, nativeReturnAdapter, nativeAdapters.ToImmutable())
231+
{
232+
Name = haveFriendlyTrampoline ? $"{declaration.Name}_PInvoke" : declaration.Name,
233+
Accessibility = haveFriendlyTrampoline ? AccessModifier.Private : declaration.Accessibility
234+
};
235+
Trampoline primaryTrampoline;
236+
237+
if (!haveFriendlyTrampoline)
238+
{ primaryTrampoline = nativeTrampoline; }
239+
else
240+
{
241+
TrampolineBuilder friendlyBuilder = new(nativeTrampoline, useAsTemplate: false)
242+
{
243+
Name = declaration.Name,
244+
Description = "Friendly Overload",
245+
Accessibility = declaration.Accessibility
246+
};
247+
248+
if (friendlyReturnAdapter is not null)
249+
{ friendlyBuilder.AdaptReturnValue(friendlyReturnAdapter); }
250+
251+
if (friendlyAdapters is not null)
252+
{ friendlyBuilder.AdaptParametersDirect(friendlyAdapters); }
253+
254+
primaryTrampoline = friendlyBuilder.Create();
255+
}
256+
257+
// Add metadata and diagnostics to the function
258+
ImmutableArray<TranslationDiagnostic> diagnositcs = declaration.Diagnostics;
259+
if (friendlyTrampolineDiagnostics is not null)
260+
{ diagnositcs.AddRange(friendlyTrampolineDiagnostics); }
261+
262+
return declaration with
263+
{
264+
Metadata = declaration.Metadata.Add(new TrampolineCollection(nativeTrampoline, primaryTrampoline)),
265+
Diagnostics = diagnositcs
266+
};
267+
}
268+
}

Biohazrd.CSharp/BiohzardExtensions.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
using Biohazrd.Expressions;
1+
using Biohazrd.CSharp.Infrastructure;
2+
using Biohazrd.CSharp.Metadata;
3+
using Biohazrd.CSharp.Trampolines;
4+
using Biohazrd.Expressions;
25
using Biohazrd.Transformation;
36
using System;
47
using System.Collections.Generic;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Runtime.CompilerServices;
510

611
namespace Biohazrd.CSharp
712
{
@@ -68,5 +73,31 @@ internal static TDeclaration WithWarning<TDeclaration>(this TDeclaration declara
6873
NullPointerConstant => CSharpBuiltinType.NativeInt,
6974
_ => null
7075
};
76+
77+
/// <summary>Gets the effective <see cref="ReferenceTypeOutputBehavior"/> for the parameter if applicable.</summary>
78+
internal static ParameterOutputMode GetParameterOutputMode(this TranslatedParameter parameter, ReferenceTypeOutputBehavior globalOutputBehavior)
79+
{
80+
if (parameter.Type is not PointerTypeReference { WasReference: true } referenceType)
81+
{ return ParameterOutputMode.Normal; }
82+
83+
ReferenceTypeOutputBehavior outputBehavior = globalOutputBehavior;
84+
85+
if (parameter.Metadata.TryGet(out OverrideReferenceTypeOutputBehavior overrideMetadata))
86+
{ outputBehavior = overrideMetadata.ReferenceTypeOutputBehavior; }
87+
88+
return outputBehavior switch
89+
{
90+
ReferenceTypeOutputBehavior.AsPointer => ParameterOutputMode.Normal,
91+
ReferenceTypeOutputBehavior.AsRefOrByValue => referenceType.InnerIsConst ? ParameterOutputMode.RefByValue : ParameterOutputMode.RefByRef,
92+
ReferenceTypeOutputBehavior.AlwaysByRef => referenceType.InnerIsConst ? ParameterOutputMode.RefByReadonlyRef : ParameterOutputMode.RefByRef,
93+
_ => throw new InvalidOperationException($"Unknown/unsupported {nameof(ReferenceTypeOutputBehavior)} '{outputBehavior}' applied to {parameter}.")
94+
};
95+
}
96+
97+
public static Trampoline? TryGetPrimaryTrampoline(this TranslatedFunction function)
98+
=> function.Metadata.TryGet(out TrampolineCollection trampolines) ? trampolines.PrimaryTrampoline : null;
99+
100+
public static Trampoline GetPrimaryTrampoline(this TranslatedFunction function)
101+
=> function.TryGetPrimaryTrampoline() ?? throw new InvalidOperationException("Tried to get the primary trampoline of a function with no trampoline metadata.");
71102
}
72103
}

Biohazrd.CSharp/ByRefKind.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Biohazrd.CSharp;
8+
9+
public enum ByRefKind
10+
{
11+
Ref,
12+
In,
13+
Out
14+
}

Biohazrd.CSharp/CSharpGenerationOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public TargetRuntime TargetRuntime
4040
{
4141
TargetLanguageVersion.CSharp9 => TargetRuntime.Net5,
4242
TargetLanguageVersion.CSharp10 => TargetRuntime.Net6,
43+
TargetLanguageVersion.CSharp11 => TargetRuntime.Net7,
4344
_ => TargetRuntime.Net6
4445
};
4546
}
@@ -74,6 +75,7 @@ public TargetLanguageVersion TargetLanguageVersion
7475
{
7576
TargetRuntime.Net5 => TargetLanguageVersion.CSharp9,
7677
TargetRuntime.Net6 => TargetLanguageVersion.CSharp10,
78+
TargetRuntime.Net7 => TargetLanguageVersion.CSharp11,
7779
_ => TargetLanguageVersion.CSharp10
7880
};
7981
}
@@ -90,6 +92,16 @@ public TargetLanguageVersion TargetLanguageVersion
9092
}
9193
}
9294

95+
/// <summary>Controls how C++ reference types are translated to C#.</summary>
96+
/// <remarks>
97+
/// By default, references are emitted according to <see cref="ReferenceTypeOutputBehavior.AsRefOrByValue"/>.
98+
///
99+
/// This setting can be overriden for individual parameters using <see cref="Metadata.OverrideReferenceTypeOutputBehavior"/>
100+
/// </remarks>
101+
public ReferenceTypeOutputBehavior ReferenceTypeOutputBehavior { get; init; } = ReferenceTypeOutputBehavior.AsRefOrByValue;
102+
103+
public bool SuppressDefaultParameterValuesOnNonPublicMethods { get; init; } = true;
104+
93105
public CSharpGenerationOptions()
94106
#pragma warning disable CS0618 // Type or member is obsolete
95107
=> DumpOptions = ClangSharpInfoDumper.DefaultOptions;

0 commit comments

Comments
 (0)