-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathCSharpLibraryGenerator.Functions.cs
520 lines (427 loc) · 22.3 KB
/
CSharpLibraryGenerator.Functions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
using Biohazrd.CSharp.Metadata;
using ClangSharp.Pathogen;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Biohazrd.CSharp.CSharpCodeWriter;
namespace Biohazrd.CSharp
{
partial class CSharpLibraryGenerator
{
private ref struct EmitFunctionContext
{
public bool NeedsTrampoline { get; }
public string DllImportName { get; }
public TypeReference? ThisType { get; }
public string ThisParameterName => "this";
public string ReturnBufferParameterName => "__returnBuffer";
public EmitFunctionContext(VisitorContext context, TranslatedFunction declaration)
{
// We emit a trampoline for functions which are instance methods or return via reference to hide those ABI semantics
NeedsTrampoline = declaration.IsInstanceMethod || declaration.ReturnByReference;
// When this function is uses a trampoline, we add a suffix to the P/Invoke method to ensure they don't conflict with other methods.
// (For instance, when there's a SomeClass::Method() method in addition to a SomeClass::Method(SomeClass*) method.)
DllImportName = NeedsTrampoline ? $"{declaration.Name}_PInvoke" : declaration.Name;
// ThisType is the type of `this` for instance methods.
if (!declaration.IsInstanceMethod)
{ ThisType = null; }
else
{
if (context.ParentDeclaration is TranslatedRecord parentRecord)
{ ThisType = new PointerTypeReference(new PreResolvedTypeReference(context.MakePrevious(), parentRecord)); }
else
{ ThisType = VoidTypeReference.PointerInstance; }
}
}
}
protected override void VisitFunction(VisitorContext context, TranslatedFunction declaration)
{
if (!declaration.IsCallable)
{
Fatal(context, declaration, $"{declaration.Name} is missing ABI information and cannot be called.");
return;
}
EmitFunctionContext emitContext = new(context, declaration);
// Emit the DllImport
if (!declaration.IsVirtual)
{ EmitFunctionDllImport(context, emitContext, declaration); }
// Emit the trampoline
if (emitContext.NeedsTrampoline)
{ EmitFunctionTrampoline(context, emitContext, declaration); }
}
private static bool FunctionNeedsCharSetParameter(TranslatedLibrary library, TranslatedFunction declaration)
{
if (declaration.ReturnType.IsCSharpType(library, CSharpBuiltinType.Char))
{ return true; }
foreach (TranslatedParameter parameter in declaration.Parameters)
{
if (parameter.Type.IsCSharpType(library, CSharpBuiltinType.Char))
{ return true; }
}
return false;
}
private void EmitFunctionDllImport(VisitorContext context, EmitFunctionContext emitContext, TranslatedFunction declaration)
{
Writer.EnsureSeparation();
// Hide from Intellisense if applicable
// (Don't do this if the function is accessed via trampoline.)
if (!declaration.IsInstanceMethod)
{ EmitEditorBrowsableAttribute(declaration); }
// Write out the DllImport attribute
Writer.Using("System.Runtime.InteropServices");
Writer.Write($"[DllImport(\"{SanitizeStringLiteral(declaration.DllFileName)}\", CallingConvention = CallingConvention.{declaration.CallingConvention}");
if (declaration.MangledName != emitContext.DllImportName)
{ Writer.Write($", EntryPoint = \"{SanitizeStringLiteral(declaration.MangledName)}\""); }
if (FunctionNeedsCharSetParameter(context.Library, declaration))
{ Writer.Write(", CharSet = CharSet.Unicode"); }
if (declaration.Metadata.Has<SetLastErrorFunction>())
{ Writer.Write(", SetLastError = true"); }
Writer.WriteLine(", ExactSpelling = true)]");
// Write out MarshalAs for boolean returns
if (declaration.ReturnType.IsCSharpType(context.Library, CSharpBuiltinType.Bool))
{ Writer.WriteLine("[return: MarshalAs(UnmanagedType.I1)]"); }
// Write out the function signature
// The P/Invokes for functions accessed via trampoline are emitted as private.
AccessModifier accessibility = emitContext.NeedsTrampoline ? AccessModifier.Private : declaration.Accessibility;
Writer.Write($"{accessibility.ToCSharpKeyword()} static extern ");
// If the return value is passed by reference, the return type is the return buffer pointer
if (declaration.ReturnByReference)
{ WriteTypeAsReference(context, declaration, declaration.ReturnType); }
else
{ WriteType(context, declaration, declaration.ReturnType); }
Writer.Write($" {SanitizeIdentifier(emitContext.DllImportName)}(");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.DllImportParameters);
Writer.WriteLine(");");
}
private void EmitFunctionTrampoline(VisitorContext context, EmitFunctionContext emitContext, TranslatedFunction declaration)
{
if (!emitContext.NeedsTrampoline)
{ throw new ArgumentException("A function trampoline is not valid in this context.", nameof(emitContext)); }
Writer.EnsureSeparation();
// Build the dispatch's method access
// (We do this first so we can change our emit if the method is broken.)
string? methodAccess = null;
string? methodAccessFailure = null;
if (!declaration.IsVirtual)
{ methodAccess = SanitizeIdentifier(emitContext.DllImportName); }
else
{
// Figure out how to access the VTable entry
if (context.ParentDeclaration is not TranslatedRecord record)
{ methodAccessFailure = $"Virtual method has no associated class."; }
else if (record.VTableField is null)
{ methodAccessFailure = "Class has no vTable pointer."; }
else if (record.VTable is null)
{ methodAccessFailure = "Class has no virtual method table."; }
else if (declaration.Declaration is null)
{ methodAccessFailure = "Virtual method has no associated Clang declaration."; }
else
{
TranslatedVTableEntry? vTableEntry = null;
foreach (TranslatedVTableEntry entry in record.VTable.Entries)
{
if (entry.Info.MethodDeclaration == declaration.Declaration.Handle)
{
vTableEntry = entry;
break;
}
}
if (vTableEntry is null)
{ methodAccessFailure = "Could not find entry in virtual method table."; }
else
{ methodAccess = $"{SanitizeIdentifier(record.VTableField.Name)}->{SanitizeIdentifier(vTableEntry.Name)}"; }
}
}
Debug.Assert(methodAccess is not null || methodAccessFailure is not null, "We need either a method access or a method failure.");
// Hide from Intellisense if applicable
EmitEditorBrowsableAttribute(declaration);
// Hide from the debugger if applicable
if (Options.HideTrampolinesFromDebugger)
{
Writer.Using("System.Diagnostics");
Writer.WriteLine("[DebuggerStepThrough, DebuggerHidden]");
}
// Add method implementation options if applicable
EmitMethodImplAttribute(declaration);
// Obsolete the method if we won't be able to build the trampoline
if (methodAccessFailure is not null)
{
Writer.Using("System");
Writer.WriteLine($"[Obsolete(\"Method not translated: {SanitizeStringLiteral(methodAccessFailure)}\", error: true)]");
Diagnostics.Add(Severity.Error, $"Method trampoline cannot be emitted: {methodAccessFailure}");
}
// If this is a constructor, determine if we'll emit it as an actual constructor
string? constructorName = null;
if (declaration.SpecialFunctionKind == SpecialFunctionKind.Constructor && context.ParentDeclaration is TranslatedRecord constructorType)
{
constructorName = constructorType.Name;
// Parameterless constructors require C# 10
if (declaration.Parameters.Length == 0 && Options.TargetLanguageVersion < TargetLanguageVersion.CSharp10)
{ constructorName = null; }
}
// Emit the method signature
if (constructorName is null)
{
Writer.Write($"{declaration.Accessibility.ToCSharpKeyword()} ");
if (!declaration.IsInstanceMethod)
{ Writer.Write("static "); }
WriteTypeForTrampoline(context, declaration, declaration.ReturnType);
Writer.Write($" {SanitizeIdentifier(declaration.Name)}(");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.TrampolineParameters);
Writer.WriteLine(')');
}
// Emit the constructor signature
else
{
Writer.Write($"{declaration.Accessibility.ToCSharpKeyword()} ");
Writer.Write($"{SanitizeIdentifier(constructorName)}(");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.TrampolineParameters);
Writer.WriteLine(')');
}
// If we failed to build the method access, just emit an exception
if (methodAccessFailure is not null)
{
using (Writer.Indent())
{
Writer.Using("System");
Writer.WriteLine($"=> throw new PlatformNotSupportedException(\"Method not available: {SanitizeStringLiteral(methodAccessFailure)}\");");
}
return;
}
// Emit the dispatch
using (Writer.Block())
{
bool hasThis;
if (emitContext.ThisType is not null)
{
hasThis = true;
Debug.Assert(declaration.IsInstanceMethod);
Writer.Write($"fixed (");
WriteType(context, declaration, emitContext.ThisType!);
Writer.WriteLine($" {SanitizeIdentifier(emitContext.ThisParameterName)} = &this)");
}
else
{
hasThis = false;
Debug.Assert(!declaration.IsInstanceMethod);
}
bool hasReturnValue = declaration.ReturnType is not VoidTypeReference;
if (hasReturnValue && declaration.ReturnByReference)
{
void EmitFunctionBodyWithReturnByReference(EmitFunctionContext emitContext)
{
WriteType(context, declaration, declaration.ReturnType);
Writer.WriteLine($" {SanitizeIdentifier(emitContext.ReturnBufferParameterName)};");
Writer.Write($"{methodAccess}(");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.TrampolineArguments);
Writer.WriteLine(");");
Writer.WriteLine($"return {SanitizeIdentifier(emitContext.ReturnBufferParameterName)};");
}
if (hasThis)
{
// If we have a fixed statement for the this pointer, wrap the return buffer logic with a block
using (Writer.Block())
{ EmitFunctionBodyWithReturnByReference(emitContext); }
}
else
{ EmitFunctionBodyWithReturnByReference(emitContext); }
}
else
{
// If we have a fixed statement for the this pointer, write out the curly braces for it
if (hasThis)
{ Writer.Write("{ "); }
if (hasReturnValue)
{ Writer.Write("return "); }
Writer.Write($"{methodAccess}(");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.TrampolineArguments);
Writer.Write(");");
if (hasThis)
{ Writer.WriteLine(" }"); }
}
}
}
private void EmitFunctionPointerForVTable(VisitorContext context, EmitFunctionContext emitContext, TranslatedFunction declaration)
{
string? callingConventionString = declaration.CallingConvention switch
{
CallingConvention.Cdecl => "unmanaged[Cdecl]",
CallingConvention.StdCall => "unmanaged[Stdcall]",
CallingConvention.ThisCall => "unmanaged[Thiscall]",
CallingConvention.FastCall => "unmanaged[Fastcall]",
_ => null
};
if (callingConventionString is null)
{
Fatal(context, declaration, $"The {declaration.CallingConvention} convention is not supported.");
Writer.Write("void*");
return;
}
Writer.Write($"delegate* {callingConventionString}<");
EmitFunctionParameterList(context, emitContext, declaration, EmitParameterListMode.VTableFunctionPointerParameters);
Writer.Write(", ");
if (declaration.ReturnByReference)
{ WriteTypeAsReference(context, declaration, declaration.ReturnType); }
else
{ WriteType(context, declaration, declaration.ReturnType); }
Writer.Write('>');
}
private enum EmitParameterListMode
{
DllImportParameters,
TrampolineParameters,
TrampolineArguments,
VTableFunctionPointerParameters,
}
private void EmitFunctionParameterList(VisitorContext context, EmitFunctionContext emitContext, TranslatedFunction declaration, EmitParameterListMode mode)
{
if (declaration.FunctionAbi is null)
{ throw new ArgumentException("Cannot emit a parameter list for an uncallable function since they lack ABI information.", nameof(declaration)); }
bool first = true;
bool writeImplicitParameters = mode is EmitParameterListMode.DllImportParameters or EmitParameterListMode.TrampolineArguments or EmitParameterListMode.VTableFunctionPointerParameters;
bool writeTypes = mode is EmitParameterListMode.DllImportParameters or EmitParameterListMode.TrampolineParameters or EmitParameterListMode.VTableFunctionPointerParameters;
bool writeNames = mode is EmitParameterListMode.DllImportParameters or EmitParameterListMode.TrampolineArguments or EmitParameterListMode.TrampolineParameters;
bool writeDefautValues = mode switch
{
EmitParameterListMode.DllImportParameters => !declaration.IsInstanceMethod, // We only emit the defaults on the trampoline.
EmitParameterListMode.TrampolineParameters => true,
_ => false
};
VisitorContext parameterContext = context.Add(declaration);
// Write out the this/retbuf parameters
if (writeImplicitParameters)
{
void WriteOutReturnBuffer(EmitFunctionContext emitContext)
{
if (!first)
{ Writer.Write(", "); }
if (!declaration.IsVirtual)
{ Writer.Write("out "); }
else if (mode == EmitParameterListMode.TrampolineArguments)
{ Writer.Write("&"); }
if (writeTypes)
{
WriteType(context, declaration, declaration.ReturnType);
//TODO: When fixing https://github.com/InfectedLibraries/Biohazrd/issues/196, use WriteTypeAsReference instead.
if (mode == EmitParameterListMode.VTableFunctionPointerParameters)
{ Writer.Write('*'); }
if (writeNames)
{ Writer.Write(' '); }
}
if (writeNames)
{ Writer.WriteIdentifier(emitContext.ReturnBufferParameterName); }
first = false;
}
// Write out before-this return buffer
if (declaration.ReturnByReference && !declaration.FunctionAbi.ReturnInfo.Flags.HasFlag(PathogenArgumentFlags.IsSRetAfterThis))
{ WriteOutReturnBuffer(emitContext); }
// Write out the this pointer
if (emitContext.ThisType is not null)
{
if (!first)
{ Writer.Write(", "); }
if (writeTypes)
{
WriteType(context, declaration, emitContext.ThisType);
if (writeNames)
{ Writer.Write(' '); }
}
if (writeNames)
{ Writer.WriteIdentifier(emitContext.ThisParameterName); }
first = false;
}
// Write out after-this return buffer
if (declaration.ReturnByReference && declaration.FunctionAbi.ReturnInfo.Flags.HasFlag(PathogenArgumentFlags.IsSRetAfterThis))
{ WriteOutReturnBuffer(emitContext); }
}
// Write out parameters
foreach (TranslatedParameter parameter in declaration.Parameters)
{
if (first)
{ first = false; }
else
{ Writer.Write(", "); }
if (writeTypes)
{
if (parameter.ImplicitlyPassedByReference)
{ WriteTypeAsReference(parameterContext, parameter, parameter.Type); }
else
{
// Write MarshalAs for booleans at pinvoke boundaries
if (mode == EmitParameterListMode.DllImportParameters && parameter.Type.IsCSharpType(context.Library, CSharpBuiltinType.Bool))
{ Writer.Write("[MarshalAs(UnmanagedType.I1)] "); }
if (mode == EmitParameterListMode.TrampolineParameters)
{ WriteTypeForTrampoline(parameterContext, parameter, parameter.Type); }
else
{ WriteType(parameterContext, parameter, parameter.Type); }
}
if (writeNames)
{ Writer.Write(' '); }
}
if (writeNames)
{ Writer.WriteIdentifier(parameter.Name); }
if (writeDefautValues && parameter.DefaultValue is not null)
{ Writer.Write($" = {GetConstantAsString(parameterContext, parameter, parameter.DefaultValue, parameter.Type)}"); }
}
}
private void EmitEditorBrowsableAttribute(TranslatedFunction declaration)
{
if (declaration.Metadata.Has<HideDeclarationFromIntellisense>())
{
Writer.Using("System.ComponentModel");
Writer.WriteLine("[EditorBrowsable(EditorBrowsableState.Never)]");
}
}
private void EmitMethodImplAttribute(TranslatedFunction declaration)
{
if (!declaration.Metadata.TryGet<TrampolineMethodImplOptions>(out TrampolineMethodImplOptions optionsMetadata))
{ return; }
MethodImplOptions options = optionsMetadata.Options;
if (options == 0)
{ return; }
Writer.Using("System.Runtime.CompilerServices");
Writer.Write("[MethodImpl(");
bool first = true;
foreach (MethodImplOptions option in Enum.GetValues<MethodImplOptions>())
{
if ((options & option) == option)
{
if (first)
{ first = false; }
else
{ Writer.Write(" | "); }
Writer.Write($"{nameof(MethodImplOptions)}.{option}");
options &= ~option;
}
}
if (first || options != 0)
{
if (!first)
{ Writer.Write(" | "); }
Writer.Write($"({nameof(MethodImplOptions)}){(int)options}");
}
Writer.WriteLine(")]");
}
private void WriteTypeForTrampoline(VisitorContext context, TranslatedDeclaration declaration, TypeReference type)
{
// For trampolines we want to hide the semantics of NativeBoolean/NativeChar so we silently replace them with their actual C# type
// See https://github.com/InfectedLibraries/Biohazrd/issues/200 for details.
if (type is TranslatedTypeReference typeReference)
{
switch (typeReference.TryResolve(context.Library))
{
case NativeBooleanDeclaration:
WriteType(context, declaration, CSharpBuiltinType.Bool);
return;
case NativeCharDeclaration:
WriteType(context, declaration, CSharpBuiltinType.Char);
return;
}
}
WriteType(context, declaration, type);
}
protected override void VisitParameter(VisitorContext context, TranslatedParameter declaration)
=> FatalContext(context, declaration);
}
}