forked from MochiLibraries/Biohazrd
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCSharpLibraryGenerator.Fields.cs
268 lines (227 loc) · 13.5 KB
/
CSharpLibraryGenerator.Fields.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
using System.Diagnostics;
using System.Linq;
using static Biohazrd.CSharp.CSharpCodeWriter;
namespace Biohazrd.CSharp
{
partial class CSharpLibraryGenerator
{
private void StartField(TranslatedField field)
{
Writer.Using("System.Runtime.InteropServices");
Writer.EnsureSeparation();
Writer.Write($"[FieldOffset({field.Offset})] ");
// Apply MarshalAs to boolean fields
// This might not strictly be necessary since our struct has an explicit layout, but we do it anyway for the sake of sanity.
// (The marshaler definitely still runs on bools in explicit layouts, but it's not immediately clear if it is trying to interpret the memory as a 4-byte or 1-byte bool.)
if (Options.TargetRuntime < TargetRuntime.Net7 && field is TranslatedNormalField { Type: CSharpBuiltinTypeReference cSharpType } && cSharpType.Type == CSharpBuiltinType.Bool)
{ Writer.Write($"[MarshalAs(UnmanagedType.I1)] "); }
Writer.Write($"{field.Accessibility.ToCSharpKeyword()} ");
}
protected override void VisitField(VisitorContext context, TranslatedField declaration)
=> Fatal(context, declaration, "The field kind is unsupported.", $"@ {declaration.Offset}");
protected override void VisitUnimplementedField(VisitorContext context, TranslatedUnimplementedField declaration)
=> VisitField(context, declaration);
protected override void VisitVTableField(VisitorContext context, TranslatedVTableField declaration)
// VTable concerns are handled in the record emit
=> FatalContext(context, declaration);
protected override void VisitBaseField(VisitorContext context, TranslatedBaseField declaration)
{
StartField(declaration);
WriteType(context, declaration, declaration.Type);
Writer.Write(' ');
Writer.WriteIdentifier(declaration.Name);
Writer.WriteLine(';');
}
protected override void VisitNormalField(VisitorContext context, TranslatedNormalField declaration)
{
StartField(declaration);
WriteType(context, declaration, declaration.Type);
Writer.Write(' ');
Writer.WriteIdentifier(declaration.Name);
Writer.WriteLine(';');
}
protected override void VisitStaticField(VisitorContext context, TranslatedStaticField declaration)
{
Writer.Using("System.Runtime.InteropServices"); // For NativeLibrary
Writer.EnsureSeparation();
Writer.Write($"{declaration.Accessibility.ToCSharpKeyword()} static readonly ");
WriteTypeAsReference(context, declaration, declaration.Type);
Writer.Write(' ');
Writer.WriteIdentifier(declaration.Name);
Writer.Write(" = (");
WriteTypeAsReference(context, declaration, declaration.Type);
//TODO: This leaks handles to the native library.
Writer.WriteLine($")NativeLibrary.GetExport(NativeLibrary.Load(\"{SanitizeStringLiteral(declaration.DllFileName)}\"), \"{SanitizeStringLiteral(declaration.MangledName)}\");");
}
protected override void VisitConstant(VisitorContext context, TranslatedConstant declaration)
{
TypeReference? type = declaration.Type ?? declaration.Value.InferType();
if (type is null)
{
Fatal(context, declaration, "Constant type was not specified and cannot be inferred.");
return;
}
Writer.EnsureSeparation();
Writer.Write($"{declaration.Accessibility.ToCSharpKeyword()} const ");
WriteType(context, declaration, type);
Writer.Write($" {SanitizeIdentifier(declaration.Name)} = ");
Writer.Write(GetConstantAsString(context, declaration, declaration.Value, type));
Writer.WriteLine(';');
}
protected override void VisitBitField(VisitorContext context, TranslatedBitField declaration)
{
// Determine the type for the backing field
static CSharpBuiltinType? GetBackingType(TranslatedLibrary library, TypeReference type, out TypeReference effectiveType, ref bool fieldIsEnum, ref bool fieldIsEnumWithSignedValues)
{
effectiveType = type;
switch (type)
{
case CSharpBuiltinTypeReference cSharpType:
return cSharpType.Type;
case TranslatedTypeReference typeReference:
switch (typeReference.TryResolve(library))
{
case TranslatedEnum { UnderlyingType: CSharpBuiltinTypeReference cSharpType } translatedEnum:
CSharpBuiltinType result = cSharpType.Type;
fieldIsEnum = true;
// Check if any of the enum's values are negative
if (cSharpType.Type.IsSigned)
{ fieldIsEnumWithSignedValues = translatedEnum.Values.Any(v => v.Value > cSharpType.Type.MaxValue); }
return result;
case TranslatedTypedef translatedTypedef:
return GetBackingType(library, translatedTypedef.UnderlyingType, out effectiveType, ref fieldIsEnum, ref fieldIsEnumWithSignedValues);
default:
return null;
}
default:
return null;
}
}
TypeReference effectiveType;
bool fieldIsEnum = false;
bool fieldIsEnumWithSignedValues = false;
CSharpBuiltinType? backingType = GetBackingType(context.Library, declaration.Type, out effectiveType, ref fieldIsEnum, ref fieldIsEnumWithSignedValues);
// Boolean bitfields require some special treatment
bool isBoolBitfield = backingType == CSharpBuiltinType.Bool;
if (isBoolBitfield)
{ backingType = CSharpBuiltinType.Byte; }
if (backingType is null || !backingType.IsIntegral)
{
Fatal(context, declaration, $"Bit field has an invalid type.");
return;
}
// To simplify some of the bit handling, we treat the backing field as unsigned
CSharpBuiltinType unsignedBackingType;
if (!backingType.IsSigned)
{ unsignedBackingType = backingType; }
else
{
if (backingType == CSharpBuiltinType.SByte)
{ unsignedBackingType = CSharpBuiltinType.Byte; }
else if (backingType == CSharpBuiltinType.Short)
{ unsignedBackingType = CSharpBuiltinType.UShort; }
else if (backingType == CSharpBuiltinType.Int)
{ unsignedBackingType = CSharpBuiltinType.UInt; }
else if (backingType == CSharpBuiltinType.Long)
{ unsignedBackingType = CSharpBuiltinType.ULong; }
else
{
Fatal(context, declaration, $"Unknown/unsupported signed C# integral type '{backingType}'.");
return;
}
// If the field is an enum, we pretend the field is unsigned (unless the enum explicitly has signed values.)
// See https://github.com/InfectedLibraries/Biohazrd/issues/71 for details
if (fieldIsEnum && !fieldIsEnumWithSignedValues)
{ backingType = unsignedBackingType; }
}
// Get all the relevant types as strings
string backingTypeString = GetTypeAsString(context, declaration, backingType);
string unsignedBackingTypeString = GetTypeAsString(context, declaration, unsignedBackingType);
string declarationTypeString = GetTypeAsString(context, declaration, effectiveType);
// Clang (and by extension, Biohazrd) puts the offset of the field at the nearest byte boundry
// We could just use the smallest underlying type can fit the bit field, but this doesn't work when the width of the field at the end of the bit field group
// would best be serviced by a 24-bit number. Consider the following layout:
// struct BitField3
// {
// unsigned int X : 9;
// unsigned int Y : 23;
// unsigned char Z;
// };
// Y is at byte offset 1, bit offset 1, and has a width of 23 bits.
// If we used a uint for it at this location, the uint would bleed into Z
// This could cause issues when writing to Y and Z from different threads, and could cause access violations for both reads and writes in cases where
// Z isn't there and the struct is allocated at the very end of a page and followed by an unallocated page or a guard page.
// As such, we unfortunately have to divine a "healthier" offset from the information Clang gives us.
long offset = declaration.Offset;
int bitOffset = declaration.BitOffset;
long extraOffset = offset % backingType.SizeOf;
offset -= extraOffset;
bitOffset += checked((int)(extraOffset * 8));
ulong bitMask = (1ul << declaration.BitWidth) - 1ul;
ulong inverseShiftedMask = ~(bitMask << bitOffset);
inverseShiftedMask &= backingType.FullBitMask;
Writer.EnsureSeparation();
if (Options.__DumpClangInfo)
{
Writer.WriteLine($"// ===== Extra bitfield info for {declaration.Name} =====");
Writer.WriteLine($"// Offset = {declaration.Offset} -> {offset}");
Writer.WriteLine($"// BitOffset = {declaration.BitOffset} -> {bitOffset}");
Writer.WriteLine($"// BitWidth = {declaration.BitWidth}");
Writer.WriteLine($"// Unshifted mask = 0x{bitMask:X8}");
Writer.WriteLine($"// Shifted ~mask = 0x{inverseShiftedMask:X8}");
}
// Write out the backing field
Writer.Using("System.Runtime.InteropServices");
string backingFieldName = SanitizeIdentifier($"__{declaration.Name}__backingField");
Writer.WriteLine($"[FieldOffset({offset})] private {unsignedBackingTypeString} {backingFieldName};");
Writer.WriteLine($"{declaration.Accessibility.ToCSharpKeyword()} {declarationTypeString} {SanitizeIdentifier(declaration.Name)}");
using (Writer.Block())
{
string getter;
// For signed types, we need to handle the sign extension
if (backingType.IsSigned)
{
int backingSizeOfBits = backingType.SizeOf * 8;
int leftShiftAmount = backingSizeOfBits - (bitOffset + declaration.BitWidth);
int rightShiftAmount = backingSizeOfBits - declaration.BitWidth;
getter = $"(({backingTypeString})({backingFieldName} << {leftShiftAmount})) >> {rightShiftAmount}";
}
else
{
getter = $"({backingFieldName} >> {bitOffset}) & 0x{bitMask:X}U";
}
// If the backing type doesn't match the declaration type or it isn't 4 or 8 bytes (IE: it isn't int, uint, long, or ulong) we need to cast the result
if (isBoolBitfield)
{ getter = $"({getter}) != 0"; }
else if (backingType != effectiveType || (backingType.SizeOf != 4 && backingType.SizeOf != 8))
{ getter = $"({declarationTypeString})({getter})"; }
Writer.WriteLine($"get => {getter};");
Writer.WriteLine("set");
using (Writer.Block())
{
Debug.Assert(unsignedBackingType.SizeOf <= 8, "Types larger than 64 bits are not handled below.");
CSharpBuiltinType intermediateBackingType = unsignedBackingType.SizeOf <= 4 ? CSharpBuiltinType.UInt : CSharpBuiltinType.ULong;
string intermediateBackingTypeString = GetTypeAsString(context, declaration, intermediateBackingType);
// uint shiftedValue = (((uint)value) & 0x00FF) << 16;
Writer.Write($"{intermediateBackingTypeString} shiftedValue = (");
if (isBoolBitfield)
{ Writer.Write($"(value ? 1U : 0U)"); }
else if (unsignedBackingType != effectiveType)
{ Writer.Write($"(({intermediateBackingTypeString})value)"); }
else
{ Writer.Write("value"); }
Writer.WriteLine($" & 0x{bitMask:X}U) << {bitOffset};");
// uint otherBits = backingField & inverseShiftedMask;
Writer.WriteLine($"{intermediateBackingTypeString} otherBits = {backingFieldName} & 0x{inverseShiftedMask:X}U;");
// backingField = otherBits | shiftedValue;
Writer.Write($"{backingFieldName} = ");
if (intermediateBackingType != unsignedBackingType)
{ Writer.Write($"({unsignedBackingTypeString})(otherBits | shiftedValue)"); }
else
{ Writer.Write("otherBits | shiftedValue"); }
Writer.WriteLine(";");
}
}
}
}
}